Spring相关知识点【详细版】

文章目录

SpringFramework的功能模块:

  1. SpringIoc:核心容器
  2. AOP:面向切面编程
  3. TX:声明式事务管理
  4. Spring MVC 提供了面向Web应用程序的集成功能。

SpringIoc---Spring的核心容器:

SpringIoc 容器接口:

  • BeanFactory:接口提供了一种高级配置机制,是SpringIoc容器标准化超接口。
  • ApplicationContext是BeanFactory的子接口,它扩展了以下功能:
    1. 更容易与Spring的AOP功能集成。
    2. 消息资源处理(用于国际化)
    3. 特定于应用程序给予此接口实现。

ApplicationContext 容器的实现类:

  • ClassPathXmlApplicationContext:通过读取类路径下的XML格式的配置文件创建IOC容器对象。
  • FileSystemXmlApplicationContext:通过文件系统路径读取XML格式的配置文件创建IOC容器对象。
  • AnnotationConfigApplicationContext:通过读取java配置类创建IOC容器对象。
  • WebApplicationContext:专门为Web应用准备,基于Web环境创建IOC容器对象,并将对象引入存入ServletContext域中。

SpringIoc 概念总结:

  1. IoC(Inversion of Control) 控制反转

    当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由IoC容器来创建和管理,即控制权由应用程序转移到了IoC容器中,也就是"反转"了控制权。

    1. DI(Dependency Injection) 依赖注入:

      DI是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理。在Spring中,DI是通过XML配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter方法注入和接口注入。

Spring IOC/DI实现的基本步骤

  1. 配置元数据(配置):

    配置元数据,既是编写,交给Spring IOC容器,管理组件的信息,配置方式有三种(基于XML的配置,基于注解的配置,基于类的配置)。

    基于XML的配置元数据的基本结构:

    spring 复制代码
    <bean id="..." class="...">
    
    </bean>

    Spring IOC容器管理一个或多个组件。这些组件是使用你提供给容器的配置元数据创建的

    一个标签代表一个组件信息声明。

    • id 属性是标识Bean定义的字符串,也就是Bean起的一个名字。
    • class 属性定义Bean的类型并使用完全限定的类名
    spring 复制代码
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
           
           <bean id="..." class="..."/>
           
           <bean id="..." class="..."/>
    
    </beans>

    该XML配置文件基本格式可以通过右键点击**resources资源包通过 new**

    可得

  2. 实例化IOC容器

    提供给ApplicationContext构造函数的位置路径是资源字符串地址,允许容器从各种外部资源 (如本地文件系统,java CLASSPATH等) 加载配置元数据。

    首先,我们应该选择一个合适的容器实现类,进行IOC容器的实例化工作:

    spring 复制代码
    //实例化IOC容器,读取外部配置文件,最终会在容器内进行ioc和di动作
    ApplicationContext context1=new ClassPathXmlApplicationContext("service.xml","dao.xml")
    
    ClassPathXmlApplicationContext context2=new ClassPathXmlApplicationContext("service.xml","dao.xml")
  3. 获取Bean(组件)

    ApplicationContext是一个高级工厂的接口,能够维护不同bean及其所依赖项的注册表。通过使用方法T getBean(String name, Class<T>requiredType),您可以检索bean的实例。

    允许读取Bean定义并访问它们,如以下救命所示:

    spring 复制代码
    //创建ioc容器对象,指定配置文件,ioc也开始实例组件对象
    ClassPathXmlApplicatonContext applicationcontext = new ClassPathXmlApplicationContext("services.xml","daos.xml");
    
    //获取ioc容器的组件对象
    PetStoreService service = applicationcontext.getBean("petstore",PetStoreService.class);
    
    //使用组件对象
    List<String> userList = service.getUsernameList();

基于 XML 配置方式组件管理

  1. 基于无参数构造函数:

    • 准备类似组件类:

      java 复制代码
      package com.jxnu.ioc
      
      public class HappyComponent{
      
      	//默认包含无参数构造函数
      	
      	public void doWork(){
      		System.out.println("HappyComponent.doWork");
      	}
      }
    • 编写配置文件:

      • 文件放置地址:resources/spring-ioc-01.xml
      spring 复制代码
      <bean id="happyComponent" class="com.jxnu.ioc.HappyComponent"/>
      • bean 标签: 通过配置bean标签告诉IOC容器需要创建对象的组件信息。
      • id属性:bean 的唯一标识,方便后期获取Bean!
      • class 属性 : 组件类的全限定符
      • 注意:要求当前组件类必须包含无参数构造函数!
  2. 基于静态工厂方法实例化

    除了使用构造函数实例化对象,还有一类是通过工厂模式实例化对象。

    • 准备类似组件类:

      java 复制代码
      package com.jxnu.ioc
      
      public class ClientService{
      	private static ClientService clientService = new ClientService();
      	private ClientService(){}
      	
      	public static ClientService createInstance(){
      		return clientService;
      	}
      }
    • 编写配置文件:

      • 文件放置地址:resources/spring-ioc-01.xml

        spring 复制代码
        <bean id="clientService" class="com.jxnu.ioc.ClientService" factory-method="createInstance"/>
        • bean 标签: 通过配置bean标签告诉IOC容器需要创建对象的组件信息。
        • id属性:bean 的唯一标识,方便后期获取Bean!
        • class 属性 : 组件类的全限定符
        • factory-method:指定静态工厂方法,注意,在静态工厂中,该方法必须是static方法。
  3. 基于实例工厂方法实例化

    • 准备组建类

      spring 复制代码
      package com.jxnu.ioc
      
      public class DefaultServiceLocator{
      	private static clientService = new ClientService();
      	
      	public ClientService createClientSERviceInstance(){
      		return clientService;
      	}
      }
    • 编写配置文件:

      • 文件放置地址:resources/spring-ioc-01.xml

        spring 复制代码
        //先将工厂类进行ioc配置
        <bean id="serviceLocator" class="com.jxnu.DefaultServiceLocator"></bean>
        
        //根据工厂对象的实例工厂方法进行实例组件对象
        <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
        • bean 标签: 通过配置bean标签告诉IOC容器需要创建对象的组件信息。
        • id属性:bean 的唯一标识,方便后期获取Bean!
        • factory-bean属性:指定当前容器中工厂 Bean 的名称。
        • factory-method:指定实例工厂方法名。注意在实例工厂中,实例方法必须是非static的!

组件(Bean)依赖注入配置(DI)

  1. 目标

    通过配置文件,实现IOC容器中Bean之间的引用(依赖注入DI配置)。

    主要涉及注入场景:基于构造函数的依赖注入和基于Setter的依赖注入。

  2. 基于构造函数的依赖注入(单个构造参数)

    • 介绍

      基于构造函数的 DI 是通过容器调用具有多个参数的构造函数来完成的,每个参数表示一个依赖项在。

      下面的救命演示一个只能通过构造函数注入进行依赖项注入的类!

    • 准备组件类

      java 复制代码
      package com.junx.ioc1
      
      public class UserDao{}
      
      public class UserService{
      	private UserDao userDao;
      	
      	public UserService(UserDao userDao){
      		this.userDao = userDao;
      	}
      }
    • 编写配置文件

      文件:resources/spring-ioc-02.xml

      spring 复制代码
      <beans>
      	<bean id="userDao" class="com.junx.ioc1.UserDao"/>
      	<bean id="userService" class="com.junx.ioc1.UserService">
      		<constructor-arg ref="userDao"/>
      	</bean>
      </beans>
      • constructor-arg 标签:可以引用构造参数 ref 引用其他 bean 的标识,用 value 引用一个的参数,例如数字或字符串。ref 和 value 这两个属性只能二选一。
  3. 基于构造函数的依赖注入(多个构造参数)

    • 介绍

      下面的示例演示通过构造函数注入多个参数,参数包含其他 bean 和基本数据类型!

    • 准备组件类

      spring 复制代码
      package com.junx.ioc1
      
      public class UserDao{}
      
      public class UserService{
      	private UserDao userDao;
      	
      	private int age;
      	
      	private String name;
      	
      	public UserService(UserDao userDao,String name,UserDao userDao){
      		this.userDao = userDao;
      		this.age=age;
      		this.name=name;
      	}
      }
    • 编写配置文件

      spring 复制代码
      //多个参数,可以按照相应构造函数参数的顺序注入数据
      <beans>
      	//被引用类的声明
      	<bean id="userDao" class="com.jxnu.ioc1.UserDao"/>
      	<bean id="userService" class="com.jxnu.ioc1.UserService">
      		//value或ref直接注入基本类型值(按顺序来)
      		<constructor-arg ref="uerDao"/>
      		<constructor-arg value="张三"/>
      		<constructor-arg value="18"/>
      	</bean>
      </beans>
      
      //多个参数,可以按照相应构造函数参数的名称注入数据
      <beans>
      	//被引用类的声明
      	<bean id="userDao" class="com.jxnu.ioc1.UserDao"/>
      	<bean id="userService" class="com.jxnu.ioc1.UserService">
      		//value或ref按照参数名称来
      		<constructor-arg name="name" value="张三"/>
      		<constructor-arg name="userDao" ref="userDao"/>
      		<constructor-arg value="18"/>
      	</bean>
      </beans>
      • constructor-arg 标签:指定构造参数和对应的值
      • constructor-arg 标签属性:name属性指定参数名、ref 属性指定其它bean的标识、value 属性指定普通属性值。
  4. 基于Setter方法依赖注入

    • 介绍

      开发中,除了构造函数注入(DI)更多的使用的Setter方法进行注入!

      下面的示例演示一个只能使用纯Setter注入进行依赖项注入的类。

    • 准备组件类

      java 复制代码
      package com.jxnu.ioc1
      
      public class MovieFinder{}
      
      public class SimpleMovieLister{
      	
      	private MovieFinder movieFinder;
      	
      	private String movieName;
      	
      	public void setMovieFinder(MovieFinder movieFinder){
      		this.movieFinder=movieFinder;
      	}
      	
      	public void setMoiveName(String movieName){
      		this.movieName=movieName;
      	}
      }
    • 编写配置文件

      spring 复制代码
      //被引用的类
      <bean id="movieFinder" class="com.jxnu.ioc1.MovieFinder"/>
      
      <bean id ="SimpleMovieLister" class="com.jxnu.ioc1.SimpleMovieLister">
      	//name属性:是指被引用函数除去set后,将开头字母小写后的函数名,一般为参数名。
      	//ref属性:是指引用bean的id值。
      	//value属性:是指基本类型值。
      	<property name="movieFinder" ref="movieFinder"/>
      	<property name="movieName" value="hello world"/>
      </bean>
      • property 标签:可以给setter方法对应的属性赋值。
      • property 标签:name 属性代表 set 方法标识、ref 代表引用 bean 的标识 id 、value 属性代表基本属性值。
  5. 总结:

    • 依赖注入(DI)包含引用类型和基本数据类型,同时注入的方式也有多种!主流的注入方式为setter方法注入的构造函数注入。

    • 需要特别注意:引用其他 bean ,使用 ref 属性。直接注入基本类型值,使用 value 属性。

IOC 容器创建和使用

  1. 容器实例化

    spring 复制代码
    //方法1:实例化并指定配置文件,推荐
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("services.xml","a.xml");
    
    //方法2:先实例化,再指定配置文件,最后刷新容器触发Bean实例化动作。
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext()
    context.setConfigLocations("services.xml","a.xml");
    context.refresh();
  2. Bean 对象读取

    spring 复制代码
    //方法1:根据id获取
    //因为没有指定类型,返回为Object,所以需要类型转化
    HappyComponent happyComponent = (HappyComponent) context.getBean("bean的id标识")//context是容器实例化对象。
    //使用组件对象
    happyComponent.doWork()
    
    //方法2:根据类型获取,但是这种方法要求:在XML配置中只有一个bean对应着该类,不能有多个bean对应该类。
    HappyComponent happyComponent1 = context.getBean(HappyComponent.class);
    happyComponent.doWork();
    
    //方法3:根据id和类型获取 
    HappyComponent happyComponent2 = context.getBean("bean的id标识",HappyComponent.class);

高级特性:组件(Bean)作用域和同期方法配置

  1. 组件周期方法配置

    • 周期方法概念:

      我们可以在组件类中定义方法,然后当IOC容器实例货物销毁组件对象的时候进行调用!这两个方法我们称为生命周期方法!

      类似于Servlet的inin/destroy方法和Vue中的mouted和beforeDestroy方法,我们可以在周期方法中完成初始化和释放资源等工作。

    • 周期方法声明

      java 复制代码
      package com.jxnu.ioc3
      
      public class BeanOne{
          public voie init(){//初始化逻辑}
      }
          
      public class BeanOne{
          public voie cleanup(){//释放资源逻辑}
      }
    • 周期方法配置

      spring 复制代码
      <beans>
          <bean id="beanOne" class="com.jxnu.ioc3.BeanOne" init-method="init" />
          <bean id="beanTwo" class="com.jxnu.ioc3.BeanTwo" destroy-method="cleanup"
      </beans>
  2. 组件作用域配置

    • Bean 作用域概念

      <bean>标签声明 Bean ,只是将 Bean 的信息配置给 IOC 容器中

      在 IOC 容器中,这些<bean>标签将对应的信息转成 Spring内部 BeanDefinition对象,BeanDefinition对象内,包含定义的信息(id,class,属性等等)!

      这意味着,BeanDefinition概念一样,IOC容器可以根据BeanDefinition对象反射创建多个Bean对象实例。

      具体创建多少个 Bean 的实例对象,由 Bean 的作用域 scope 属性来决定。

    • scope作用域可以选值

      取值 取值 创建对象的时机 默认值
      singleton 在IOC容器中,这个bean的对象始终为单实例 IOC容器初始化时
      prototype 这个bean在IOC容器中有多个实例 获取bean时

      如果是在WebApplicationContext 环境下还会有另外两个作用域(但是不常用):

      取值 含义 创建对象的时机 默认值
      request 请求范围内有效的实例 每次请求
      session 会话范围内有效的实例 每次会话
    • 配置 scope 范围

      spring 复制代码
      //scope属性:取值为singleton(默认),bean在IOC容器中只有一个实例,IOC容器初始化时创建对象。
      //scope属性:取值为prototype, bean 在IOC容器中可以有多个实例,getBean()时创建对象
      <bean id="happyMachine" scope="prototype" class="com.jxnu.ioc3.HappyMachine"/>
      
      <bean id="happyComponent" class="com.jxnu.ioc3.HappyComponent"/>

高级特性:FactoryBean 特性和使用

  1. FactoryBean 简介

    FactoryBean 接口 是用于配置复杂的 Bean 对象,可以将创建过程存储在 FactoryBean 的 getObject 方法中。

    FactoryBean<T> 接口提供三种方法:

    • T getObject<T>:

      返回此工厂创建的对象的实例。该返回值会被存储到IOC容器中!

    • boolean isSingleton():

      默认是返回 true ,如果是返回单例,则是 true,否则是 false。

    • Class<?> getObjectType():

      返回getObject()方法返回的对象类型,如果事先不知道类型,则返回 null。

  2. FactoryBean 使用场景

    • 代理类的创建
    • 第三方框架整合
    • 复杂对象实例化等
  3. FactoryBean 的应用

    • 准备 FactroyBean 实现类

      java 复制代码
      package com.jxnu.ioc4
      
      public class HappyMachineFactoryBean implements FactoryBean<HappyMachine>{
      	
      	private String machineName;
      	
      	public String getMachineName(){
      		return machineName;
      	}
      	
      	public void setMachineName(String machineName){
      		this.machineName=machineName;
      	}
      	
      	@Override
      	public HappyMachine getObject() throws Exception{
      		//方法内部模拟创建,设置一个对象的复杂过程。
      		HappyMachine happyMachine = new HappyMachine();
      		
      		happyMachine.setMachineName(this.machineName);
      		
      		return happyMachine;
      	}
      	
      	@Override
      	public Class<?> getObjectType(){
      		//返回要生产的对象的类型
      		return HappyMachine.class;
      	}
      }
    • 配置 FactoryBean 实现类

      spring 复制代码
      <bean id="happyMachine" class="com.jxnu.ioc4.HappyMachineFactoryBean">
      	<property name="machineName" value="张三"/>
      </bean>
    • FactoryBean 和 BeanFactory 的区别

      • FactoryBean 是用来实现复杂实例化bean对象的,它是一个特殊的bean。可自定义逻辑创建的bean。

      • BeanFactory 是 Spring 框架的基础,是ApplicaionContext 的父接口。

      • 总的来说,FactoryBean 和 BeanFactory 的区别主要是前者是用来创建Bean的接口,后者是IOC的实例化接口,是用来管理 Bean 的。

基于注解方式管理 Bean

Bean 注解标记和扫描(IOC)

  1. 步骤:

    • 标记,可以用@Component、@Repository、@Controller、@Service。@Component是基础命名,另外三个是基础命名的别名。
    • 扫描,在XML上用<context:来指定注解标记在哪些包上,加快扫描速度。
  2. 准备Spring 项目和组件:

    • 准备项目 pom.xml

    • 准备组件类:

      • 普通组件:

        java 复制代码
        @Component(value="hello world")
        public class CommonComponent{}

        在这里 value 的作用是在IOC容器中给CommonComponent 实例对象起一个id 也就是在XML上中的id。

        这里它的id 名是hello world。

      • Controller组件:

        java 复制代码
        @Controller
        public class XxxController{}
      • Service组件:

        java 复制代码
        @Service
        public class XxxService{}
      • Dao组件:

        java 复制代码
        @Repository
        public class XxxDao{}
    • 配置文件确定扫描范围

      • 情况1:基本扫描配置(常用)

        spring 复制代码
        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
                                   http://www.springframework.org/schema/beans/spring-beans.xsd
                                    http://www.springframework.org/schema/context
                                    http://www.springframework.org/schema/context/spring-context.xsd ">
        
            <context:component-scan base-package="com.jxnu.ioc01"/>
        
        </beans>
      • 情况2:指定排除组件

        spring 复制代码
        <?xml version="1.0" encoding="UTF-8"?>
        <beans xmlns="http://www.springframework.org/schema/beans"
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
               xsi:schemaLocation="http://www.springframework.org/schema/beans
                                   http://www.springframework.org/schema/beans/spring-beans.xsd
                                    http://www.springframework.org/schema/context
                                    http://www.springframework.org/schema/context/spring-context.xsd ">
        
            <context:component-scan base-package="com.jxnu.ioc01">
                <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
            </context:component-scan>
        
        </beans>
  3. 组件BeanName问题:

    • 默认情况下:

      类名首字母小写就是 bean 的 id。例如: SolidierController 类对应的 bean 的id 就是 soldierController。

    • 使用 value 属性指定:

      java 复制代码
      @Controller(value="hello world")//或者@Controller("hello world")
      public class SolidierController{}

组件(Bean)作用域和周期方法注解

  1. 组件周期方法配置

    它需要
    作为依赖。

    • 周期方法概念

      我们可以在组件类中定义方法,然后当IOC容器实例化和销毁组件对象的时候进行调用!这两个方法我们叫作生命周期方法!

      类似于Servlet 的init/destroy 方法,我们可以在周期方法完成初始化和释放资源等工作。

    • 周期方法声明

      java 复制代码
      package com.jxnu.ioc2
      
      public class BeanOne{
      
      	@PostConstruct	//要求方法必须是 public void 无形参列表
      	public void init(){}
      }
      
      public class BeanTwo{
      	
      	@PreDestroy
      	public void cleanup(){}
      }
  2. 组件作用域配置

    如果你想在test/java中测试的话,你需要引入依赖

    java 复制代码
    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON)//单例,默认值
    @Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)//多例,【单例,多例二选一】
    public class BeanOne{
    	@PostConstruct
    	public void init(){}
    }

Bean 属性赋值:引用类型自动装配(DI)

  1. 自动装配实现

    • @Autowired 注解

      在成员变量上直接标记 @Autowired 注解即可,不需要提供 setXxx()方法。

    • 给 Controller 装配 Service

      java 复制代码
      @Controller
      public class SoldierController{
      	@Autowired
      	private SoldierService soldierService;
      	
      	public void getMessage(){
      		soldierService.getMessage();
      	}
      }
  2. @Autowired 注解细节

    • 标记位置:

      1. 在成员变量上。
      2. 在构造函数上。
      3. 在setter方法上。
    • 工作流程:

      • 首先根据所需要的组件类型到IOC 容器中查找

        • 能够找到唯一的 bean :直接执行装配

        • 如果完全找不到匹配这个类型的 bean :装配失败

        • 和所需类型匹配的 bean 不只一个时

          • 没有@Qualifier 注解:根据 @Autowired标记位置的成员变量的变量名作为 bean 的 id 进行匹配

            java 复制代码
            @Controller
            public class SoldierController{
            	@Autowired
            	private SoldierService soldierService;
            }

            在这里根据 SoldierService soldierService中的soldierService作为 bean 的 id

            • 能够找到:执行装配
            • 找不到:装配失败
          • 使用@Qualifier 注解:根据@Qualifier 注解中指定的名称作为 bean 的 id 进行匹配

            • 能够找到:执行装配
            • 找不到:装配失败
        java 复制代码
        @Controller
        public class SoldierController{
        	@Autowired
        	@Qualifier(value="hello_world")//指定查找id为hello world的IOC实例对象。
        	private SoldierService soldierService;
        }

        也可以用@Resource(name="hello world") == @Autowired + @Qualifier(value="hello world")

Bean 属性赋值:基本类型属性赋值(DI)

  1. @Value通常用于注入外部化属性
  • 声明外部配置

    /resources/application.properties

    复制代码
    name=hello_world
  • xml 引入外部配置

    spring 复制代码
    <context:property-placeholder location="application.properties"/>
  • @Value 注解读取配置

    java 复制代码
    @Component
    public class CommonComponent{
    	@Value("${name:nokey}")//{key:defaultValue}在外部配置中如果有key对应的值,则用对应的值,如果没有则用defaultValue自己写的值 。
    	private String name;
    }
  1. 一般直接在类中赋值作为DI

    java 复制代码
    @Component
    public class CommonComponet{
    	private String name="hello_world";
    }

基于 配置类 方式管理 Bean

  1. 完全注解开发理解

    Spring 完全注解配置是通过 java 配置类代码来配置 Spring 应用程序,即:使用注解来替代原本在XML 配置文件中的配置。

    两种方式思维转化:

配置类和扫描注解

  1. 配置类:

    用配置类 代替 之前用的 XML

    写在config/JavaConfiguration中

    使用 @Configuration 注解将一个普通的类标记为 Spring 的配置类

    java 复制代码
    @Configuration	//是表明它是一个配置类
    @ComponentScan(basePackage={"com.jxnu.ioc"})	//是表明它的扫描范围
    @PropertySource(value={"classpath:jdbc.properties"})		//是表明注解读取外部配置
    public class JavaConfiguration{}
  2. 创建IOC 容器:

    java 复制代码
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(JavaConfiguration.class);
  3. 总结:

    • @Configuration 指定一个类为配置类,在配置类中可以
      • 表明注解扫描范围,用ComponentScan(basePackages={""})
      • 引入外部配置,比如:jdbc.properties,用PropertySource(value={""})
      • 引入第三方包,例如:DruidDataSource,jdbcTemplate

@Bean 方式实现

  1. 需要分析:第三方jar 包的类,要添加到IOC 容器中,无法使用 @Component 等相关注解,如jdbcTemplate,Druid等等包,所以只能在配置类中用@Bean 来配置第三方包。

    jdbcTemplate 有upate 方法可以用来增删改,有queryForObject,query用来查。其中查时,当数据库属性名和java后端属性名一致时,或可以用 select class as classes 的方法让其属性名一致,当一致时,可以用 List studentList = jdbcTemplate.query(sql,BeanPropertyRowMapper<>(Student.class));

  2. 用配置类的方式实现

    java 复制代码
    @Configuration	//用来定义配置类
    @ComponentScan(basePackages={"com.jxnu.ioc"})	//用来确定扫描范围
    @PropertySource(value={"classpath:jdbc.properties"})	//用来引入外部文件
    public class JavaConfiguration{
    	@Bean("hello world")	//在这里Druid组件的id 为"hello world",默认情况为函数名也就是druidDataSource
    	public DruidDataSource druidDataSource(@Value(${jdbc.url}) String url 
    												 @Value(${jdbc.driver}) String driver
    												 @Value(${jdbc.username}) String username
    												 @Value(${jdbc.password}) String password){
    			//在配置类通过@Bean标签可以自动在IOC容器中创建对应的第三方包实例对象。								
                DruidDataSource druidDataSource = new DruidDataSource();
                druidDataSource.setUrl(url);
                druidDataSource.setDriver(driver);
                druidDataSource.setUsername(username);
                druidDataSource.setPassword(password);
                
                return druidDataSource
         }
    
    }

高级特性:@Bean 注解细节

  1. 指定@Bean 的名称

    java 复制代码
    @Configuration
    public class JavaConfiguration{
    
    	@Bean("hello world")	//在这里id的名称是"hello world",但是默认情况是函数名称,也就是jdbcTemplate。
    	public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
    		JdbcTemplate jdbcTemplate = new JdbcTemplate();
    		jdbcTemplate.setDataSource(druidDataSource);
    		return jdbcTemplate
    	}
    }
  2. @Bean 初始化和销毁方法

    @Bean注解支持指定任意初始化和销毁回调方法,非常类似于 Spring XML 在 bean 标签上的 init-methoddestroy-method 属性

    java 复制代码
    public class BeanOne{
    	pubilc void init(){}
    }
    public class BeanTwo{
    	public void cleanUp(){}
    }
    
    @Configuration
    public class JavaConfiguration{
    	@Bean(initMethod = "init")
    	public BeanOne beanOne{
    		return new BeanOne();
    	}
    	@Bean(destroyMethod = "cleanUp")
    	public BeanTwo beanTwo{
    		return new BeanTwo();
    	}
    }
  3. @Bean Scope 作用域

    可以指定使用 @Bean注释定义的 bean 应具有的特定范围,默认是单例。

    java 复制代码
    @Configuration
    public class JavaConfiguration{
    	@Bean
    	@Scope("prototype")
    	public A a(){
    		return new A();
    	}
    }
  4. @Bean 方法之间的依赖

    • 案例:JdbcTemplate 的实例化需要引入DruidDataSource连接池。

    java 复制代码
    @Configuration
    public class JavaConfiguration{
        @Bean
        public DataSource druidDataSource(){
            ......
            return druidDataSource;
        }
        @Bean
        public JdbcTemplate jdbcTemplate(DataSource druidDataSource){//可以直接在参数中,引入需要在IOC中的对象
            JdbcTemplate jdbcTemplate = new JdbcTemplate();
            jdbcTemplaet.setDataSource(druidDataSource);
            return jdbcTemplate
        }
    }

    通过方法参数传递 Bean实例的引用 来解决 Bean 实例之间的依赖关系

  5. @Import 扩展

    • @Import 注释允许从另一个配置加载 @Bean 定义

      java 复制代码
      @Configuration
      pubilc class ConfigA{
      	@Bean
      	public A a{
      		return new A();
      	}
      }
      
      @Configuration
      @Import(ConfigA.class)
      pubilc class ConfigB{
      	@Bean
      	public B b{
      		return new B();
      	}
      }

      这样在实例化对象时,就不用指定两个配置类了,只需要一个ConfigB,就可以代表ConfigB和ConfigA。

      java 复制代码
      AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigB.class);

整合 Spring5-Test5 搭建测试环境

  1. 整合测试环境作用

    • 不需要自己创建IOC容器对象了
    • 任何需要的 bean 都可以在测试类中直接享受自动装配
  2. 导入相关依赖

    maven 复制代码
    <!--junit5测试-->
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.3.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>6.0.6</version>
        <scope>test</scope>
    </dependency>
  3. 整合测试注解使用

    java 复制代码
    //@SpringJUnitConfig(location = {"classpath:spring-xml.xml"})	//指定基于XML的配置文件xml
    @SpringJUnitConfig(value = {JavaConfiguration.class})	//指定配置类
    public class Test{
    	@Autowired	//自动装配
    	private JdbcTemplate jdbcTemplate;
    	@Test
    	public void HaveATry(){
    		System.out.Println(jdbcTemplate);
    	}
    }

Spring AOP 面向切面编程

Spring AOP 框架介绍和关系梳理

  1. AOP(面向切面编程) 是一种区别于 OOP(面向对象编程)的编程思维,用来完善和解决OOP 的非核心代码冗余和不方便统一维护问题!

  2. 代理技术(动态代理|静态代理)是实现AOP 思维编程的具体技术,但是自己使用动态代理实现代码比较繁琐!

  3. Spring AOP 框架,基于 AOP编程思维,封装了动态代理技术,简化了动态代理技术实现的框架!Spring AOP 内部帮助我们实现动态代理,我们只需要写少量的配置,指定生效范围即可。

    spirng中有ioc, 有四种,aop有两种,tx,mybaits,spring MSC

Spring AOP 基于注解方式实现和细节(常用)

初步实现

  1. 加入依赖

    spring 复制代码
    <!-- spring-aspects会帮我们传递过来aspectjweaver -->
    <dependency>//在这里我们可以用spring-context,因为spring-context传递了spring-aop
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>6.0.6</version>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.0.6</version>
    </dependency>
  2. 准备接口

    java 复制代码
    public interface Calculator {
        
        int add(int i, int j);
        
        int sub(int i, int j);
        
        int mul(int i, int j);
        
        int div(int i, int j);
        
    }
  3. 实现纯净类

    java 复制代码
    package com.atguigu.proxy;
    
    
    /**
     * 实现计算接口,单纯添加 + - * / 实现! 掺杂其他功能!
     */
    @Component
    public class CalculatorPureImpl implements Calculator {
        
        @Override
        public int add(int i, int j) {
        
            int result = i + j;
        
            return result;
        }
        
        @Override
        public int sub(int i, int j) {
        
            int result = i - j;
        
            return result;
        }
        
        @Override
        public int mul(int i, int j) {
        
            int result = i * j;
        
            return result;
        }
        
        @Override
        public int div(int i, int j) {
        
            int result = i / j;
        
            return result;
        }
    }
  4. 声明切面类

    有:前置:@Before,后置:@AfterReturning,异常:@AfterThrowing,最后:@After,环绕:@Around
    放在/aspect/LogAspect下

    复制代码
    package com.atguigu.advice;
    
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    // @Aspect表示这个类是一个切面类
    @Aspect
    // @Component注解保证这个切面类能够放入IOC容器
    @Component
    public class LogAspect {
            
        // @Before注解:声明当前方法是前置通知方法
        // value属性:指定切入点表达式,由切入点表达式控制当前通知方法要作用在哪一个目标方法上
        @Before(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
        public void printLogBeforeCore() {
            System.out.println("[AOP前置通知] 方法开始了");
        }
        
        @AfterReturning(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
        public void printLogAfterSuccess() {
            System.out.println("[AOP返回通知] 方法成功返回了");
        }
        
        @AfterThrowing(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
        public void printLogAfterException() {
            System.out.println("[AOP异常通知] 方法抛异常了");
        }
        
        @After(value = "execution(public int com.atguigu.proxy.CalculatorPureImpl.add(int,int))")
        public void printLogFinallyEnd() {
            System.out.println("[AOP后置通知] 方法最终结束了");
        }
        
    }
  5. 开启 aspectj 注解支持

    • xml方式

      spring 复制代码
      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
      
          <!-- 进行包扫描-->
          <context:component-scan base-package="com.jxnu" />
          <!-- 开启aspectj框架注解支持-->
          <aop:aspectj-autoproxy />
      </beans>
    • 配置类方式

      spring 复制代码
      @Configuration
      @ComponentScan(basePackages = "com.atguigu")
      //作用等于 <aop:aspectj-autoproxy /> 配置类上开启 Aspectj注解支持!
      @EnableAspectJAutoProxy
      public class MyConfig {
      }

获取通知细节信息

  1. JoinPoint 接口

    需要获取方法签名,传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。

    • 要点1 :JoinPoint 接口通过 getSignature() 方法获取目标方法的签名(方法声明时的完整信息)
    • 要点2 :通过目标方法签名对象获取方法名。
    • 要点3 :通过 JoinPoint 对象获取外界调用目标方法时传入的实参列表组成的数组。
    java 复制代码
    @Before(value="execution(* com..impl.*.*(..))")
    public void before(JoinPoint joinPoint){
    	//获取方法的签名对象
    	Signature signature = joinPoint.getSignature();
    	
    	//通过方法的签名对象,获取目标方法的详细信息
    	String methodName = signature.getName();
    	
        //返回的是 pubilc abstract
    	int modifiers = signature.getModifier();
    	
        //返回的是类的路径
    	String declaringTypeName = signature.getDeclaringTypeName();
    	
    	//通过JoinPoint,获取参数数组。
    	Object[] args = joinPoint.getArgs();
    	
    	//由于数组直接打印看不到具体的数据,所以转换为List集合
    	List<Object> argList = Arrays.asList(args);
    	
    }
  2. 方法返回值

    在返回通知中,通过@AfterReturning 注解的returning 属性获取目标方法的返回值!

    java 复制代码
    @AfterReturning(value="execution(* com..impl.*.*(..))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
    
    }
  3. 异常对象捕捉

    在异常通知中,通过@AfterThrowing 注解的 throwing 属性获取目标方法抛出的异常对象

    java 复制代码
    @AfterThrowing(value="execution=("* com..impl.*.*(..)")",throwing="throwable")
    public void afterThrowing(JoinPoint joinPoint,Throwable throwable){
    
    }

切点表达式语法

  1. 切点表达式作用

    AOP 切点表达式是一种用于指定切点的语言,它可以通过定义匹配规则 ,来选择需要被切入的目标对象。

  2. 切点表达式语法

重用(提取)切点表达式

我们可以将切点提取,在增强上进行引用即可!

  • 切点统一管理

    建议:将切点表达式统一存储到一个类中进行集中管理和维护

    放在/pointCut/中

    java 复制代码
    package com.jxnu.pointCut
    @Component
    public class XxxPointCut{
    	
    	@Pointcut(value="execution(* com..Impl.*.*(..))")
    	public void pc1(){}
    	
    	@Pointcut(value="execution(public int com.jxnu.Entity.Impl.CalculatorImpl.*(..))")
    	public void pc2(){}
    }
  • 在不同类中引用

    java 复制代码
    @Before(value="com.jxnu.pointCut.XxxPointCut.pc1()")
    public void before(JoinPoint joinPoint){}

环绕通知

环绕能对应整个 try...catch...finally 结构,包括前面四种通知的所有功能。

放在/aspect/中

java 复制代码
package com.jxnu.aspect
@Component
public class XxxAspect{
	@Around(value="com.jxnu.pointCut.PointCut.XxxAspect()")
	public Object around(ProceedingJoinPoint joinPoint){
	//用@Around时,方法参数要用ProceedingJoinPoint 
		Object[] args = joinPoint.getArgs();
		
		Object result;
		try{
			//写前置任务
			........
			
			//目标方法返回值
			result = joinPoint.proceed(args);
			
			//写后置任务
			........
		}catch(Throwable e){
			System.out.Println(e.getClass().getName());
		}finally{
			........
		}
		return result;
	}
}

切面优先级设置---当有多个切面时

使用 @Order 注解可以控制切面的优先级:

  • @Order(较小的数):优先级高
  • @Order(较大的数):优先级低

要导入包 import org.springframework.core.annotation.Order;

CGLib动态代理生效

在目标类没有实现任何接口的情况下,Spring 会自动使用 cglib 技术实现代理。

如果目标类有接口的情况下,Spring 会自动使用 jdk 技术实现代理。
若目标类有接口,则将其继承接口的增强类放入IOC中(其接口的实现类,目标类,不会放入IOC中)
若目标类没有接口,则将其目标类的子类,也是增强类放入IOC中(目标类是不会放入IOC中的)

Spring AOP 基于XML 方式实现(了解)

  1. 配置Spring 配置文件

    java 复制代码
    <!-- 配置目标类的bean -->
    <bean id="calculatorPure" class="com.atguigu.aop.imp.CalculatorPureImpl"/>
        
    <!-- 配置切面类的bean -->
    <bean id="logAspect" class="com.atguigu.aop.aspect.LogAspect"/>
        
    <!-- 配置AOP -->
    <aop:config>
        
        <!-- 配置切入点表达式 -->
        <aop:pointcut id="logPointCut" expression="execution(* *..*.*(..))"/>
        
        <!-- aop:aspect标签:配置切面 -->
        <!-- ref属性:关联切面类的bean -->
        <aop:aspect ref="logAspect">
            <!-- aop:before标签:配置前置通知 -->
            <!-- method属性:指定前置通知的方法名 -->
            <!-- pointcut-ref属性:引用切入点表达式 -->
            <aop:before method="printLogBeforeCore" pointcut-ref="logPointCut"/>
        
            <!-- aop:after-returning标签:配置返回通知 -->
            <!-- returning属性:指定通知方法中用来接收目标方法返回值的参数名 -->
            <aop:after-returning
                    method="printLogAfterCoreSuccess"
                    pointcut-ref="logPointCut"
                    returning="targetMethodReturnValue"/>
        
            <!-- aop:after-throwing标签:配置异常通知 -->
            <!-- throwing属性:指定通知方法中用来接收目标方法抛出异常的异常对象的参数名 -->
            <aop:after-throwing
                    method="printLogAfterCoreException"
                    pointcut-ref="logPointCut"
                    throwing="targetMethodException"/>
        
            <!-- aop:after标签:配置后置通知 -->
            <aop:after method="printLogCoreFinallyEnd" pointcut-ref="logPointCut"/>
        
            <!-- aop:around标签:配置环绕通知 -->
            <!--<aop:around method="......" pointcut-ref="logPointCut"/>-->
        </aop:aspect>
        
    </aop:config>
  2. 测试

    java 复制代码
    @SpringJUnitConfig(locations = "classpath:spring-aop.xml")
    public class AopTest {
    
        @Autowired
        private Calculator calculator;
    
        @Test
        public void testCalculator(){
            System.out.println(calculator);
            calculator.add(1,1);
        }
    }

Spring 声明式事务---基于 注解的声明式事务

事务的作用:事务是用来保证一系列数据库操作要么全部成功,要么全部失败的一种机制。

也就是添加了一套try...catch...finally...
作用步骤 1. 装配事务管理实现对象 mybaits和jdbcTemplate 用DataSourceTransactionManager

  1. 用 @Transactional 标签放在方法或类上即可

基本事务控制

  1. 准备依赖

    spring 复制代码
    <dependencies>
      <!--spring context依赖-->
      <!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>6.0.6</version>
      </dependency>
    
      <!--junit5测试-->
      <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter-api</artifactId>
          <version>5.3.1</version>
      </dependency>
    
      <!--用来@SpringJUnitConfig,不用手动创建IOC和组件实例化的-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>6.0.6</version>
          <scope>test</scope>
      </dependency>
      
      <!--注解的依赖-->
      <dependency>
          <groupId>jakarta.annotation</groupId>
          <artifactId>jakarta.annotation-api</artifactId>
          <version>2.1.1</version>
      </dependency>
    
      <!-- 数据库驱动 和 连接池-->
      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.25</version>
      </dependency>
    
      <!--用来DruidDataSource连接池的-->
      <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.2.8</version>
      </dependency>
    
      <!-- spring-jdbc,里面有 DataSourceTransactionManager 接口 -->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>6.0.6</version>
      </dependency>
    
      <!-- 声明式事务依赖,用来一致性的-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-tx</artifactId>
          <version>6.0.6</version>
      </dependency>
    
     // <!--用来Spring AOP的面向切面的编程,但是Spring-context会传递这个-->
     // <dependency>
     //     <groupId>org.springframework</groupId>
     //     <artifactId>spring-aop</artifactId>
     //     <version>6.0.6</version>
     // </dependency>
     
      <!--用来Spring AOP的面向切面的编程-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>6.0.6</version>
      </dependency>
    </dependencies>
  2. 外部配置文件

    jdbc.properties

    java 复制代码
    jxnu.url=jdbc:mysql://localhost:3306/studb  //或者 jxnu.url= jdbc:mysql:///studb
    jxnu.driver=com.mysql.cj.jdbc.Driver
    jxnu.username=root
    jxnu.password=password
  3. Spring 配置文件

    spring 复制代码
    @Configuration
    @ComponentScan(value="com.jxnu")
    @PropertySource("classpath:jdbc.properties")
    @EnableTransactionManager	//在配置文件中允许事务
    public class JavaConfiguration{
    
    	@Bean
    	public DataSource dataSource(@Value("${jxnu.url}") String url,@Value("${jxnu.driver}" String driver)
    				@Value("${jxnu.username}") String username,@Value("${jxnu.password}") String password)	 
    	{
    		DruidDataSource druidDataSource = new DruidDataSource();
    		druidDataSource.setUrl(url);
    		druidDataSource.setDriver(driver);
    		druidDataSource.setUsername(username);
    		druidDataSource.setPassword(password);
    		return druidDataSource;
    	}
    	
    	@Bean
    	public JdbcTemplate jdbcTemplate(DataSource dataSource){
    		JdbcTemplate jdbcTemplate = new JdbcTemplate();
    		jdbcTemplate.setDataSource(dataSource);
    		return jdbcTemplate;
    	}
    	
    	@Bean	//装配事务管理实现对象
    	public TransactionManager transactionManager(DataSource dataSource){
    		DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
    		dataSourceTransactionManager.setDataSource(dataSource);
    		return dataSourceTransactionManager;
    		//或者 return new DataSourceTransactionManager(dataSource)就可以了。
    	}
    }
  4. 准备 dao/service 层

    • dao
    java 复制代码
    @Repository
    public class StudentDao{
    
    	@Autowired
    	private JdbcTemplate jdbcTemplate;
    	
    	public void updateNameById(String name,Integer id){
    		String sql = "update students set name = ? where id= ?";
    		int rows = jdbcTemplate.update(sql,name,id);
    	}
    	
    	public void updateAgeById(Integer age,Integer id){
    		String sql = "update students set age = ? where id= ?";
    		int rows = jdbcTemplate.update(sql,age,id);
    	}
    }
    • service
    java 复制代码
    @Service
    public class StudentService{
    	
    	@Autowired
    	private StudentDao studentDao;
    	
    	@Transactional	//也可以放在类上,代表类中每一个方法都有事务。
    	public void changeInfo(){
    		studentDao.updateAgeById(100,1);
    		
    		studentDao.updateNameById("test1",1);
    	}
    }

事务属性:只读

  1. 只读介绍:

    对一个查询操作来说,如果我们把它设置成只读,更快。

    一般情况下,都是通过类添加注解,添加事务

    类下的所有方法都有事务

    查询方法可以通过再次添加只读事务,提高效率!

    查询方法不需要事务,因为它不会改变数据库中的数据!

  2. 设置方式

    jaav 复制代码
    @Transactional(readOnly="true")

    默认值是false

超时时间

  1. 需求:

    事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源。

  2. 设置超时时间

    java 复制代码
    @Transactional(reanOnly=true,timeout = 3)	// 超时3秒就回滚,释放资源 。

事务属性:事务异常

  • 设置回滚异常

    rollbackFor 属性:指定哪些异常类才会回滚,默认是 RuntimeException 和 Error 异常方可回滚。

    java 复制代码
    @Transactional(timeout=3,readOnly=true,rollbackFor=Expection.class)

事务传播行为

即当一个函数调用一个,或多个具有事务的子函数,子函数是独立,还是与父函数具有相同的事务。就是与子函数之间,是否一起成功和失败,还是子函数之间相互不影响。

  1. propagation 属性:

    名称 含义
    REQUIRED 如果父方法有事务,就加入,如果没有就自己独立
    REQYUERS_NEW 不管父方法是否有事务,都独立
  2. 代码

    java 复制代码
    @Transactional(propagation = Propogation.REQUIRES_NEW)

    如果你想要把哪个子函数的事务独立出来,就在哪个子函数上加REQUIERS_NEW

  3. 注意:

    • propagation属性是加在子函数上的,默认是REQUIRED
    • 父函数与子函数不能在同一个类上。因为无法代理

Spring 核心掌握总结

核心点 掌握目标
spring框架理解 spring 家族和 spring framework 框架
spring核心功能 ioc/di ,aop,tx
spring ioc/di 组件管理、ioc 容器、ioc/di、三种配置方式
spring aop aop 和 aop 框架和代理技术、基于注解的aop 配置
spring tx 声明式和编程式事务、动态事务管理器、事务注解、属性
相关推荐
靠沿3 小时前
Java数据结构初阶——LinkedList
java·开发语言·数据结构
qq_12498707533 小时前
基于springboot的建筑业数据管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·后端·毕业设计
一 乐3 小时前
宠物管理|宠物共享|基于Java+vue的宠物共享管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·springboot·宠物
白露与泡影3 小时前
MySQL中的12个良好SQL编写习惯
java·数据库·面试
foundbug9994 小时前
配置Spring框架以连接SQL Server数据库
java·数据库·spring
凯酱4 小时前
@JsonSerialize
java
-大头.4 小时前
JVM框架实战指南:Spring到微服务
jvm·spring·微服务
悦悦子a啊4 小时前
项目案例作业(选做):使用文件改造已有信息系统
java·开发语言·算法
lkbhua莱克瓦244 小时前
Java项目——斗地主小游戏(控制台版)
java·开发语言·windows·斗地主项目