Spring注解及其原理

spring回顾

之前我们用spring,都是在beans.xml下配置好各种bean,然后在主程序里就可以使用了。

但是其实可以完全不使用配置文件,而仅仅使用配置类,直接用@Configuration注解,这样配置文件就和配置类给一一对应起来了。

beans.xml下面有各种各样的bean,对应了类中的各种各样的方法,方法需要加上@Bean标签。类型为方法返回值的类型,id默认即为方法名。

之前学习的是,你还需要到主配置文件里开启注解扫描,其实你也可以直接用AnnotationConfigApplicationContext这个类。这个类的构造函数就可以扔进去一个配置类,就相当于进行了包扫描。

一般我们在beans.xml下写的是<context:component-scan base-package="com.example"/>,当然这个也有对应的注解——@ComponentScan

注册组件

我们需要为IOC容器新增组件,有以下这些方法:

  • 包扫描+组件声明(@Controller、@Service、@Repository和@Component)
  • 直接@Bean
  • @Import,快速给容器内新增一个组件
    • @Import,id默认就是类的全限定类名
    • @ImportSelector,返回值一个String的数组,每个元素就是全限定类名
    • @ImportBeanDefinitionRegistry,手动注册bean到容器中。
  • 使用FactoryBean,默认会获得实现这个接口的getObject方法的返回值。

除了这些以外,还可以通过@Conditional来添加条件,@Primary来指定优先等。

Bean的生命周期

简单来说,容器管理bean的生命周期,即创建——初始化——销毁这个过程。

初始化即对象创建完成,并且赋值完成(利用setter方法)之后;而销毁则看是singleton还是prototype,单例的话是随着容器一起消亡,prototype是交给相对应的类自己管理。有下面四种方法可以对初始化和销毁的方法进行人工干预。

  • 指定初始化和销毁的方法。只需要写好初始化和销毁的方法,然后在@Bean注解中写入即可。
  • 通过让Bean实现InitializingBean(定义初始化逻辑),DisposableBean(定义销毁逻辑),所以我们只需要定义一个类实现这两个接口即可。
  • 可以使用JSR250(即Java规范规定的注解)
    • @PostConstruct:在bean创建完成并且属性赋值完成,来执行初始化方法
      • @PreDestroy:在容器销毁bean之前通知我们进行清理工作
  • BeanPostProcessor接口:bean的后置处理器,创建一个实现类来实现这个接口,然后把这个实现类放入容器即可工作。注意!这个接口非常的重要,它是spring框架高度可扩展性的一个体现,它的作用就是在IOC把bean都加载进来之后,我们还想对这些bean进行修改的时候所使用的。
    • postProcessBeforeInitialization:在初始化之前工作
    • postProcessAfterInitialization:在初始化之后工作

所以最后真正的顺序是:

  1. 执行对象的构造方法。单实例在容器启动时创建,而多实例在每次获取对象的时候创建。
  2. populateBean(beanName, mbd, instanceWrapper);,给bean进行属性赋值。
  3. 实例化、依赖注入完毕,在调用显示的初始化之前,会执行BeanPostProcessor.postProcessBeforeInitialization(当然是如果有的话,下同)
  4. 进行初始化。对应源代码中的invokeInitMethods,执行初始化操作。
  5. 执行BeanPostProcessor.postProcessAfterInitialization,此时bean已经在容器中。
  6. 销毁:单实例随着容器消亡,多实例容器不负责管理。

属性赋值

可以直接在类对应的属性上面加上@Value("")来对属性赋值,同样支持${}来引用外部的配置文件,需要在配置类上面通过@PropertySource读取外部配置文件中的k/v保存到运行的环境变量中。

自动装配

自动装配实际中用的非常多,因为它简单啊。

原理

它的原理是首先根据对象的类型去容器中查找有没有相关的实例,如果只有一个那就直接匹配;如果有多个的话,就需要根据属性的名字来进行匹配;使用@Qualifier指定需要装配的组件的id,而不是使用属性名;如果一个都没找到,则会报错,但是可以用@Autowired(required=false)就不报错。在实际中还可以用@Primary来优先使用哪个bean。

所以在实际中,我们最好还是让它只有一个组件在IOC容器中。

当然Java规范自己还带两个标签,分别是@Resource@Inject

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

所以显而易见,我们在spring应该使用autowired注解。

背后的代码

自动装配主要是由AutowiredAnnotationBeanPostProcessor这个类来完成的。和上面的生命周期类一样。

除此之外,@Autowired还可以用在构造器,参数,方法,属性上;它们都是从容器中获取参数组件的值

  • 标注在方法位置:@Bean+方法参数;参数从容器中获取;默认不写@Autowired效果是一样的;都能自动装配
  • 标在构造器上:如果组件只有一个有参构造器,这个有参构造器的@Autowired可以省略,参数位置的组件还是可以自动从容器中获取。
  • 放在参数位置

如果我自定义的一些组件希望使用类似ApplicationContext这样的spring容器的底层组件,自定义组件需要实现xxxAware,然后实现里面的一些方法。打个比方就像我原先是把我的组件交给IOC容器,让它自己一步步帮我搞定这个组件,但是现在我希望我能够干涉某些步骤,所以我现在让这个组件自己去实现某些aware接口,这样当组件到达某一个步骤的时候就会执行特定的方法,这种感觉其实就跟回调函数一样。

所有的这些Aware,其实都是通过Processor这些后置处理器来实现的。

Profile切换组件

简单来说就是根据不同的环境,动态来切换组件。比如我在开发的时候用的数据库是A,但是到了生产环境我希望用的数据库是B,就可以通过它来实现。

@Profile的使用非常简单,只需要在你需要的组件上面加上这个,然后就相当于给这个组件增加了一个条件,只有当环境是对应的时候才会加载这个组件。

如何设置环境呢?

  • -Dspring.profiles.active=xxx 可以在虚拟机参数中指定

  • 直接在代码中指定:

    1
    2
    3
    4
    AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
    annotationConfigApplicationContext.getEnvironment().setActiveProfiles("test");
    annotationConfigApplicationContext.register(MainConfig.class);
    annotationConfigApplicationContext.refresh();

记得如果你没对一个bean添加这个注解,那么在任何环境下都会加载的。

AOP

假设定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、方法出现异常)

定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知MathCalculator.div运行到哪里然后执行;

通知方法:

  • 前置通知(@Before):在目标方法(div)运行之前运行
  • 后置通知(@After):在目标方法(div)运行结束之后运行(无论方法正常结束还是异常结束)
  • 返回通知(@AfterReturning):在目标方法(div)正常返回之后运行,该注解还带有一个returning的属性,用来告知结果给谁。
  • 异常通知(@AfterThrowing):在目标方法(div)出现异常以后运行,该注解还有一个throwing的属性,用来把异常赋值给谁。
  • 环绕通知(@Around):动态代理,手动推进目标方法运行(joinPoint.procced())

然后把这两个类都加入到IOC容器中,并且给切面类(即日志类)加上@Aspect注解,然后在配置类上面开启自动代理@EnableAspectJAutoProxy,最后通过容器获取对象并执行。

我们当然也可以通过在切面类中传入JoinPoint来获取被代理的函数的信息。

###EnableAspectJAutoProxy源码

首先可以根据上面的注解,看到这个注解类:@Import({AspectJAutoProxyRegistrar.class})显然这个注册类和它有着千丝万缕的关系。而这个类其实又实现了ImportBeanDefinitionRegistrar,很明显这个类就是之前用过的,能给容器中自定义增加组件的。所以显然,这个类就是用来给容器中添加组件的。

那么它添加了什么bean呢?debug模式下跟踪下可以发现其实是AnnotationAwareAspectJAutoProxyCreator,那么毫无疑问,这个组件就是核心了。

然而…这个组件的复杂程度….

image-20200413223615366

我们主要关注上面的这些PostProcessor,同时关注下BeanFactoryAware。

创建AnnotationAwareAspectJAutoProxyCreator

  • 传入配置类,创建ioc容器

  • 注册配置类,调用refresh()方法刷新容器,下面是核心代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    this.postProcessBeanFactory(beanFactory);
    this.invokeBeanFactoryPostProcessors(beanFactory);
    this.registerBeanPostProcessors(beanFactory); //详细分析下这个
    this.initMessageSource();
    this.initApplicationEventMulticaster();
    this.onRefresh();
    this.registerListeners();
    this.finishBeanFactoryInitialization(beanFactory);
    this.finishRefresh();
  • registerBeanPostProcessors(beanFactory);注册bean的后置处理器来方便拦截bean的创建;

    • 先获取ioc容器已经定义了的需要创建对象的所有BeanPostProcessor

    • 给容器中加别的BeanPostProcessor

    • 优先注册实现了PriorityOrdered接口的BeanPostProcessor;

    • 再给容器中注册实现了Ordered接口的BeanPostProcessor;

      1
      2
      3
      4
      List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList();
      List<BeanPostProcessor> internalPostProcessors = new ArrayList();
      List<String> orderedPostProcessorNames = new ArrayList();
      List<String> nonOrderedPostProcessorNames = new ArrayList();
    • 注册没实现优先级接口的BeanPostProcessor;

    • 注册BeanPostProcessor,实际上就是创建BeanPostProcessor对象,保存在容器中;创建internalAutoProxyCreator的BeanPostProcessor【即AnnotationAwareAspectJAutoProxyCreator】

      • 创建Bean的实例
      • populateBean;给bean的各种属性赋值
      • initializeBean:初始化bean;
        • invokeAwareMethods():处理Aware接口的方法回调
        • applyBeanPostProcessorsBeforeInitialization():应用后置处理器的postProcessBeforeInitialization()
        • invokeInitMethods();执行自定义的初始化方法
        • applyBeanPostProcessorsAfterInitialization();执行后置处理器的postProcessAfterInitialization()
      • BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator)创建成功;–》aspectJAdvisorsBuilder
    • beanFactory.addBeanPostProcessor(postProcessor);

  • 到这里AnnotationAwareAspectJAutoProxyCreator创建完了,之后就继续reflesh里面的方法,会把其它我们自定义的一些bean加进来(包括切面类和被增强的类)

  • 之后就是用固定的模式去增强原来的方法了(细节省略),反正就是动态代理。

声明式事务

其实事务使用起来非常简单,只需要三个步骤即可:

  1. 给方法上标注 @Transactional 表示当前方法是一个事务方法;
  2. @EnableTransactionManagement 开启基于注解的事务管理功能;
  3. 配置事务管理器来控制事务;

源码

看下@EnableTransactionManagement,会发现其实本质上就是用了给IOC容器中增加了AutoProxyRegistrar和ProxyTransactionManagementConfiguration,即这两个组件完成了所有的工作。

AutoProxyRegistrar会搞一个后置的处理器,利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理对象执行方法利用拦截器链进行调用;

ProxyTransactionManagementConfiguration则是首先给容器中增加事务增强器,这个事务增强器就可以获取先关的属性,进行事务的操作。