Spring注解驱动原理详解

 

学习本篇需要对Spring有一定的基础。扩展原理的部分,这是一定要了解的,这会为我们以后的学习和进步带来很大的收益。

以帮助我们更好的阅读Spring的源码。在Spring Boot中注解也随处可见,这也为我们学习Spring Boot、Cloud打下基础。

注解(组件添加)作用
@Configuration 告诉Spring这是一个配置类
@ComponentScan  
@Bean  
@Component  
@Service  
@Controller  
@Repository  
@Conditional(重要)  
@Primary  
@Lazy  
@Scope  
@Import(重要)  

 

注解(组件赋值)作用
@Value  
@Qualifier  
@Autowired  
@Resources (JSR250)  
@Inject (JSR330,需要导入javax.inject )  
@PropertySource  
@Profile Environment/-Dspring.profiles.active=test

 

1、Bean的注册

Spring 容器可以管理 singleton 作用域 Bean 的生命周期,在此作用域下,Spring 能够精确地知道该 Bean 何时被创建,何时初始化完成,以及何时被销毁。 而对于 prototype 作用域的 Bean,Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。

1.1、@Bean(注册bean)

使用xml在容器中注册bean,写一个实体类→在配置文件中注册,可以设置属性 id等

 

使用注解注册bean

使用一个配置类代替配置文件,配置类只用写一次即可,配置文件在生成bean的时候需要改动。

配置类class==配置文件xml

@Configuration,告诉Spring这是一个配置类

@Bean //给容器注册一个Bean 类型:返回值的类型; id:方法名

@Configuration  //告诉Spring这是一个配置类
public class MainConfig {


   @Bean   //给容器注册一个Bean 类型:返回会值的类型; id:方法名
   public Person person() {
       return new Person("宝宝", 12);
  }
}

使用方法:new AnnotationConfigApplicationContext

获取Bean的一些信息,这里获取类型为Person的Bean的id,返回值是一个数组,表示有多少bean的类型是Person

String[] namesForType = context.getBeanNamesForType(Person.class);
for (String name : namesForType) {
   System.out.println(name);
}
//这里返回的是person,id为方法名 :person

将config中的方法名改成person01,这里的值也对应改变。

也可以使用@Bean指定id,用法:@Bean(name = "person"), 此时id与方法名无关了,这个优先级高。

1.2、@ComponentScan(包扫描)

注解@ComponentScan(value = "com.yong.config")等于xml中的以下这段

<context:component-scan base-package="com.yong"/>

包扫描,只要标注了@Controller、@Service、@Repository、@Component,就会被扫描。

 

测试

添加三层架构和类并加上注解

image-20211206164505535

@Repository
public class BookDao {

}

@Controller
public class BookController {
}

@Service
public class BookService {
}

输出Spring容器中注册了那些组件

@Test
public void annotation1() {
   ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
   String[] names = context.getBeanDefinitionNames();
   for (String name : names) {
       System.out.println(name);
  }
}
/**输出:
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory
mainConfig
bookController
bookDao
bookService
person
*/

发现除了一些Spring自己加载的组件还有我们添加注解的类也被加载了。

@Configuration中使用@Component注解,所以mainConfig也被加载

 

在@ComponentScan有过滤器,参数为数组:

  • includeFilters:只扫描的时候只要哪些(只包含)

  • excludeFilters:——排除哪些(不需要)

image-20211206165634510

@ComponentScan(value = "com.yong",excludeFilters = {
       @ComponentScan.Filter(
               type = FilterType.ANNOTATION,   //按照注解类型来排除
               classes = {Controller.class, Service.class}   //这两个不扫描,参数:数组
              )
})

image-20211206165938222

 

但是,直接使用includeFilters是不生效的

image-20211206170225303

 

原因是:Spring有默认的 过滤规则,也就是扫描所有。使用includeFilters的时候需要先将默认的过滤规则禁用,这样includeFilters才能生效

禁用方法:

1.原来在xml中的禁用是:

<context:component-scan base-package="com.yong" use-default-filters="false"/>

2.注解中,点进@ComponentScan源码,发现也有这个属性,将其设置为false即可

useDefaultFilters = false

image-20211206170724142

可以在统一代码段中设置多个过滤规则,添加多个 @ComponentScan,可以使用@ComponentScans

 

type = FilterType.ASSIGNABLE_TYPE,   //按照注解的类型来排除
classes = {BookService.class}

 

自定义过滤规则

type = FilterType.CUSTOM,
classes = {自定义的过滤规则:MyTypeFilter.class}

 

定义一个MyTypeFilter类实现TypeFilter接口,重写match方法,编写过滤规则,返回false是匹配失败

public class MyTypeFilter implements TypeFilter {
   /**
    *
    * @param metadataReader:读取到的当前正在扫描的类的信息
    * @param metadataReaderFactory:是一个工厂,探索其他类,获取其他类的信息
    */
   public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
       //获取当前类注解信息
       AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
       //获取当前正在扫描类的类信息
       ClassMetadata classMetadata = metadataReader.getClassMetadata();
       //获取当前类的资源信息
       Resource resource = metadataReader.getResource();
       /*
       可以根据上面获取的属性来设置过滤规则,
       比如利用类信息来过滤
        */
       String className = classMetadata.getClassName();
       System.out.println("---->"+className);
       //如果类名中含有er,则不过滤
       if (className.contains("er")) {
           return true;
      }
       return false;
  }
}

Myconfig

@ComponentScans(value = {
      @ComponentScan(value = "com.yong",includeFilters = {
              @ComponentScan.Filter(type = FilterType.CUSTOM,classes = {MyTypeFilter.class})
      },useDefaultFilters = false)
})

image-20211206190556977

可以发现我们并没有在MyTypeFilter上面标注解,MyTypeFilter也被注入IOC容器中了,这是因为这个自定义过滤规则是扫描该路径下所有的类

 

1.3、@Scope(控制作用域)

Spring容器创建对象规则默认是单例,如果想要改变它的单例模式,可以使用@Scope注解

public class MainConfig2 {
   /**
    * prototype:多例
    * singleton:单例
    */
   @Scope("prototype")
   @Bean(name = "person")
   public Person test01(){
       return new Person("老六", 95);
  }
}

测试:

@Test
public void annotation1() {
   ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig2.class);
   
   Person person = context.getBean("person", Person.class);
   Person person2 = context.getBean("person", Person.class);
   System.out.println(person==person2);//输出false
}

相当于xml文件中的

image-20211206205711713

@Scope 使用默认模式,也就是单例模式的情况,bean会在容器创建的时候就创建,使用的时候直接从容其中拿出来。

使用prototype模式则会在调用的时候才创建bean。调用一次创建一次,所以输出false。

在单例模式下,如果有大量的bean,那么服务启动的速度必定会慢,如果并不需要在服务启动时加载全部的bean,那么可以使用懒加载。

用法:加上@Lazy注解即可

@Lazy
@Bean(name = "person")
public Person test01(){
  return new Person("老六", 95);
}

这样,只有当第一次获取它的时候才会被创建,并且只会创建一次——延迟加载(懒加载)

 

1.4、@Conditional(按条件注册Bean)

该注是时Spring提供的一个强大的注解,在Spring底层也大量使用了该注解

作用:按照条件注册Bean

点进condition注解的源码

public @interface Conditional {

  /**
   * All {@link Condition} classes that must {@linkplain Condition#matches match}
   * in order for the component to be registered.
   */
  Class<? extends Condition>[] value();
}

public interface Condition {

/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked
* @return {@code true} if the condition matches and the component can be registered,
* or {@code false} to veto the annotated component's registration
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

可以看到它的 参数是Condition的数组,这个就是条件,我们只需要写一个类实现Condition接口,然后重写matches方法,也就是条件,传进来即可。

测试:

这里写两个Condition条件类

WindowsConditionLinuxCondition,如果系统是windows,给容器注册麻花腾,否则注册刘强东

public class WindowsCondition implements Condition {

   public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
       //以下这些都可以用来获取判断条件的内容
       //1 获取当前环境信息
       Environment environment = context.getEnvironment();
       //2 获取IOC使用的beanFactory
       ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
       //3 获取类加载器
       ClassLoader classLoader = context.getClassLoader();
       //4 获取Bean定义的注册类,可以用这个来操作bean的增删查改
       BeanDefinitionRegistry registry = context.getRegistry();

       //如果系统是windows,给容器注册麻花腾,否则注册刘强东,使用environment
       String property = environment.getProperty("os.name");//获取操作系统名字,我的为Windows10
       if (property.contains("Windows")) {
           return true;
      }
       return false;
  }
}


public class LinuxCondition implements Condition {

   public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
       //1 获取当前环境信息
       Environment environment = context.getEnvironment();
       //如果系统是windows,给容器注册麻花腾,否则注册刘强东
       String property = environment.getProperty("os.name");//获取操作系统名字,我的为Windows10
       if (property.contains("Linux")) {
           return true;
      }
       return false;
  }
}

给config加上Condition注解

@Conditional({WindowsCondition.class})
@Bean("麻花腾")
public Person person01() {
   return new Person("麻花腾", 55);
}

@Conditional({LinuxCondition.class})
@Bean("刘强东")
public Person person02() {
   return new Person("刘强东", 75);
}

测试

@Test
public void test01() {
   String[] names = context.getBeanNamesForType(Person.class);
   Environment environment = context.getEnvironment();
   String property = environment.getProperty("os.name");
   System.out.println("当前操作系统:"+property);
   System.out.println("容器中的Bean有:");
   for (String name : names) {
       System.out.println(name);
  }
}
/**输出
当前操作系统:Windows 10
容器中的Bean有:
person
麻花腾
*/

配置虚拟机参数,将操作系统名字设置为Linux

image-20211206222714583

再次测试,发现输出变成了刘强东

也可以使用其他的来做判条件,比如registry,在LinuxCondition中加上

/**
* 如果容器中有person这个bean,则返回true
*/
BeanDefinitionRegistry registry = context.getRegistry();
boolean person = registry.containsBeanDefinition("person");
if (person) {
  return true;
}
return false;

image-20211206223401967

@Condition可以标在方法上也可以标注在类上

放在类上代表:满足条件,这个类上的所有Bean才被注册。


1.5、@Import(快速导入注册)

快速的导入组件

比如导入第三方包的时候,可以使用。

  1. 如现在有一个Son类,想导入这个Bean,可以直接将这个类放在数组中

@Configuration
@Import({Son.class})
public class MainConfig2 {
……
}

导入到容器,容器会自动注册这组件,id默认是组件的全类名

 

 

  1. 也可以使用ImportSelector选择器实现多个组件导入(这在Spring Boot的源码中使用比较多)

方法:新建MyImportSelector实现这个ImportSelector类,重写其中的方法,返回值即为需要导入到容器的组件。在@Import中加入这个类

public class MyImportSelector implements ImportSelector {
   /**
    *
    * @param importingClassMetadata
    * @return 就是导入到容器的组件的全类名,不能是 null,也必须存在
    ,新建两个类DearSon,HoneySon
    */
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
       return new String[]{"com.yong.bean.DearSon","com.yong.bean.HoneySon"};
  }
}



@Import({Son.class, MyImportSelector.class})
public class MainConfig2 {
   ……
}

测试

@Test
public void IOCTest(){
   printBeans(context);
   DearSon bean = context.getBean(DearSon.class);
   System.out.println(bean);
}

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

发现两个类已经被注册到Spring容器中

image-20211207164454718

ImportSelector:不是将实现这个类的类导入到IOC容器中,而是根据这个类返回的组件的全类名导入。

 

 

  1. ImportBeanDefinitionRegistrar:手动注册

    ……

  2. FactoryBean(工厂注册Bean)

    • 这个是Spring提供的,不是@Import注解的

    • 在Spring与其他框架整合的时候FactoryBean使用得特别多,这是比较重点的一个注解

    使用方法:

    定义一个类工厂,实现FactoryBean<T>,泛型指定创建什么类型的对象,重写方法,这里使用Person类举例。

     

    public class PersonFactoryBean implements FactoryBean<Person> {

       //返回一个Person,并且会注册到容器中
       public Person getObject() throws Exception {
           System.out.println("PersonFactoryBean..getObject");
           return new Person();
      }

       //返回类型
       public Class<?> getObjectType() {
           return Person.class;
      }

       //控制是否设置单例
       public boolean isSingleton() {
           return true;
      }
    }

然后将PersonFactoryBean加入到 容器中

//在MainConfig2中增加
@Bean
public PersonFactoryBean personFactoryBean() {
   return new PersonFactoryBean();
}

注意这里我们加入的是PersonFactoryBean工厂Bean,我们来看看IOC里面有什么Bean

image-20211207171747180

确实这个Bean已经被我们加入了,来看看它是什么类型

@Test
public void IOCTest(){
   printBeans(context);
   Object personFactoryBean = context.getBean("personFactoryBean");
   System.out.println("bean类型:"+personFactoryBean.getClass());
}

image-20211207171943155

发现虽然看着它是一个工厂类型,但其实它是一个Person。那我们应该如何拿到这个工厂Bean呢? 只需要在getBean的参数加上一个前缀&

@Test
public void IOCTest(){
   printBeans(context);
   //创建了两次,因为是单例,所以getObject只执行了一次。
   Object personFactoryBean = context.getBean("personFactoryBean");
   Object personFactoryBean2 = context.getBean("personFactoryBean");
   Object personFactoryBean3 = context.getBean("&personFactoryBean");
   System.out.println("bean类型:"+personFactoryBean.getClass());
   System.out.println("bean类型:"+personFactoryBean2.getClass());
}

image-20211207172318735

加前缀是Spring设置的,如果我们加上前缀,Spring就知道我们需要获取的是工厂本身

image-20211207172736954

2、Bean的生命周期

Bean的生命周期:创建——初始化——销毁的过程

2.1、@Bean(指定方法)

创建一个类和一些方法

public class Car {
  public Car() {
      System.out.println("Car---->Create");
  }
  public void init() {
      System.out.println("Car---->init");
  }
  public void destroy() {
      System.out.println("Car---->destroy");
  }
}

@Bean指定创建和销毁的方法

@Configuration
@ComponentScan("com.yong.bean")
public class MainConfigOfLifeCycle {

   @Bean(initMethod = "init",destroyMethod = "destroy")
   public Car car(){
       return new Car();
  }
}


public class TestCar {
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class);
   @Test
   public void test(){
       System.out.println("容器创建完成");
       context.close();
  }
}

/*输出
Car---->Create
Car---->init
容器创建完成
Car---->destroy
*/

相当于xml配置中的

<bean id="car" class="com.yong.bean.Car" init-method="init" destroy-method="destroy">
</bean>
  • 初始化:对象创建完成并赋值好的时候被调用

  • 销毁:容器关闭的时候销毁

特殊,如果使用@Scope设置Bean为多实例,和之前一样,初始化会在完成被创建的时候也就是getBean之后才会初始化。而且不会调用销毁方法,可以手动销毁。

 

2.2、通过实现接口

让Bean实现InitializingBeanDisposableBean接口,重写他们的方法

  • InitializingBean:定义初始化逻辑

  • DisposableBean:定义销毁逻辑

@Component  //定义为组件
public class Dog implements InitializingBean, DisposableBean {

   public void destroy() throws Exception {//销毁
       System.out.println("Dog---->destroy");
  }

   public void afterPropertiesSet() throws Exception {//初始化
       System.out.println("Dog---->init");
  }
}

 

2.3、使用JSR250规范的注解

  • @PostConstruct:在Bean创建完成,并且属性赋值完成后,执行初始化方法

  • @PreDestroy:在Bean销毁之前通知我们进行清理工作

这两个注解的作用域是在方法上

@Component
public class Bird {
   
   public Bird() {
       System.out.println("Bird---->Create");
  }
   
   @PostConstruct
   public void init() {
       System.out.println("Bird---->init");
  }
   
   @PreDestroy
   public void destroy() {
       System.out.println("Bird---->destroy");
  }
}

 

2.4、通过后置处理器

后置处理器BeanPostProcessor,其中有两个方法

  • postProcessBeforeInitialization:在bean初始化之前执行

  • postProcessAfterInitialization:在bean初始化之后执行

在bean初始化前后进行工作

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
   /**
    *
    * @param bean : bean的实例
    * @param beanName :bean的名字
    * @return :可以对bean加工后返回,也可直接返回
    */
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
       System.out.println("postProcessAfterInitialization:"+beanName+"-->"+bean);
       return bean;
  }

   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
       System.out.println("postProcessBeforeInitialization:"+beanName+"-->"+bean);
       return bean;
  }
}

可以看出无论是使用上面的哪一种初始化方法,在初始化前后时处理器都生效,

image-20211207220802823

它的工作时机时bean初始化的前后。

BeanPostProcessor原理在Spring的底层也被大量地使用,几十个实现类

image-20211207221249480

包括上面的第三点,也是依靠这个实现的。

去了解:BeanPostProcessor底层的使用

3、Bean的属性赋值

3.1、@Value

@Value:添加在属性上

  • 基本数值

  • SpEL(表达式):#{24-2}

  • ${}:可以取出配置文件中的值,(在运行环境中的)

public class Person {
  @Value("凤飞飞")
  private String name;
  @Value("#{24-2}")
  private Integer age;
  @Value("${person.address}")  
  private String address;
  ……
}

创建一个person.properties资源文件,添加person.address属性

person.address=北京

@PropertySource:用于指定资源文件读取的位置,它不仅能读取properties文件,也能读取xml文件。和xml配置文件中导入资源文件等价

@PropertySource(value = {"classpath:person.properties"},encoding="gbk") //注意乱码

使用该注解后,资源文件中的值被加载到了环境中,也可以通过容器获得

ConfigurableEnvironment environment = context.getEnvironment();
String property = environment.getProperty("person.address");
System.out.println(property);//北京

4、自动装配

4.1、@Autowired

利用Spring的依赖注入(DI),完成对IOC容器中各个组件的依赖关系赋值

@Autowire:自动注入,默认先按照类型去容器中找对应的组件。如果找到了多个,那么根据属性的名字作为id去匹配

@Autowired
private BookDao bookDao2; //如果有多个类型为BookDao的bean,那么会去找id为bookDao2的bean

所以名字尽量写规范。

那如果写了bookDao2,就是想让他去装配bookDao1行不行呢?

可以的,使用@Qualifier("bookDao1")来指定。如果不存在该Bean,会报错,所以一定要将属性赋值好

 

那是否可以设置让它找到就装配,找不到就算了,而不报错呢?

可以!在@Autowire里设置required属性即可

@Autowired(required = false)

 

使用@Primary可以指定某个bean为首选的。加在Bean上。

4.2、@Resource、@Inject

@Resource

  • 是按照名称去容器中寻找,可以设置name属性

  • 不能搭配Spring的@Qualifier和@Primary使用

@Inject:

  • 需要导入依赖

  • 能结合@Qualifier使用,和@Autowired功能一样,不过没有required属性

 

这两个注解是java的规范,都能实现装配。

 

这三个注解,总的来说@Autowired的功能最多,但是它不能脱离SPring框架。其他2个为java规范,可以在其他框架使用,但功能较少。看情景决定需要使用哪一个注解

 

补充:

@Autowired:

  • 还可以使用在构造器,参数,属性,方法上

标注在方法上,表示方法的参数会从IOC容器中获取,参数的规范要满足@Autowired的限制,即上面的内容,否则报错

@Component
public class Driver {
   private Car car;

   public Driver(Car car) {
       this.car = car;
  }
   /**
    * 标注在方法上(set),Spring容器就会调用当前方法,完成赋值
    * @param car :参数会从IOC容器中获取
    */
   @Autowired
   public void setCar(Car car) {
       this.car = car;
  }

   @Override
   public String toString() {
       return "Driver{" +
               "car=" + car +
               '}';
  }

   public Car getCar() {
       return car;
  }
}

@Test
   public void print() {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAutowire.class);
       Driver driver = context.getBean(Driver.class);//获取driver
       System.out.println(driver);
       Car car = context.getBean(Car.class);  //从Spring容器中拿出car
       System.out.println(driver.getCar()==car);  //与Driver中的对比,结果是true
  }

这样也是可以的,加不加@Autowired效果都一样,都能自动装配

@Bean
public Driver driver(Car car) {
   return new Driver(car);
}

标在有参构造器上,和标在方法上是同样的效果。

@Autowired
public Driver(Car car) {
   this.car = car;
}

Spring中如果无标注,默认是使用无参构造器,然后在进行赋值操作,构造器中使用的组件,也都是从IOC容器中获取

如果类中只有一个有参构造器,那么@Autowired是可以省略的

标注在参数上,效果也是一样的

public void setCar(@Autowired Car car) {
   this.car = car;
}

 

4.3、注入Spring底层组件

在我们自定义的组件中如果想使用Spring的组件(ApplicationContext、BeanFactroy、……),比如Son类需要引入一个ApplicationContext

那么Son可以实现ApplicationContext接口,利用其的回调函数注入一个ApplicationContext。

//注入了一个Spring的底层组件applicationContext,和一个BeanName
@Component
public class Son implements ApplicationContextAware, BeanNameAware {
   private ApplicationContext applicationContext;

   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       this.applicationContext = applicationContext;
       System.out.println("传入的IOC容器为:" + applicationContext);
  }

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

在测试类中获取contexte,查看是否与上面注入的相同

Son son = context.getBean(Son.class);
System.out.println(context==son.getApplicationContext());//true

实现xxxxAware可以在自定义Bean中注入Spring的底层组件

 

原理

在调用Aware方法的时候,会触发有关的后置处理器AwareProcessor,在初始化的时候判断Bean的Aware接口,然后调用对应set方法传入Spring的组件。

ApplicationContextAware为例

在上面Son的代码中实现了ApplicationContextAware接口,执行的时候触发ApplicationContextAwareProcessor,在后置处理器源码中可以看到,会进行一个判断,如果是使用了Aware,然后执行invokeAwareInterfaces,调用对应的set方法,将组件注入

class ApplicationContextAwareProcessor implements BeanPostProcessor {

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    //判断是否调用的aware接口
if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware ||
bean instanceof ApplicationStartupAware)) {
return bean;
}

AccessControlContext acc = null;

if (System.getSecurityManager() != null) {
acc = this.applicationContext.getBeanFactory().getAccessControlContext();
}

if (acc != null) {   //权限检查
AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
invokeAwareInterfaces(bean);
return null;
}, acc);
}
else {
invokeAwareInterfaces(bean);  //执行该方法
}

return bean;
}

   //z
  private void invokeAwareInterfaces(Object bean) {
     if (bean instanceof EnvironmentAware) {
        ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
    }
     if (bean instanceof EmbeddedValueResolverAware) {
        ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
    }
     if (bean instanceof ResourceLoaderAware) {
        ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
    }
     if (bean instanceof ApplicationEventPublisherAware) {
        ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
    }
     if (bean instanceof MessageSourceAware) {
        ((MessageSourceAware) bean).setMessageSource(this.applicationContext);
    }
     if (bean instanceof ApplicationStartupAware) {
        ((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
    }
     if (bean instanceof ApplicationContextAware) {
        ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
    }
  }

}

 

4.4、@Profile

Profile:Spring为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能

比如说生产环境的切换,对数据源的切换

搭建测试环境:建立三个数据源,生产、测试、开发,和Config类、测试类

@Configuration
@PropertySource("classpath:/db.properties")
public class MainConfigOfProfile {

   @Value("db.user")
   private String user;

   @Profile("dev")
   @Bean("devDataSource")
   public DataSource dataSourceDev() throws Exception {
       ComboPooledDataSource dataSource = new ComboPooledDataSource();
       dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/javaweb?");
       dataSource.setUser(user);
       dataSource.setPassword("1234");
       dataSource.setDriverClass("com.mysql.jdbc.Driver");

       return null;
  }

   @Profile("test")
   @Bean("testDataSource")
   public DataSource dataSourceTest(@Value("db.password") String password) throws Exception {
       ComboPooledDataSource dataSource = new ComboPooledDataSource();
       dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test?");
       dataSource.setUser("root");
       dataSource.setPassword(password);
       dataSource.setDriverClass("com.mysql.jdbc.Driver");

       return null;
  }

   @Profile("prod")
   @Bean("ProdDataSource")
   public DataSource dataSourceProd() throws Exception {
       ComboPooledDataSource dataSource = new ComboPooledDataSource();
       dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/ssmbuild?");
       dataSource.setUser("root");
       dataSource.setPassword("1234");
       dataSource.setDriverClass("com.mysql.jdbc.Driver");

       return null;
  }

}
public class TestProfile {

   @Test
   public void test() {
       AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfProfile.class);
       String[] names = context.getBeanNamesForType(DataSource.class);
       for (String name : names) {
           System.out.println(name);
      }
       context.close();
  }
}
db.user=root
db.password=1234
db.driverClass=com.mysql.jdbc.Driver

 

 

@Profile:指定组件在哪个环境下才能被注册到环境中,不指定,任何环境下都能注册这个组件。没有标注@Profile的Bean在任何环境下都是加载的。

在Bean上加上环境标识@Profile,只有这个环境被激活的时候,才能注册到容器中,默认是default

 

如何切换数据源

1、设置虚拟机参数

-Dspring.profiles.active=xxx

xxx:我们需要设置的运行环境

2、使用代码

使用无参构造器创建容器

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//设置需要激活的环境
context.getEnvironment().setActiveProfiles("test", "dev");
//注册配置类
context.register(MainConfigOfProfile.class);
//启动、刷新容器
context.refresh();

 

@Profile还可以写在类上

当写在类上,表示只有是指定的环境,这个配置类的所有配置才生效

 

IOC

end


 

5、AOP

AOP【动态代理实现】:指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;

环境搭建:

导入AOP模块

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

 

定义一个业务类,对该业务进行日志切入

public class MathBusiness {

  //一个业务
  public int div(int a,int b) {
      return a / b;
  }
}

 

定义一个日志切面类,里面的方法用来感知业务执行到哪里

@Aspect声明这是一个切面类

@Aspect
public class LogAspects {

   public void logStart() {
       System.out.println("业务运行开始……");
  }

   public void logEnd() {
       System.out.println("业务运行结束……");
  }

   public void logException() {
       System.out.println("业务运行异常……");
  }

   public void logReturn() {
       System.out.println("业务的返回值……");
  }
}

 

通知方法:

前置通知(@Before):logstart:在目标方法(div)运行之前运行

后置通知(@After):logEnd:在目标方法(div)运行结束之后运行

返回通知(@AfterReturning):logReturn:在目标方法(div)正常返回之后运行

异常通知(@AfterThrowing):logException:在目标方法(div)出现异常以后运行

环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced( ))

注解的参数为:切入点。也就是哪个类,哪个方法(全类名)

@PointCut定义切入点

//定义切入点,表示切入点为com.yong.aop.MathBusiness下的所有方法,包含所有参数,也可以更具体。返回值为Int
@Pointcut("execution(public int com.yong.aop.MathBusiness.*(..))")
public void pointCut() {}

在切面类中对应的方法标注对应的注解

@Aspect
public class LogAspects {

   //定义切入点
   @Pointcut("execution(* com.yong.aop.MathBusiness.*(..))")
   public void pointCut() {}

   //使用本类中的切入点
   @Before("pointCut()")
   public void logStart() {
       System.out.println("业务运行开始……");
  }

   //原始方法
   @After("execution(public int com.yong.aop.MathBusiness.*(..))")
   public void logEnd() {
       System.out.println("业务运行结束……");
  }

   //使用外部切入点,全类名
   @AfterThrowing("com.yong.aop.LogAspects.pointCut()")
   public void logException() {
       System.out.println("业务运行异常……");
  }

   @AfterReturning("pointCut()")
   public void logReturn() {
       System.out.println("业务的返回值……");
  }

}

在容器中注入切入类和业务类

@EnableAspectJAutoProxy
@Configuration
public class MainConfigOfAOP {

   @Bean
   public MathBusiness mathBusiness() {
       return new MathBusiness();
  }

   @Bean
   public LogAspects aspects() {
       return new LogAspects();
  }
}

最后不要忘了开启基于注解的AOP模式@EnableAspectJAutoProxy

 

来测试一下

@Test
public void annotation() {
   ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
   MathBusiness business = context.getBean(MathBusiness.class);
   int i = business.div(2, 3);
   System.out.println(i);
}

 

完善切面类,获取业务类的信息

@Aspect
public class LogAspects {

   //定义切入点
   @Pointcut("execution(* com.yong.aop.MathBusiness.*(..))")
   public void pointCut() {}

   //使用本类中的切入点
   @Before("pointCut()")
   public void logStart(JoinPoint joinPoint) {
       Object[] args = joinPoint.getArgs();
       System.out.println(joinPoint.getSignature().getName()+"业务运行开始……,参数列表为{"
               + Arrays.asList(args)+"}");
  }

   //原始方法
   @After("execution(public int com.yong.aop.MathBusiness.*(..))")
   public void logEnd() {
       System.out.println("业务运行结束……");
  }

   //使用外部切入点,全类名
   @AfterThrowing(value = "com.yong.aop.LogAspects.pointCut()",throwing = "e")
   public void logException(Exception e) {
       System.out.println("业务运行异常信息-->"+e);
  }

   @AfterReturning(value = "pointCut()",returning = "result")
   public void logReturn(Object result) {
       System.out.println("业务的返回值:{"+result+"}");
  }

}

总结

分三个步骤

1)、将业务逻辑组件和切面类都加入到容器中﹔告诉Spring哪个是切面类(@Aspect)

2)、在切面类上的每一个通知方法上标注通知注解,告诉Spring何时何地运行(切入点表达式)

3)、开启基于注解的aop模式;

 

posted @ 2021-12-11 12:14  降雨几率  阅读(475)  评论(0)    收藏  举报