https://blog.csdn.net/shawnyang2019/article/details/102568833

Spring注解驱动开发

组件注册

一、组件注册-@Configuration&@Bean给容器中注册组件

  • 环境配置

    1
    2
    3
    4
    5
    6
    7
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Person {
    private int id;
    private String username;
    }
  • ==@Configuration==

    1
    2
    @Configuration//告诉Spring这是一个配置类
    public class MainConfig {}
  • ==@Bean:给容器中注册一个Bean;类型为返回值类型,id默认是是用方法名作为id==

    • 使用方式1

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      //配置类 == 配置文件
      @Configuration//告诉Spring这是一个配置类
      public class MainConfig {
      @Bean //id默认为类的首字母小写
      public Person person() {
      return new Person(1 , "韩信");
      }
      }

      public class PersonTest {
      @Test
      public void test1() {
      ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);

      Person person = context.getBean(Person.class);
      System.out.println(person);
      }
      }
- **使用方式2:给配置多个bean,@Bean( "bean的id值" )**

    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//配置类 == 配置文件
@Configuration//告诉Spring这是一个配置类
public class MainConfig {

@Bean("person1")
public Person person() {
return new Person(1 , "韩信");
}

@Bean("person2")
public Person person01() {
return new Person(2 , "李白");
}
}

public class PersonTest {
@Test
public void test1() {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
Person person1 = context.getBean("person1",Person.class);
Person person2 = context.getBean("person2",Person.class);
System.out.println(person1);
System.out.println(person2);
}
}

二、组件注册-@ComponentScan和@ComponentScans自动扫描组件和指定扫描规则

1
2
3
@ComponentScanvalue 指定要扫描的包
excludeFilters = Filter[]:指定排除规则,指定扫描的时候按照扫描规则排除哪些组件
includeFilters = Filter[]:指定要的时候需要包含安歇组件包,需要将默认的扫描规则设置为false(useDefaultFilters = false
  • ==@ComponentScan==

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //@ComponentScan:指定要扫描的包
    @ComponentScan(value = {"com.hngy.lf"})

    //excludeFilters:指定排除规则,填写的规则将不被扫描
    @ComponentScan(value = {"com.hngy.lf"}, excludeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
    })

    //includeFilters:指定要的时候需要包含安歇组件包,,需要将默认的扫描规则设置为false(useDefaultFilters = false)
    @ComponentScan(value = {"com.hngy.lf"}, includeFilters = {
    @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class})
    }, useDefaultFilters = false)
  • 在JDK8中==@ComponentScan==注解可以写多个,不是JDK1.8可以使用==@ComponentScans==注解

    1
    2
    3
    //使用多个@ComponentScan注解
    @ComponentScan(value = {"com.hngy.lf.service"})
    @ComponentScan(value = {"com.hngy.lf.dao"})
  • 不是JDK1.8可以使用==@ComponentScans==注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //@ComponentScans注解里面是一个@ComponentScan注解数组,可以添加多个@ComponentScan注解
    public @interface ComponentScans {
    ComponentScan[] value();
    }

    //========================================使用================================================
    @ComponentScans({
    @ComponentScan(value = {"com.hngy.lf.service"}) ,
    @ComponentScan(value = {"com.hngy.lf.dao"})
    })

三、组件注册-自定义@TypeFilter指定过滤规则

==使用@Filter注解:需要将默认的扫描规则设置为false(useDefaultFilters = false)==

  • @ComponentScan模板,过滤规则可以填写多个
1
2
3
4
5
6
7
8
9
10
11
12
13
@ComponentScan
(
value = {"com.hngy.lf"},
excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Service.class}) ,
},
includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}) ,
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}) ,
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) ,
},
useDefaultFilters = false
)
  • @ComponentScan注解中的属性
1
2
3
4
5
6
7
excludeFilters = Filter[]:指定排除规则,指定扫描的时候按照扫描规则排除哪些组件
includeFilters = Filter[]:指定要的时候需要包含安歇组件包,需要将默认的扫描规则设置为false(useDefaultFilters = false)
FilterType.ANNOTATION :按照注解
FilterType.ASSIGNABLE_TYPE :按照给定的类型,只要是对应的类型都会被加载,子类,父类
FilterType.ASPECTJ :使用ASPECTJ表达式
FilterType.REGEX :使用正则表达式
FilterType.CUSTOM :自定义规则,需要实现TypeFilter接口已
1
2
3
4
5
6
//@Filter注解可以配置多个,使用指定过滤规则需要将 ”useDefaultFilters = false“ 设置为false,否则指定的过滤规则不起作用
@ComponentScan(value = {"com.hngy.lf"}, includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = {Controller.class}) ,
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {BookService.class}) ,
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) ,
}, useDefaultFilters = false)
  • 使用@TypeFilter注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//========================================配置类================================================
@ComponentScan(value = {"com.hngy.lf"}, includeFilters = {
@ComponentScan.Filter(type = FilterType.CUSTOM, classes = {MyTypeFilter.class}) ,}, useDefaultFilters = false)
public class MainConfig {}


//========================================实现了TypeFilter接口的实现类================================================
public class MyTypeFilter implements TypeFilter {
/**
* @param metadataReader 读取到的当前正在扫描的类的信息
* @param metadataReaderFactory 可以获取到其他任何信息的
*/
@Override
public boolean match(MetadataReader metadataReader , MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();

//获取当前正在扫描的类的信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();

//这里为获取com.hngy.li包下的所有类
String className = classMetadata.getClassName();
System.out.println("className = " + className);

//获取当前类的资源信息(类的路径)
Resource resource = metadataReader.getResource();

//指定一个规则
return className.contains("BookService");
}
}

四、组件注册-@Scope-设置组件作用域

Spring容器管理的bean在默认情况下是单例的,也即,一个bean只会创建一个对象,存在内置 map中,之后无论获取多少次该bean,都返回同一个对象。

==@Scope:设置是否为单例,Spring默认采用单例方式,减少了对象的创建,从而减少了内存的消耗。==

但是在实际开发中是存在多例的需求的(如购物车,用户的订单等),Spring也提供了选项可以将bean设置为多例模式。

  • 属性值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //singleton:单实例的 :    @Scope("singleton")
    //prototype:多实例的 : @Scope("prototype")
    //request:同一次请求创建一个实例 : @Scope("request")
    //session:同一个session创建的一个实例 : @Scope("session")

    <!--
    scope:
    singleton - 单例,默认值
    容器创建时创建对象并保存到容器内部
    自后无论获取多少次,都是从容器内部中获取该对象返回,所以得到的将是同一个对象
    直到容器被销毁时,对象被移除出容器
    prototype - 多例
    容器初始化时,不会创建多例对象,只是将多例bean信息保存
    每次获取多例对象时,都会根据保存的bean信息创建新的对象并返回
    且这些对象不会保存到容器内部的map中
    对象何时被销毁完全取决于用户程序本身
    -->
  • 在Spring中默认为单实例(单例的,对象创建一次就不会创建了)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //========================================配置类================================================
    @Configuration//告诉Spring这是一个配置类
    @ComponentScan(value = {"com.hngy.lf"})
    public class MainConfig {

    @Bean
    public Person person() {
    return new Person(1 , "韩信");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    @Test
    public void test1() {
    ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
    Person person1 = context.getBean("person" , Person.class);
    Person person2 = context.getBean("person" , Person.class);
    System.out.println(person1 == person2); ///输出为 :true
    }
  • 指定@Scope值为多例的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //singleton:单实例的 :    @Scope("singleton")
    //prototype:多实例的 : @Scope("prototype")
    //request:同一次请求创建一个实例 : @Scope("request")
    //session:同一个session创建的一个实例 : @Scope("session")
    @Bean
    @Scope("prototype")//设置为多实例(多例)
    public Person person() {
    return new Person(1 , "韩信");
    }
    1
    2
    3
    4
    5
    6
    7
    @Test
    public void test1() {
    ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
    Person person1 = context.getBean("person" , Person.class);
    Person person2 = context.getBean("person" , Person.class);
    System.out.println(person1 == person2); //因为设置了为多实例:输出为 :false
    }
  • ==注意:==
    • ==当作用域为单例的时候,IOC容器在启动(new AnnotationConfigApplicationContext(MainConfig.class),该代码执行的时候)的时候,就会将容器中所有作用域为单例的bean的实例给创建出来;以后的每次获取,就直接从IOC容器中来获取,相当于是从map.get()的一个过程;==
    • ==也就是说当bean是作用域为多例的时候,IOC容器启动的时候,就不会去创建bean的实例的,而是当我们调用getBean()( new AnnotationConfigApplicationContext(MainConfig.class).getBean())获取的时候去创建bean的实例;而且每次调用的时候,都会创建bean的实例;==

五、组件注册-@Lazy-bean-懒加载

==@Lazy注解 :懒加载机制只对单例bean有作用,使用该注解之前相当于单例模式中的饿汉式,使用了该注解之后相当于单例模式中的懒汉式 ==

写在配置类上,表示该类下的所有Bean都为懒加载,写在单独的Bean上,只是该Bean为懒加载

懒加载机制只对单例bean有作用,对于多例bean设置懒加载没有意义

懒加载只是延后了对象创建的时机,对象仍然是单例的。

  • 懒加载:是专门针对于单实例的bean的
    • 单实例的bean:默认是在容器启动的时候创建对象;
    • 懒加载:容器启动的时候,不创建对象,而是在第一次使用(获取)Bean的时候来创建对象,并进行初始化
  • 使用:

    1
    2
    3
    4
    5
    6
    @Bean
    @Lazy
    public Person person() {
    System.out.println("1");
    return new Person(1 , "韩信");
    }
  • 为全局配置懒加载(当需要设置的懒加载的bean过多时,不用一个个的去设置可以设置==default-lazy-init=”true”,不设置默认为false==)

    1
    2
    3
    4
    5
    6
    7
    <!--========================================配置文件实现全局懒加载================================================-->
    <?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"
    default-lazy-init="true">
    </beans>

六、组件注册-@Conditional-懒加载

==@Conditional:是SpringBoot底层大量使用的注解,按照一定的条件来进行判断,满足条件 给容器注册bean==

@Conditional 的这个注解还可以标注在类上,对容器中的组件进行统一设置:满足当前条件,这个类中配置的所有bean注册才能生效

  • 现在下面的两个bean注册到IOC容器是要条件的:
    • 如果系统是windows,给容器注册(“bill”)
    • 如果系统是linux,给容器注册(“linus”)
  • 判断是否是Windows

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * 判断是否为Windows系统
    */
    public class WindowsCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context , AnnotatedTypeMetadata metadata) {
    //3.能获取当前环境的信息
    Environment environment = context.getEnvironment();
    //获取操作系统
    String property = environment.getProperty("os.name");
    assert property != null;

    //返回property是否包含windows字符串
    return property.contains("Windows");
    }
    }
  • 判断是否是Linux

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    /**
    * 判断操作系统是否为Linux系统
    */
    public class LinuxCondition implements Condition {
    /**
    * ConditionContext : 判断条件能使用的上下文(环境)
    * AnnotatedTypeMetadata : 注释信息
    */
    @Override
    public boolean matches(ConditionContext context , AnnotatedTypeMetadata metadata) {
    //判断是否为Linux系统
    //1.能获取到IOC容器里面的BeanFactory
    ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();

    //2.获取类加载器
    ClassLoader classLoader = context.getClassLoader();
    //3.能获取当前环境的信息
    Environment environment = context.getEnvironment();
    //4.获取到bean定义的注册类
    BeanDefinitionRegistry registry = context.getRegistry();

    //获取操作系统
    String property = environment.getProperty("os.name");
    assert property != null;

    //返回property是否包含linux字符串
    return property.contains("linux");
    }
    }
  • 配置类

    1
    2
    3
    4
    5
    6
    //满足当前条件,这个类中配置的所有bean注册才能生效
    //在类上加入该注解
    @Conditional({WindowsCondition.class})

    //在方法上加入该注解
    //满足当前条件,这个bean注册才能生效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//配置类 == 配置文件
@Configuration//告诉Spring这是一个配置类
@ComponentScan(value = {"com.hngy.lf"})
public class MainConfig {

/**
* 现在下面的两个bean注册到IOC容器是要条件的:
* 1.如果系统是windows,给容器注册("bill")
* 1.如果系统是linux,给容器注册("linus")
*/
@Conditional({WindowsCondition.class})
@Bean("bill")
public Person person01() {
return new Person(62 , "Bill Gates");
}

@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person02() {
return new Person(48 , "linus");
}

}

七、组件注册-@Import-给容器中快速导入一个组件

==使用了该注解之后,相当于将该组件以Bean的形式注册到了Spring中,相当于==

而当我们使用了 @Import(Color.class)注解之后,我们可以看到IOC容器里面就有这个组件了,id默认就是这个组件的全类名:

  • 实体类

    1
    2
    3
    4
    5
    6
    7
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Person {
    private int id;
    private String username;
    }
  • 使用该注解之前的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Configuration//告诉Spring这是一个配置类
    @ComponentScan(value = {"com.hngy.lf"})
    public class MainConfig { }

    //======================================测试========================================
    @Test
    public void test2() {
    Map<String, Person> person = context.getBeansOfType(Person.class);
    System.out.println(person); // 输出 : {}
    }
  • 使用该注解之后:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Configuration//告诉Spring这是一个配置类
    @ComponentScan(value = {"com.hngy.lf"})
    @Import(Person.class)
    public class MainConfig { }

    //======================================测试========================================
    @Test
    public void test2() {
    Map<String, Person> person = context.getBeansOfType(Person.class);
    System.out.println(person);//输出{com.hngy.lf.pojo.Person=Person(id=0, username=null)}
    }

八、组件注册-@Import-使用ImportSelector

==ImportSelector是一个接口:返回需要的组件的全类名的数组;==

1
2
3
4
5
6
7
8
9
public interface ImportSelector {

/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);

}

==自定义逻辑返回需要导入的组件,需要实现 ImportSelector 这个接口:==

1
2
3
4
5
6
7
8
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//不要返回null,会报空指针异常
return new String[0];
}
}

==可以这样来写:@Import({Color.class,Red.class,MyImportSelector.class})==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
//满足当前条件,这个类中配置的所有bean注册才能生效
@Conditional({WindowsCondition.class})
//快速导入组件,id默认是组件的全类名
@Import({MyImportSelector.class})
public class MainConfig2 {

/**
* 现在下面的两个bean注册到IOC容器是要条件的:
* 1.如果系统是windows,给容器注册("bill")
* 1.如果系统是linux,给容器注册("linus")
*/
@Bean("bill")
public Person person01() {
return new Person("Bill Gates",62);
}

@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person02() {
return new Person("linus",48);
}

/**
* 给容器中注册组件:
* 1)、扫描+组件标注注解(@Controller/@Service/@Repository/@Component
* 【局限于要求是自己写的类,如果导入的第三方没有添加这些注解,那么就注册不上了】
*
* 2)、@Bean[导入的第三方包里面的组件]
* 3)、@Import[快速的给容器中导入一个组件]
* (1)、 @Import(要导入容器中的组件);容器中就会自动的注册这个组件,id默认是全类名
* (2)、 ImportSelector :返回需要的组件的全类名的数组;
*/
}

九、组件注册- @Import-使用ImportBeanDefinitionRegistrar

==添加一个自定义的组件==

==ImportBeanDefinitionRegistrar是一个接口:==

1
2
3
4
public interface ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}

==自定义一个MyImportBeanDefinitionRegistrar类并且实现ImportBeanDefinitionRegistrar这个接口:==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata 当前类的注解信息
* BeanDefinitionRegistry BeanDefinition注册类
*
* 我们把所有需要添加到容器中的bean通过BeanDefinitionRegistry里面的registerBeanDefinition方法来手动的进行注册
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//判断IOC容器里面是否含有这两个组件
boolean definition = registry.containsBeanDefinition("com.ldc.bean.Red");
boolean definition2 = registry.containsBeanDefinition("com.ldc.bean.Blue");
//如果有的话,我就把RainBow的bean的实例给注册到IOC容器中
if (definition && definition2) {
//指定bean的定义信息,参数里面指定要注册的bean的类型:RainBow.class
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(RainBow.class);
//注册一个bean,并且指定bean名
registry.registerBeanDefinition("rainBow", rootBeanDefinition );
}
}
}

十、组件注册-使用FactoryBean注册组件

  • FactoryBean:接口

    1
    2
    3
    4
    5
    6
    7
    public interface FactoryBean<T> {
    T getObject() throws Exception;

    Class<?> getObjectType();

    boolean isSingleton();
    }
  • 创建一个类,并且实现 FactoryBean 接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //创建一个Spring定义的FactoryBean
    public class ColorFactoryBean implements FactoryBean<Color> {

    //返回一个Color对象,这个对象会添加到容器中
    @Override
    public Color getObject() throws Exception {
    System.out.println("ColorFactoryBean...getBean...");
    return new Color();
    }

    //返回的类型
    @Override
    public Class<?> getObjectType() {
    return Color.class;
    }

    //控制是否为单例
    // true:表示的就是一个单实例,在容器中保存一份
    // false:多实例,每次获取都会创建一个新的bean
    @Override
    public boolean isSingleton() {
    return true;
    }
    }
  • 这个时候,在配置类里面进行配置:可以看到表面上装配的是ColorFactoryBean这个类型,但是实际上我们装配的是Color这个bean的实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    @Configuration
    //满足当前条件,这个类中配置的所有bean注册才能生效
    @Conditional({WindowsCondition.class})
    //快速导入组件,id默认是组件的全类名
    @Import({Color.class,Red.class,MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
    public class MainConfig2 {

    /**
    * 给容器中注册组件:
    * 1)、扫描+组件标注注解(@Controller/@Service/@Repository/@Component
    * 【局限于要求是自己写的类,如果导入的第三方没有添加这些注解,那么就注册不上了】
    *
    * 2)、@Bean[导入的第三方包里面的组件]
    * 3)、@Import[快速的给容器中导入一个组件]
    * (1)、 @Import(要导入容器中的组件);容器中就会自动的注册这个组件,id默认是全类名
    * (2)、 ImportSelector :返回需要的组件的全类名的数组;
    * (3)、 ImportBeanDefinitionRegistrar : 手动注册bean到容器中
    *
    * 4)、使用Spring提供的FactoryBean(工厂bean)
    * (1)、默认获取到的是工厂bean调用getObject创建的对象
    * (2)、要获取工厂bean本身,我们需要给id前面加上一个“&”符号:&colorFactoryBean
    */

    @Bean
    public ColorFactoryBean colorFactoryBean() {
    return new ColorFactoryBean();
    }
    }
  • 如果我们就想要获取这个工厂bean,我们就可以在id的前面加上一个”&”符号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @Test
    public void test4() {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class);
    printBeans(applicationContext);

    //工厂bean获取的是调用getObject方法创建的对象
    Object colorFactoryBean = applicationContext.getBean("colorFactoryBean");
    System.out.println("bean的类型:"+colorFactoryBean.getClass());

    //如果我们就想要获取这个工厂bean,我们就可以在id的前面加上一个"&"符号
    Object colorFactoryBean2 = applicationContext.getBean("&colorFactoryBean");
    System.out.println("bean的类型:"+colorFactoryBean2.getClass());
    }

    private void printBeans(ApplicationContext applicationContext) {
    String[] definitionNames = applicationContext.getBeanDefinitionNames();
    for (String name : definitionNames) {
    System.out.println(name);
    }
    }

生命周期

  • bean的生命周期:bean的创建->初始化->销毁的过程
  • 容器管理bean的生命周期:
  • 我们可以自定义初始化方法和销毁的方法:容器在bean进行到当前的生命周期的时候,来调用我们自定义的初始化方法和销毁方法
    构造(对象创建):
    • 单实例:在容器启动的时候创建对象
    • 多实例:在每次获取的时候来创建对象
  • 初始化方法:
    • 对象创建完成,并赋值好,调用初始化方法
  • 销毁方法:
    • 单实例的bean:在容器关闭的时候进行销毁
    • 多实例的bean:容器不会管理这个bean,容器不会调用销毁的方法

生命周期-@Bean-指定初始化和销毁方法

  • 指定初始化方法和销毁方法:

    • 我们可以通过@Bean(initMethod = “init”,destroyMethod = “destroy”)来指定初始化方法和销毁方法
    • 相当于xml配置文件bean标签里面的 init-method=”” 和 destroy-method=”” 属性
  • Person类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Person {
    private int id;
    private String username;

    public Person() {
    System.out.println("Person ---> 给IOC容器中添加Bean");
    }

    public void init() {
    System.out.println("初始化方法");
    }

    public void destroy() {
    System.out.println("销毁方法");
    }
    }
  • 配置类配置初始化方法和销毁方法

    1
    2
    3
    4
    5
    6
    7
    8
    @Configuration//告诉Spring这是一个配置类
    @ComponentScan(value = {"com.hngy.lf"})
    public class MainConfig {
    @Bean(initMethod = "init" ,destroyMethod = "destroy")
    public Person person(){
    return new Person();
    }
    }
  • 测试类

    1
    2
    3
    4
    5
    6
    7
    8
    @Test
    public void test1() {
    //执行了Person类中的构造函数和初始化方法
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);

    //执行了Person类中的销毁方法
    context.close();
    }

生命周期-InitializingBean-和-DisposableBean

InitializingBean接口:在bean的初始化方法调用之后进行调用。DisposableBean接口:在Bean的销毁方法调用之后进行调用

通过bean实现InitializingBean(定义初始化逻辑), DisposableBean(定义销毁逻辑);

  • 写一个类实现DisposableBean接口和InitializingBean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Cat implements DisposableBean, InitializingBean {
    public Cat() {
    System.out.println("Cat的构造方法");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    System.out.println("Cat ---> 初始化方法");
    }

    @Override
    public void destroy() throws Exception {
    System.out.println("Cat ---> 销毁方法");

    }
    }
  • 配置类中Cat的配置方法

    1
    2
    3
    4
    @Bean
    public Cat cat() {
    return new Cat();
    }
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void test2() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
    /*
    Cat的构造方法
    Cat ---> 初始化方法
    */
    context.close();//Cat ---> 销毁方法
    }

生命周期- @PostConstruct&@PreDestroy

  • 可以使用JSR250规范里面定义的两个注解:只能定义在方法上的注解

    • @PostConstruct :在bean创建完成并且属性赋值完成,来执行初始化方法
    • @PreDestroy :在容器销毁bean之前通知我们来进行清理工作
  • Dog类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Dog {
    public Dog() {
    System.out.println("Dog ----> 构造函数");
    }

    @PostConstruct
    public void init() {
    System.out.println("Dog ---> 初始化方法");
    }

    @PreDestroy
    public void destroy() {
    System.out.println("Dog ---> 销毁方法");
    }
    }
  • Dog类在配置类中的配置

    1
    2
    3
    4
    @Bean
    public Dog dog() {
    return new Dog();
    }
  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Test
    public void test1() {
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
    /*
    Dog ----> 构造函数
    Dog ---> 初始化方法
    */
    context.close();//Dog ---> 销毁方法
    }

生命周期-BeanPostProcessor-后置处理器

  • BeanPostProcessor接口:bean的后置处理器,在bean初始化前后做一些处理工作,这个接口有两个方法:

    • postProcessBeforeInitialization:在初始化之前工作

    • postProcessAfterInitialization:在初始化之后工作

    • 执行顺序

      1
      2
      3
      postProcessBeforeInitialization();//初始化方法之前执行
      init();//初始化方法执行
      postProcessAfterInitialization();//初始化方法之后执行
  • 写一个类实现接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class Man implements BeanPostProcessor {
    public Man() {
    System.out.println("Man ---> 构造函数");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean , String beanName) throws BeansException {
    System.out.println("postProcessBeforeInitialization..." + beanName + "=>" + bean);
    return BeanPostProcessor.super.postProcessBeforeInitialization(bean , beanName);
    }

    @Override
    public Object postProcessAfterInitialization(Object bean , String beanName) throws BeansException {
    System.out.println("postProcessAfterInitialization..." + beanName + "=>" + bean);
    return BeanPostProcessor.super.postProcessAfterInitialization(bean , beanName);
    }
    }
  • 配置类中注册Bean

    1
    2
    3
    4
    @Bean
    public Man dog() {
    return new Man();
    }

生命周期-BeanPostProcessor-原理

生命周期- BeanPostProcessor-在Spring底层的使用

属性赋值

属性赋值-@Value-赋值

在属性上使用@Value注解给属性赋值赋值

1
2
3
4
5
6
7
8
9
public class Person {

@Value("张三")
private String name;
@Value("18")
private Integer age;

//省略get和set方法
}

属性赋值- @PropertySource 加载外部配置文件

使用@PropertySource注解加载外部配置文件,读取配置文件中的值赋给属性

  • ==XML配置文件读取配置文件==

    1
    <context:property-placeholder location="classpath:person.properties"/>
  • ==注解读取配置文件:==查看源码的时候,我们可以发现,这个注解是一个可重复标注的注解,可多次标注,也可以在一个注解内添加外部配置文件位置的数组,我们也可以用PropertySources内部包含多个PropertySource :

    • @PropertySources注解:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Repeatable(PropertySources.class)
      public @interface PropertySource {

      String name() default "";

      String[] value();

      boolean ignoreResourceNotFound() default false;

      String encoding() default "";

      Class<? extends PropertySourceFactory> factory() default PropertySourceFactory.class;

      }
    • @PropertySources :内部可以指定多个@PropertySource

      1
      2
      3
      4
      5
      6
      7
      8
      @Target(ElementType.TYPE)
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface PropertySources {

      PropertySource[] value();

      }
- **在配置类中使用注解加载配置文件加载配置文件:**

    
1
2
3
@PropertySource(value = {"classpath:/jdbc.properties"})//加载配置文件
@Configuration
public class MainConfig { }
- 读取配置文件中的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Person {
//使用@Value赋值
//1.基本的数值
//2.可以写SpEL: #{}
//3.可以写${}:取出配置文件【properties】中的值(在运行环境变量里面的值)

//1.基本的数值
@Value("张三")
private String name;

//2.可以写SpEL: #{}
@Value("#{20-2}")
private Integer age;

//3.可以写${}:取出配置文件【properties】中的值(在运行环境变量里面的值)
@Value("${jdbc.driverClass}")
private String jdbcName;
}
  • ==可以用Environment里面的getProperty()方法来获取:==
- 配置类

    
1
2
3
@PropertySource(value = {"classpath:/jdbc.properties"})//加载配置类
@Configuration
public class MainConfig { }
- properties配置文件
1
2
3
jdbc.username = root
jdbc.password = root
jdbc.driverClass = com.mysql.cj
- 测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void valueAnnotationTest() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);

ConfigurableEnvironment environment = context.getEnvironment();

String username = environment.getProperty("jdbc.username");
String password = environment.getProperty("jdbc.password");
String property = environment.getProperty("jdbc.driverClass");

System.out.println("username = " + username);//username = root
System.out.println("password = " + password);//password = root
System.out.println("driverClass = " + property);//driverClass = com.mysql.cj
}

自动装配- @Autowired & @Qualifier & @Primary

==@Autowired 和 @Autowired(required = false)==

  • @Autowired:自动注入
    • (1)默认优先按照类型去容器中去找对应的组件:applicationContext.getBean(BookDao.class);如果找到了则进行赋值;
    • (2)如果找到了多个相同类型的组件,再将属性的名称作为组件的id去容器中查找applicationContext.getBean(“bookDao”)
    • (3)@Qualifier(“bookDao”):使用 @Qualifier 指定需要装配的组件的id,而不是使用属性名
    • (4)自动装配默认一定要将属性赋值好,没有就会报错,可以使用@Autowired(required = false);来设置为非必须的
    • (5)可以利用@Primary:让Spring在进行自动装配的时候,默认使用首选的bean也可以继续使用@Qualifier(“bookDao”)来明确指定需要装配的bean的名字
  • ==@Autowired & @Qualifier & @Primary 注解的优先级:==
    • @Autowired注解:自动装配@Autowired注解是必须要使用的。@Autowired注解是根据属性的名字来进行自动装配的,如果该类型的Bean有两个,当属性名字和两个及以上Bean都不一样的时候会报错误,这时需要使用到@Qualifier注解来直接指定自动装配的属性名,或使用@Primary注解指定该类型的Bean优先匹配@Primary注解标注的Bean
    • @Qualifier注解和@Autowired注解的使用:@Qualifier注解需要和@Autowired注解一起使用。@Qualifier指定装配哪个Bean之后,该属性只能装配该Bean,不能改变,@Qualifier注解的优先级比@Primary优先级高
    • @Primary 注解:使用@Primary 标注的Bean,属性自动装配的时候会首先匹配该注解标注的Bean,同一个类型的Bean只能有一个Bean使用该注解来标注,否则会报错误,如果非要标注两个及以上的@Primary注解,需要该类型的Bean全部都添加@Qualifier注解
    • ==三个注解优先级:@Qualifier(根据指定的Bean的id名) > @Primary(指定Bean的优先装配) > @Autowired(根据标注的属性的名称自动装配)==
  • ==注意:当使用自动装配的时候必须要有至少一个属性该类型的Bean,否则会报错,应为@Autowired注解中 required 属性值默认为true,所以必须要有至少一个该类型的Bean。这个时候不想给属性赋值,又想使用自动自动装配,可以使用:@Autowired(required = false),将 required 属性的值设置为false就不会报错了。不过此属性的值为null==
  • @Qualifier注解和@Aurowired注解的使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    @Service
    public class BookService {
    @Autowired
    private BookDao bookDao;

    @Qualifier("userDao")
    @Autowired
    private UserDao userdao;
    }
  • @Primary注解的使用:当没有使用@Qualifier注解实现自动装配的属性会优先使用@Primary标注的Bean

    1
    2
    3
    4
    5
    6
    7
    @Primary
    @Bean("bookDao3")
    public BookDao bookDao2() {
    BookDao bookDao = new BookDao();
    bookDao.setLable("3");
    return bookDao;
    }

自动装配- @Resource & @Inject

Spring还支持使用 @Resource(JSR250) 和 @Inject (JSR330)(Java中的)

  • (1)@Resource:可以和@Autowired一样实现自动的装配,默认是按照组件的名称来进行装配,没有支持@Primary
    也没有支持和@Autowired(required = false)一样的功能
  • (2)@Inject:需要导入javax.inject的包,和@Autowired的功能一样,没有支持和@Autowired(required = false)一样的功能,支持@primary注解)
  • @Resource使用:

    1
    2
    3
    4
    5
    @Service
    public class BookService {
    @Resource
    private BookDao bookDao;
    }
  • 我们也可以用@Resource注解里面的name属性来指定装配哪一个:

    1
    2
    3
    4
    5
    6
    @Service
    public class BookService {

    @Resource(name = "bookDao2")
    private BookDao bookDao;
    }
  • @Inject的使用==(@Inject支持@primary注解)==:

    • 导入jar包:

      1
      2
      3
      4
      5
      <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
      </dependency>
    • 使用==(@Inject支持@primary注解)==:

      1
      2
      3
      4
      5
      6
      @Service
      public class BookService {

      @Inject
      private BookDao bookDao;
      }

自动装配-方法、构造器位置的自动装配

@Autowired注解可以标注的位置:构造器,参数,方法,属性;都是从容器中来获取参数组件的值

1
2
3
4
5
6
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
boolean required() default true;
}
  • @Autowired注解标注在方法上:用的最多的方式就是在@Bean注解标注的方法的参数,这个参数就是会从容器中获取,在这个方法的参数前面可以加上@Autowired注解,也可以省略,默认是不写@Autowired,都能自动装配;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @Component
    public class Boss {

    private Car car;

    @Autowired //标注在方法上,Spring容器在创建当前对象的时候,就会调用当前方法完成赋值;
    //方法使用的参数,自定义类型的值从IOC容器里面进行获取
    public void setCar(Car car) {
    this.car = car;
    }
    }
  • @Autowired注解标注在构造器上,默认加在IOC容器中的组件,容器启动的时候会调用无参构造器创建对象,再进行初始化赋值操作,构造器要用的组件,也都是从容器中来获取:

  • 注意:如果组件只有一个有参的构造器,这个有参的构造器的 @Autowired注解可以省略,参数位置的组件还是可以自动从容器中获取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //默认加在IOC容器中的组件,容器启动的时候会调用无参构造器创建对象,再进行初始化赋值操作
    @Component
    public class Boss {

    private Car car;

    //构造器要用的组件,也都是从容器中来获取
    @Autowired
    public Boss(Car car) {
    this.car = car;
    System.out.println("Boss的有参构造器"+car);
    }
    }
  • @Autowired注解标注在参数上(方法中的形参):效果是一样的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //默认加在IOC容器中的组件,容器启动的时候会调用无参构造器创建对象,再进行初始化赋值操作
    @Component
    public class Boss {

    private Car car;

    //构造器要用的组件,也都是从容器中来获取
    //我们也可以标注在参数上效果是一样的
    public Boss(@Autowired Car car) {
    this.car = car;
    System.out.println("Boss的有参构造器"+car);
    }
    }
  • 如果Boss这个类里面只有一个有参构造器,在构造器里面不加@Autowired注解也是可以的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //默认加在IOC容器中的组件,容器启动的时候会调用无参构造器创建对象,再进行初始化赋值操作
    @Component
    public class Boss {

    private Car car;

    //构造器要用的组件,也都是从容器中来获取

    //我们也可以标注在参数上效果是一样的
    public Boss(Car car) {
    this.car = car;
    System.out.println("Boss的有参构造器"+car);
    }
    }
  • 在配置类中传入参数进行装配():

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Configuration
    @ComponentScan({"com.ldc.service","com.ldc.dao","com.ldc.controller","com.ldc.bean"})
    public class MainConfigOfAutowired {

    //@Bean标注的方法创建对象的时候,方法参数的值从容器中获取
    @Bean
    public Color color(Car car) {
    Color color = new Color();
    color.setCar(car);
    return color;
    }

    }

自动装配-Aware注入Spring底层组件&原理

自定义组件想要使用Spring容器底层的一些组件(ApplicationContext、BeanFactory…)

自定义组件==实现xxxAware接口==就可以实现,在创建对象的时候,会调用接口规定的方法注入相关的组件;

把Spring底层的一些组件注入到自定义的bean中;

xxxAware等这些都是利用后置处理器的机制,比如ApplicationContextAware 是通过ApplicationContextAwareProcessor来进行处理的;

  • 实现ApplicationContextAware 接口,里面有一个setApplicationContext方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Component
    public class Dog implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.applicationContext = applicationContext;
    }
    }
  • Aware 是一个总接口:

    Aware子接口.webp

    1
    public interface Aware { }
  • 实现部分Aware的子接口,进行测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @Component
    public class Red implements ApplicationContextAware, BeanNameAware , EmbeddedValueResolverAware {

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    //如果我们后来要用,我们就用一个变量来存起来
    System.out.println("传入的IOC容器:"+applicationContext);
    this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String name) {
    System.out.println("当前bean的名字:"+name);
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
    String resolveStringValue = resolver.resolveStringValue("你好${os.name} 我是#{20*18}");
    System.out.println("解析的字符串"+resolveStringValue);
    }
    }

自动装配- @Profile 环境搭建

==当在不同环境可以选择链接不同的数据库:测试环境,开发环境,生产环境==

  • @Profile注解源码

    1
    2
    3
    4
    5
    6
    7
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(ProfileCondition.class)
    public @interface Profile {
    String[] value();
    }
  • 引入数据源:druid、mysql驱动
  • properties配置文件

    1
    2
    3
    4
    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/store?serverTimezone=UTC
    jdbc.username=root
    jdbc.password=root
  • 这个时候,我们就可以这样来进行配置:配置类中加上@PropertySource(“classpath:配置文件路径”):

    • 三种获取配置文件中的值:

      • 直接通过属性上面加上@Value(“${db.user}”)

      • 在参数上面使用@Value(“${db.password}”)

        public DataSource dataSourceTest(@Value(“${db.password}”) String pwd)

      • 实现EmbeddedValueResolverAware接口,在setEmbeddedValueResolver(StringValueResolver resolver)方法里面进行获取:里面使用 resolver.resolveStringValue(“${db.driverClass}”)方法解析,返回赋值给成员属性private StringValueResolver resolver;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      /**
      * Profile:
      * Spring为我们提供的可以根据当前的环境,动态的激活和切换一系列组件的功能;
      * 开发环境,测试环境,生产环境
      * 我们以切换数据源为例:
      * 数据源:开发环境中(用的是A数据库)、测试环境(用的是B数据库)、而生产环境(用的又是C数据库)
      */
      @PropertySource(value = {"classpath:jdbc.properties"})//加载配置类
      @Configuration
      @ComponentScan({"com.hngy.value.service" , "com.hngy.value.controller" , "com.hngy.value.dao"})
      public class MainConfig implements EmbeddedValueResolverAware {

      @Value("${jdbc.username}")
      private String username;

      private String driverClass;

      @Bean("testDataSource")
      public DruidDataSource dataSourceTest(@Value("${jdbc.password}") String password) {
      DruidDataSource dataSource = new DruidDataSource();
      dataSource.setUsername(username);//在属性上使用@Value注解赋值
      dataSource.setPassword(password);//在参数上使用@Value注解赋值
      dataSource.setUrl("jdbc:mysql://localhost:3306/store?serverTimezone=UTC");//直接传值
      dataSource.setDriverClassName(driverClass);//使用实现EmbeddedValueResolverAware接口来获取值
      return dataSource;
      }

      @Bean("devDataSource")
      public DruidDataSource dataSourceDev(@Value("${jdbc.password}") String password) {
      DruidDataSource dataSource = new DruidDataSource();
      dataSource.setUsername(username);//在属性上使用@Value注解赋值
      dataSource.setPassword(password);//在参数上使用@Value注解赋值
      dataSource.setUrl("jdbc:mysql://localhost:3306/store?serverTimezone=UTC");//直接传值
      dataSource.setDriverClassName(driverClass);//使用实现EmbeddedValueResolverAware接口来获取值
      return dataSource;
      }

      @Bean("prodDataSource")
      public DruidDataSource dataSourceProd(@Value("${jdbc.password}") String password) {
      DruidDataSource dataSource = new DruidDataSource();
      dataSource.setUsername(username);//在属性上使用@Value注解赋值
      dataSource.setPassword(password);//在参数上使用@Value注解赋值
      dataSource.setUrl("jdbc:mysql://localhost:3306/store?serverTimezone=UTC");//直接传值
      dataSource.setDriverClassName(driverClass);//使用实现EmbeddedValueResolverAware接口来获取值
      return dataSource;
      }

      @Override
      public void setEmbeddedValueResolver(StringValueResolver resolver) {
      driverClass = resolver.resolveStringValue("${jdbc.driver}");
      }
      }

自动装配- @Profile 根据环境注册bean

@Profile:

Spring为我们提供的可以根据当前的环境,动态的激活和切换一系列组件的功能;开发环境,测试环境,生产环境

@Profile: 指定组件在哪一个环境的情况下才能被注册到容器中,不指定任何环境都能被注册这个组件

  1. 加了环境标识的bean,只有这个环境被激活的时候才能注册到容器中,默认是default环境,如果指定了default,那么这个bean默认会被注册到容器中
  2. @Profile 写在配置类上,只有是指定的环境,整个配置类里面的所有配置才能开始生效
  3. 没有标注环境标识的bean,在任何环境都是加载的
  • 用 @Profile 注解只激活哪一个数据源:

    1
    2
    3
    4
    5
    6
    7
    8
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Conditional(ProfileCondition.class)
    public @interface Profile {
    String[] value();

    }
  • 给数据源加上标识:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    @PropertySource(value = {"classpath:jdbc.properties"})//加载配置类
    @Configuration
    @ComponentScan({"com.hngy.value.service" , "com.hngy.value.controller" , "com.hngy.value.dao"})
    public class MainConfig implements EmbeddedValueResolverAware {

    @Value("${jdbc.username}")
    private String username;

    private String driverClass;

    @Profile("test")
    @Bean("testDataSource")
    public DruidDataSource dataSourceTest(@Value("${jdbc.password}") String password) {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUsername(username);//在属性上使用@Value注解赋值
    dataSource.setPassword(password);//在参数上使用@Value注解赋值
    dataSource.setUrl("jdbc:mysql://localhost:3306/store?serverTimezone=UTC");//直接传值
    dataSource.setDriverClassName(driverClass);//使用实现EmbeddedValueResolverAware接口来获取值
    return dataSource;
    }

    @Profile("dev")
    @Bean("devDataSource")
    public DruidDataSource dataSourceDev(@Value("${jdbc.password}") String password) {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUsername(username);//在属性上使用@Value注解赋值
    dataSource.setPassword(password);//在参数上使用@Value注解赋值
    dataSource.setUrl("jdbc:mysql://localhost:3306/store?serverTimezone=UTC");//直接传值
    dataSource.setDriverClassName(driverClass);//使用实现EmbeddedValueResolverAware接口来获取值
    return dataSource;
    }

    @Profile("prod")
    @Bean("prodDataSource")
    public DruidDataSource dataSourceProd(@Value("${jdbc.password}") String password) {
    DruidDataSource dataSource = new DruidDataSource();
    dataSource.setUsername(username);//在属性上使用@Value注解赋值
    dataSource.setPassword(password);//在参数上使用@Value注解赋值
    dataSource.setUrl("jdbc:mysql://localhost:3306/store?serverTimezone=UTC");//直接传值
    dataSource.setDriverClassName(driverClass);//使用实现EmbeddedValueResolverAware接口来获取值
    return dataSource;
    }

    @Override
    public void setEmbeddedValueResolver(StringValueResolver resolver) {
    driverClass = resolver.resolveStringValue("${jdbc.driver}");
    }
    }
  • 测试:

    • 使用命令行动态参数的方式:在虚拟机参数的位置加载-Dspring.profile.active=test

      设置环境.webp

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      //1.使用命令行动态参数的方式
      @Test
      public void test01() {
      ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
      String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
      Stream.of(beanNamesForType).forEach(System.out::println);
      /*
      运行结果:
      testDataSource
      */
      }
- **切换到开发的环境:**

    ![image-开发环境](https://www.naste.top:9000/webp/%E5%BC%80%E5%8F%91%E7%8E%AF%E5%A2%83.webp)





- **利用代码的方式来实现激活某种环境,这个时候,我们在创建IOC容器的时候,必须要用无参构造器:**

    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test01() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//1)使用无参构造器来创建applicationContext对象
//2)设置需要激活的环境
applicationContext.getEnvironment().setActiveProfiles("dev");
//3)加载主配置类
applicationContext.register(MainConfigOfProfile.class);
//4)启动刷新容器
applicationContext.refresh();

String[] beanNamesForType = applicationContext.getBeanNamesForType(DataSource.class);
Stream.of(beanNamesForType).forEach(System.out::println);
}
  • @Profile 写在配置类上,只有是指定的环境,整个配置类里面的所有配置才能开始生效:

    1
    2
    3
    4
    5
    @Profile("test") // 将该注解写在配置类上,需要将满足该环境,该配置类下的所
    @PropertySource(value = {"classpath:jdbc.properties"})//加载配置类
    @Configuration
    @ComponentScan({"com.hngy.value.service" , "com.hngy.value.controller" , "com.hngy.value.dao"})
    public class MainConfig implements EmbeddedValueResolverAware { ... }

IOC-小结

容器:

  • AnnotationConfigApplicationContext:

    • 配置类
  • 包扫描

  • 组件添加:

    • @ComponentScan
    • @Bean

      • 指定初始化销毁

      • 初始化其他方式

        • (1)InitializingBean(初始化设置值之后)
        • (2)InitializingBean(初始化设置值之后)
        • (3)JSR250:@PostConstruct、@PreDestroy
  • @Configuration
  • @Component
  • @Service
  • @Controller

    • @Repository
    • @Conditional
    • @Primary
    • @Lazy
    • @Scope
    • @Import
    • ImportSelector
    • 工厂模式:FactoryBean:&beanName获取Factory本身
  • 组件赋值

    • @Value
    • @Autowired
      • (1)@Qualifier
      • (2)@Resources(JSR250)
      • (3)@Inject(JSR330,需要导入javax.inject)
    • @PropertySource @PropertySources @Profile
      • (1)Environment
      • (2)-Dspring.profiles.active=test
  • 组件注入
    • 方法参数
    • 构造器注入
    • xxxAware
    • ApplicationContextAware
      ApplicationContextAwareProcessor

AOP

主要把握三步:

  1. 将业务逻辑组件和切面类都加入到IOC容器中,并且告诉Spring哪一个是切面类(@Aspect)
  2. 在切面类上的每一个通知方法标注通知注解:告诉Spring何时何地运行(写好切入点表达式)
  3. 开启基于注解的AOP模式:@EnableAspectJAutoProxy

AOP-AOP功能测试

  • 导入jar包

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.4</version>
    </dependency>
  • 写一个目标类,被代理的类

    1
    2
    3
    4
    5
    6
    7
    public class MathCalculator {
    //除法
    public int div(int i, int j) {
    System.out.println("MathCalculator...div...");
    return i / j;
    }
    }
  • 配置一个切面类:该切面类是无参数的切面类,下面还有一个可以传入参数的切面类,具体使用看下面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    //告诉Spring当前类是一个切面类
    @Aspect
    public class LogAspects {

    //抽取公共的切入点表达式
    //本类引用:pointCut()
    //其他的切面类要引用
    @Pointcut("execution(public int com.ldc.aop.MathCalculator.*(..))")
    public void pointCut() {

    }

    //@Before在目标方法之前切入:切入点表达式(指定在哪个方法切入)
    @Before("pointCut()")
    public void logStart() {
    System.out.println("除法运行...参数列表是:{}");
    }

    @After("pointCut()")
    public void logEnd() {
    System.out.println("除法结束...");
    }

    @AfterReturning("pointCut()")
    public void logReturn() {
    System.out.println("除法正常返回...运行结果为:{}");
    }
    @AfterThrowing("pointCut()")
    public void logException() {
    System.out.println("除法异常...异常信息为:{}");
    }
    }
  • 再来写一个配置类 ,把上面这两个类加入到IOC容器中,并且通过==@EnableAspectJAutoProxy注解==开启基于注解的aop模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    /**
    * AOP:【动态代理】
    * 能在程序运行期间动态的将某段代码片段切入到指定的方法指定位置进行运行的编程方式;
    *
    * 1、导入aop模块,Spring AOP:(spring-aspects)
    * 2、定义一个业务逻辑类(MathCalculator),在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、包括方法出现异常等等)
    * 3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里了然后执行
    * 切面类里面的方法就是通知方法:
    * (1)前置通知(@Before):logStart,在目标方法(div)运行之前运行
    * (2)后置通知(@After):logEnd,在目标方法(div)运行之前运行
    * (3)返回通知(@AfterReturning):logReturn,在目标方法(div)执行返回(无论是正常返回还是异常返回)之后运行
    * (4)异常通知(@AfterThrowing):logException,在目标方法(div)出现异常之后运行
    * (5)环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())
    * 4、给切面类的目标方法标注何时何地运行(通知注解)
    * 5、将切面类和业务逻辑类(目标方法所在的类)都加入到容器中;
    * 6、必须要告诉Spring哪一个类是切面类(只要给切面类上加上一个注解:@Aspect
    * 【7】、给配置类中加入@EnableAspectJAutoProxy:开启基于注解的aop模式
    */
    @EnableAspectJAutoProxy//配置注解AOP模式
    @Configuration
    public class MainConfigOfAOP {

    //将业务逻辑类加入到容器中
    @Bean
    public MathCalculator mathCalculator() {
    return new MathCalculator();
    }

    //将切面类加入到容器中
    @Bean
    public LogAspects logAspects() {
    return new LogAspects();
    }
    }
  • 可以在切面里面的通知方法里面获取方法名、参数、返回值以及异常等等:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    //告诉Spring当前类是一个切面类
    @Aspect
    public class LogAspects {

    //抽取公共的切入点表达式
    //本类引用:pointCut()
    //其他的切面类要引用
    @Pointcut("execution(public int com.ldc.aop.MathCalculator.*(..))")
    public void pointCut() {

    }

    //@Before在目标方法之前切入:切入点表达式(指定在哪个方法切入)
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
    System.out.println(joinPoint.getSignature().getName()+"除法运行...参数列表是:{"+ Arrays.asList(joinPoint.getArgs()) +"}");
    }

    @After("pointCut()")
    public void logEnd(JoinPoint joinPoint) {
    System.out.println(joinPoint.getSignature().getName()+"除法结束...");
    }

    @AfterReturning(value = "pointCut()",returning = "result")
    public void logReturn(Object result) {
    System.out.println("除法正常返回...运行结果为:{"+result+"}");
    }

    //JoinPoint这个参数一定要出现在参数列表的第一位
    @AfterThrowing(value = "pointCut()",throwing = "exception")
    public void logException(JoinPoint joinPoint,Exception exception) {
    System.out.println(joinPoint.getSignature().getName()+"除法异常...异常信息为:{}");
    }
    }

    ==注意:如果要写JoinPoint这个参数,那么这个参数一定要写在参数列表的第一位;==

声明式事务

声明式事务-环境搭建

  • 导入相关依赖:数据源、数据库驱动、Spring-jdbc模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.4</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.4</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.4</version>
    </dependency>
  • 配置数据源、JdbcTemplate(Spring提供简化数据库操作的工具)操作数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    @PropertySource({"classpath:dbconfig.properties"})
    @Configuration
    public class TxConfig {

    //数据源
    @Bean
    public DataSource dataSource() throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser("root");
    dataSource.setPassword("root");
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
    return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
    //Spring会的@Configuration类会做特殊的处理:给容器中添加组件,多次调用都是从容器中找组件
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
    return jdbcTemplate;
    }

    }
  • 如上配置类还只是可以操作数据库,如需要开启事务,将@Transactional注解标注在类或方法上还是不行的,还需要使用,@EnableTransactionManagement 开启基于注解的事务管理功能;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    /**
    声明式事务:

    环境搭建:
    1.导入相关依赖:数据源、数据库驱动、Spring-jdbc模块
    2.配置数据源、JdbcTemplate(Spring提供简化数据库操作的工具)操作数据
    3.给方法上标注@Transactional注解表示当前的方法是一个事务方法;
    4.@EnableTransactionManagement 开启基于注解的事务管理功能;
    5.配置事务管理器来控制事务(必须要有这一步)
    */
    @ComponentScan({"com.ldc.tx"})
    @PropertySource({"classpath:dbconfig.properties"})
    @Configuration
    @EnableTransactionManagement
    public class TxConfig {

    //数据源
    @Bean
    public DataSource dataSource() throws PropertyVetoException {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser("root");
    dataSource.setPassword("12358");
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
    return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
    //Spring会的@Configuration类会做特殊的处理:给容器中添加组件,多次调用都是从容器中找组件
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
    return jdbcTemplate;
    }

    //注册事务管理器在容器中
    @Bean
    public PlatformTransactionManager transactionManager() throws PropertyVetoException {
    return new DataSourceTransactionManager(dataSource());
    }

    }