0 概述
在以前看了Spring实战第四版以后,最近再次看了《Spring Boot实战》,觉得Spring很神奇,怎么加个注解能实现这么多功能。
最后翻到了这本好书,《Spring源码深度解析(第2版)》,算是彻底解开了这部分的谜题了。
1 XmlBeanFactory
1.1 功能
XmlBeanFactory的基础功能是,读取xml构造bean容器,然后可以随意在容器中获取bean。它的主要功能有:
- 多构造器构造bean,自定义bean的构造参数
- 多scope构造支持(单例构造,原型构造,session和请求级别的构造),factory方法构造,factory类构造,懒加载构造,dependsOn依赖构造
- 构造后的init-method回调,销毁时的destory-method回调
- 多种方式的依赖注入,构造器注入,setter注入。
- 支持循环依赖,这点确实很屌!
- 支持以不同beanId(即使同类型)来构造bean,或者仅支持类型传入来构造bean。
- 支持丰富的插件机制,beanPostProcessor,各种aware,各种生命周期回调
- 支持属性注解解析,属性表达式求解,属性编辑器(字符串转目标类型)的插件支持。
- 支持多线程
1.2 例子
1.2.1 xml描述bean
代码在这里
<?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="service1" class="spring_test.ServiceA">
<constructor-arg index="0">
<value>fish</value>
<constructor-arg>
</bean>
</bean id="service2" class="spring_test.ServiceA">
<constructor-arg index="0">
<list>
<value>cat</value>
<value>dog</value>
<list>
</constructor-arg>
</bean>
</<!--不能直接通过私有变量来注入-->
bean id="service3" class="spring_test.ServiceA">
<property name="gg" value="kk">
<property>
</bean>
</<!--setter注入-->
bean id="service4" class="spring_test.ServiceB">
<property name="animal">
<value>shell</value>
<property>
</bean>
</<!--factory模式-->
bean id="service5" class="spring_test.ServiceCFactory">
<property name="animal">
<value>Weasel</value>
<property>
</bean>
</beans> </
完全以xml的方式来描述bean,支持构造器和setter来注入依赖。注意,可以指定bean的工厂来生成bean。但是注意不能以私有变量的方式注入依赖。
package spring_test;
import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
= new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
BeanFactory beanFactory
//通过id来获取bean
= (ServiceA)beanFactory.getBean("service1");
ServiceA service1 .showMessage();
service1
= (ServiceA)beanFactory.getBean("service2");
ServiceA service2 .showMessage();
service2
//<!--不能直接通过私有变量来注入-->
//ServiceA service3 = (ServiceA)beanFactory.getBean("service3");
//service3.showMessage();
//通过类型来获取bean
= (ServiceB)beanFactory.getBean(ServiceB.class);
ServiceB service4 .showAnimal();
service4
//获取factory的bean
//IServiceC service5 = (IServiceC) beanFactory.getBean(IServiceC.class);
= (IServiceC) beanFactory.getBean("service5");
IServiceC service5 .swim();
service5}
}
支持以id或者类型的方式来获取bean,非常方便。
package spring_test;
import org.springframework.beans.factory.FactoryBean;
/**
* Created by fish on 2021/2/20.
*/
public class ServiceCFactory implements FactoryBean<ServiceC> {
private String animal;
public void setAnimal(String animal){
this.animal = animal;
}
public ServiceC getObject(){
System.out.println("Factory getBean serviceC");
return new ServiceC(this.animal);
}
public Class<ServiceC> getObjectType(){
return ServiceC.class;
}
public boolean isSingleton(){
return true;
}
}
特别注意的是,当bean的类型实现了FactoryBean接口的时候,Spring不是创建这个ServiceCFactory类型,而是创建getObject方法返回的实例作为bean。
1.2.2 循环依赖
代码在这里
<?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="serviceA" class="spring_test.ServiceA">
<property name="serviceB" ref="serviceB"></property>
<bean>
</bean id="serviceB" class="spring_test.ServiceB">
<property name="serviceC" ref="serviceC">
<property>
</bean>
</bean id="serviceC" class="spring_test.ServiceC">
<property name="serviceA">
<ref bean="serviceA"></ref>
<property>
</bean>
</beans> </
定义三个服务,这三个服务互相引用对方,注意,只能是属性注入的方式。如果是构造器注入依赖的方式,循环依赖会失败。
package spring_test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by fish on 2021/2/19.
*/
public class ServiceA {
private ServiceB serviceB;
public void setServiceB(ServiceB serviceB){
this.serviceB = serviceB;
}
public ServiceB getServiceB(){
return this.serviceB;
}
}
ServiceA代码
package spring_test;
/**
* Created by fish on 2021/2/19.
*/
public class ServiceB {
private ServiceC serviceC;
public void setServiceC(ServiceC serviceC){
this.serviceC = serviceC;
}
public ServiceC getServiceC(){
return this.serviceC;
}
}
ServiceB代码
package spring_test;
/**
* Created by fish on 2021/2/20.
*/
public class ServiceC {
private ServiceA serviceA;
public void setServiceA(ServiceA serviceA){
this.serviceA = serviceA;
}
public ServiceA getServiceA(){
return this.serviceA;
}
}
ServiceC代码
package spring_test;
import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
= new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
BeanFactory beanFactory
//获取bean
= (ServiceA)beanFactory.getBean(ServiceA.class);
ServiceA serviceA = (ServiceB)beanFactory.getBean(ServiceB.class);
ServiceB serviceB = (ServiceC)beanFactory.getBean(ServiceC.class);
ServiceC serviceC
//检查对应的ref
System.out.println(serviceA.getServiceB()==serviceB);
System.out.println(serviceB.getServiceC()==serviceC);
System.out.println(serviceC.getServiceA()==serviceA);
}
}
获取bean,并且检查它们互相的引用都是正确的
1.2.3 扩展机制
代码在这里
package spring_test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by fish on 2021/2/19.
*/
public class ServiceA implements BeanNameAware,BeanClassLoaderAware,BeanFactoryAware{
private String animal;
public void setAnimal(String animal){
this.animal = animal;
}
//BeanNameAware接口
private String beanName;
public void setBeanName(String var1){
this.beanName = var1;
}
//BeanClassLoaderAware接口
private ClassLoader classLoader;
public void setBeanClassLoader(ClassLoader var1){
this.classLoader = var1;
}
//BeanFactoryAware接口
private BeanFactory beanFactory;
public void setBeanFactory(BeanFactory var1) throws BeansException{
this.beanFactory = var1;
}
public void showAnimal(){
System.out.println("beanFactory:"+this.beanFactory);
System.out.println("classLoader:"+this.classLoader);
System.out.println("beanName:"+this.beanName);
System.out.println("animal:"+this.animal);
}
}
Spring可以指定各个Aware接口,在创建bean以后会检查是否满足Aware接口,满足的话回调数据给他。Aware扩展提供了bean获取bean容器自身相关信息的工具。
package spring_test;
import org.springframework.beans.factory.InitializingBean;
/**
* Created by fish on 2021/2/19.
*/
public class ServiceB implements InitializingBean{
public ServiceB(){
System.out.println("serviceB constructor");
}
private String message;
public void setMessage(String message){
System.out.println("serviceB property inject");
this.message = message;
}
//InitializingBean接口
public void afterPropertiesSet() throws Exception{
System.out.println("serviceB afterPropertiesSet");
}
//在xml指定的initMethod
public void myInitMethod(){
System.out.println("serviceB myInit Method");
}
public void showMessage(){
System.out.println("serviceB showMessage:"+this.message);
}
}
bean id="serviceB" class="spring_test.ServiceB" init-method="myInitMethod">
<property name="message"><value>fish</value></property>
<bean> </
定义一个ServiceB,实现InitializingBean接口,并且在xml中指定了init-method。那么,Spring的调用顺序是:
- serviceB constructor
- serviceB property inject
- serviceB afterPropertiesSet
- serviceB myInit Method
InitializingBean扩展提供了bean获取初始化完成回调的通知
package spring_test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.Nullable;
/**
* Created by fish on 2021/2/20.
*/
public class MyBeanPostProcessor implements BeanPostProcessor {
@Nullable
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("before init:"+beanName);
return bean;
}
@Nullable
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("after init:"+beanName);
//对于符合条件的bean,我们可以返回新的bean给他
//注意,原始的bean,只能以参数的方式传入
if( beanName.equals("serviceD")){
return new ServiceCMock((ServiceC)bean);
}
return bean;
}
}
package spring_test;
import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
= new XmlBeanFactory(new ClassPathResource("beanFactory.xml"));
DefaultListableBeanFactory beanFactory
//手动添加BeanPostProcessor扩展
= beanFactory.getBean(MyBeanPostProcessor.class);
MyBeanPostProcessor myBeanPostProcessor .addBeanPostProcessor(myBeanPostProcessor);
beanFactory
//serviceC没有被mock掉
= (ServiceC)beanFactory.getBean("serviceC");
ServiceC serviceC .work();
serviceC
//serviceD被mock掉了,mock的逻辑在MyBeanPostProcessor里面
= (ServiceC)beanFactory.getBean("serviceD");
ServiceC serviceD .work();
serviceD}
}
BeanPostProcessor是最为重要的扩展,它提供了通知第三方bean初始化完成前,初始化完成后的通知。这种通知并且给与了BeanPostProcessor修改bean实例的机会,甚至提供了修改bean指向的能力。
1.3 原理
1.3.1 循环依赖
具体原理看这里。它的主要思路是,建立三级缓存:
- singletonObjects缓存,已经完整建立bean以后的缓存
- earlySingletonObjects缓存,已经执行构造器创建bean,但是仍没有进行属性注入和初始化的缓存
- singletonsCurrentlyInCreation缓存,在构造器开始前添加缓存,构造器完成,并且注入属性依赖后删除缓存。
doGenBean(final String name){//P86
T //查找singletonObjects缓存,或者earlySingletonObjects缓存有没有该bean
Object sharedInstance = getSingleton(beanName);
if( sharedInstance != null ){
//有的话直接返回
return sharedInstance
}else{
...
= getSingleton(beanName,new ObjectFactory<Object>{
sharedInstance public Object getObject() throws BeansException{
try{
//创建bean本身,这一行最终会调用到doCreateBean
return createBean(beanName,mbd,args);
}
}
});
}
}
我们执行getBean的时候,先尝试在singletonObjects缓存,或者earlySingletonObjects缓存查看一下有没有这个对象,有的话就返回
//P98,当没有缓存的时候,尝试创建对象
public Object getSingleton(String beanName, ObjectFactory singletonFactory){
synchronized(this.singletonObjects){
Object singletonObject = this.singletonObjects.get(beanName);
//添加到singletonsCurrentlyInCreation缓存
beforeSingletonCreation(beanName);
...
//创建对象本身
= singletonFactory.getObject();
singletonObject
//删除到singletonsCurrentlyInCreation缓存
afterSingletonCreation(beanName);
//添加到singletonObjects缓存
addSingleton(beanName,singletonObject);
}
}
public void beforeSingletonCreation(String beanName){
//检查是否在循环构造中
if( !this.singletonsCurrentlyInCreation.add(beanName)){
throw new BeanCurrentlyInCreationException(beanName);
}
}
public void afterSingletonCreation(String beanName){
//检查是否在循环依赖中
if( !this.singletonsCurrentlyInCreation.remove(beanName)){
throw new IllegalStateException('....');
}
}
没有缓存的时候,使用getSingleton来创建bean本身。注意,这里用了singletonsCurrentlyInCreation来检查循环的构造依赖。完成所有bean创建以后,会添加singletonObjects缓存。
Object doCreateBean(String beanName){//P108
...
= createBeanInstance(....);//108,调用构造器创建bean
bean ...
addSingletonFactory(beanName,new ObjectFactory(){})//P109,提供添加到earlySingletonObjects缓存的工厂方法
...
poplulateBean(beanName,mbd,bean)//P109,属性依赖注入
...
initializeBean(beanName,...)//P109,调用aware扩展,InitializingBean扩展,和BeanPostProcessor扩展
...
return bean;
}
ObjectFactory最终会执行getObject来创建bean,而getObject会调用createBean,createBean会调用doCreateBean。doCreateBean会先用构造创建bean,然后就马上添加到earlySingletonObjects缓存。
这个就是创建bean的关键流程了,我们试一下分析具体的例子
1.3.1.1 普通流程
创建A的bean,并且A的构造器依赖于B。B的构造器是空的。
-> 检查singletonObjects和earlySingletonObjects是否有A的bean,发现没有,需要创建A的bean。
-> singletonsCurrentlyInCreation缓存添加A
-> new A()
->检查singletonObjects和earlySingletonObjects是否有B的bean,发现没有,需要创建B的bean。
->singletonsCurrentlyInCreation缓存添加B
->new B()
->earlySingletonObjects缓存添加B
->singletonsCurrentlyInCreation缓存删除B
->singletonObjects缓存添加B
->earlySingletonObjects缓存添加A
->singletonsCurrentlyInCreation缓存删除A
->singletonObjects缓存添加A
这是我们尝试构造一个的过程。
1.3.1.2 构造器循环依赖
创建A的bean,并且A的构造器依赖于B。B的构造器是也是依赖于A的。
-> 检查singletonObjects和earlySingletonObjects是否有A的bean,发现没有,需要创建A的bean。
-> singletonsCurrentlyInCreation缓存添加A
-> new A()
->检查singletonObjects和earlySingletonObjects是否有B的bean,发现没有,需要创建B的bean。
->singletonsCurrentlyInCreation缓存添加B
->new B()
->singletonsCurrentlyInCreation缓存添加A,失败,这个时候报错,因为singletonsCurrentlyInCreation缓存已经有A了,有循环构造依赖的问题
这就是Spring中构造器循环依赖失败的过程。
1.3.1.3 属性循环依赖
创建A的bean,并且A的属性注入依赖于B。B的属性注入也是依赖于A的。
-> 检查singletonObjects和earlySingletonObjects是否有A的bean,发现没有,需要创建A的bean。
-> singletonsCurrentlyInCreation缓存添加A
-> new A(),空构造器
-> earlySingletonObjects缓存添加A
->检查singletonObjects和earlySingletonObjects是否有B的bean,发现没有,需要创建B的bean。
->singletonsCurrentlyInCreation缓存添加B
->new B()
->earlySingletonObjects缓存添加B
->检查singletonObjects和earlySingletonObjects是否有A的bean,发现在earlySingletonObjects已经有了,直接返回A的bean。
->singletonsCurrentlyInCreation缓存删除B
->singletonObjects缓存添加B
->singletonsCurrentlyInCreation缓存删除A
->singletonObjects缓存添加A
由于构造器执行之后,就会马上添加earlySingletonObjects缓存,所以,B的bean在属性注入的时候可以复用这个只创建,但是仍没有执行属性依赖的A的bean。因此,Spring实现了属性循环依赖
1.3.2 属性流程
//P143 设置@Qualifer和@Autowired的自动注入解析器
.setAutowireCandicateResolver(new QulifierAnnotationAutowireCandidateResolver);
beanFactory
//P145 设置属性SpEL表达式处理器,运行时解析字符串为#{xxx}形式时的值,来注入属性
.setExpressionResolver(new StandardBeanExpressionResolver);
beanFactory
//P145 设置属性编辑器,设置字符串到属性目标类型的转换,来注入属性
.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this,getEnvironment())); beanFactory
在populateBean中主要实现的上面的三个方法。
//P123
protected void populdateBean(String beanName,AbstractBeanDefinition mbd,BeanWrapper bw){
...
if( mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ){
autowireByName(beanName,mbd,bw,pvs)
}
if( mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPEs ){
autowireByType(beanName,mbd,bw,pvs)
}
...
applyPropertyValues(beanName,mbd,bw,pvs);
};
//P126
protected void autowireByTye(String beanName,AbstractBeanDefinition mbd,BeanWrapper bw, MutablePropertyValues pvs){
...
for( String propertyName: propertyNames ){
...
Object autowireArgument = resolveDependency(...);
.add(propertyName,autowireArgument);
pvs...
}
}
//P128
protected void doResolveDependency(...){
//在这里使用了beanFactory的setAutowireCandicateResolver的值,用了自动注入注解的解析
Object value = getAutowireCandidateResolver().getSuggestedValue(descripteor);
if( value != null ){
if( value instanceof String){
//读取配置文件中的值
String strVal = resolveEmbeddedValue((String)value);
}
...
}
...
}
//P131
protected void applyPropertyValues(String beanName, BeanDefinition mbd , BeanWrapper bw, PropertyValue pvs){
...
List<PropertyValue> original = pvs.getPropertyValueList();
...
= new BeanDefinitionValueResolver(this,beanName,mbd,converter);
BeanDefinitionValueResolver valueResolver ...
for( PropertyValue pv:original){
...
//在这里使用了expressionResolver来处理SpEL表达式处理器
Object resolvedValue = valueResolver.resoveValueIfNecessary(pv,originalValue);
...
}
...
//BeanWrapper内部使用属性编辑器来做字符串到目标类型的转换
.setPropertyValues(...);
bw}
以上就是概要的属性注入流程了
2 ClassPathXmlApplicationContext
2.1 功能
ClassPathXmlApplicationContext相对于XmlBeanFactory提供了的功能是:
- 自动添加BeanFactoryPostProcessor和BeanPostProcessor
- 预先加载bean,这样就能避免首次使用时才加载的延迟时间,但是会增加初始化启动程序的时间
- 消息广播器,国际化支持
2.2 例子
2.2.1 BeanFactoryPostProcessor和其他aware
代码在这里
package spring_test;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.EmbeddedValueResolverAware;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.util.StringValueResolver;
public class ServiceB implements EnvironmentAware,EmbeddedValueResolverAware, ApplicationContextAware{
public ServiceB(){
System.out.println("serviceB constructor");
}
//获取,环境变量
private Environment environment;
public void setEnvironment(Environment var1){
this.environment = var1;
}
//获取,配置文件的字符串解析器
private StringValueResolver stringValueResolver;
public void setEmbeddedValueResolver(StringValueResolver var1){
this.stringValueResolver = var1;
}
//获取,应用程序上下文
private ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext var1) throws BeansException{
this.applicationContext = var1;
}
public void show(){
System.out.println("environment:"+this.environment);
System.out.println("stringValueResolver:"+this.stringValueResolver);
System.out.println("applicationContext:"+this.applicationContext);
}
}
加入了新的aware机制
package spring_test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
/**
* Created by fish on 2021/2/24.
*/
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{
System.out.println("beanFactoryPostProcesssor postProcessBeanFactory :"+beanFactory);
}
}
加了新的扩展机制,BeanFactoryPostProcessor,它会在ApplicationContext在执行前回调。一般给予了第三方注册自己的bean的机会。
package spring_test;
import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
System.out.println("before init application context ");
= new ClassPathXmlApplicationContext("beanFactory.xml");
ApplicationContext bf
System.out.println("after init application context");
= (ServiceA) bf.getBean("serviceA");
ServiceA serviceA .showAnimal();
serviceA
= (ServiceB) bf.getBean("serviceB");
ServiceB serviceB .show();
serviceB}
}
值得注意的是,ClassPathXmlApplicationContext不再需要手动注册BeanPostProcessor和BeanFactoryPostProcessor。它会在启动的时候自动寻找所有实现了BeanPostProcessor和BeanFactoryPostProcessor的bean,并把它们注册到ApplicationContext里面
2.2.2 消息广播器
代码在这里
package spring_test;
import org.springframework.context.ApplicationEvent;
/**
* Created by fish on 2021/2/25.
*/
public class MyEvent extends ApplicationEvent {
private String msg ;
public MyEvent(Object source,String msg){
super(source);
this.msg = msg;
}
public void print(){
System.out.println(this.msg);
}
}
先定义一个消息类型MyEvent
package spring_test;
import org.springframework.context.ApplicationEvent;
/**
* Created by fish on 2021/2/25.
*/
public class MyEvent2 extends ApplicationEvent {
private String msg ;
public MyEvent2(Object source,String msg){
super(source);
this.msg = msg;
}
public void print(){
System.out.println(this.msg);
}
}
再定义另外一个消息类型MyEvent2
package spring_test;
import org.springframework.context.ApplicationListener;
/**
* Created by fish on 2021/2/19.
*/
public class MyListener implements ApplicationListener<MyEvent>{
public void onApplicationEvent(MyEvent var1){
System.out.println("receive MyEvent: "+var1);
}
}
注册一个MyListener,只响应MyEvent的消息
package spring_test;
import org.springframework.beans.BeansException;
import org.springframework.context.*;
import org.springframework.core.env.Environment;
import org.springframework.util.StringValueResolver;
public class ServiceB implements ApplicationEventPublisherAware{
public ServiceB(){
System.out.println("serviceB constructor");
}
private ApplicationEventPublisher applicationEventPublisher;
public void setApplicationEventPublisher(ApplicationEventPublisher var1){
this.applicationEventPublisher = var1;
}
public void sendMsg(){
this.applicationEventPublisher.publishEvent(new MyEvent("hello","msg"));
}
public void sendMsg2(){
this.applicationEventPublisher.publishEvent(new MyEvent2("hello2","msg2"));
}
}
对于任意一个bean,如果它想发布消息,只需要实现ApplicationEventPublisherAware接口就可以了,取出publisher来直接发布消息。
<?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="serviceB" class="spring_test.ServiceB"></bean>
<bean class="spring_test.MyListener"></bean>
<beans> </
xml中的定义也不需要对消息倾听器和发布器进行区别对待,把它们都放进bean容器就可以了。
package spring_test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
= new ClassPathXmlApplicationContext("beanFactory.xml");
ApplicationContext bf
= (ServiceB) bf.getBean("serviceB");
ServiceB serviceB
//这个消息,MyListener是接收的
.sendMsg();
serviceB
//这个消息,MyListener是不接收的,因为类型不匹配
.sendMsg2();
serviceB}
}
最后是启动程序
2.3 原理
public void refresh(){
...
//对beanFactory进行功能填充
prepareBeanFactory();
...
//手动查找BeanFactoryPostProcessor,并调用它们。
invokeBeanFactoryPostProcessor();
//手动查找BeanPostProcessor,并注册它们
registerBeanPostProcessors();
//初始化注册国际化的消息源
initMessageSource();
//初始化注册应用消息广播器
initApplicationEventMulticaster();
...
//手动查找消息倾听器,并注册它们
registerListerns();
//初始化剩下的单实例,预热bean
finishBeanFactoryInitializtion();
}
在默认的ApplicationContext的构造函数中,会自动调用refresh函数来执行固定化的初始化流程。
3 aop
3.1 功能
Spring的Aop提供了面向切面编程,以aspectJ的方式指定切面,并提供了外部接口,让第三方以注解的方式实现切面。
3.2 例子
3.2.1 AspectJ的AOP使用
代码在这里
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
aop:aspectj-autoproxy/>
<bean id="serviceA" class="spring_test.ServiceA">
<property name="animal">
<value>dog</value>
<property>
</bean>
</bean class="spring_test.MyAspect"></bean>
<beans> </
在xml上,首先引入aspectj-autoproxy标签,它实际上是注册了一个BeanPostProcessor。然后直接将某个Aspect切面加入到bean工厂就可以了。这样,AspectJ的AOP指定就实现了。
package spring_test;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
/**
* Created by fish on 2021/2/27.
*/
@Aspect
public class MyAspect {
@Pointcut("execution( * *.showAnimal(..))")
public void test(){
}
@Before("test()")
public void beforeShowAnimal(){
System.out.println("before test...");
}
@After("test()")
public void afterShowAnimal(){
System.out.println("after test...");
}
@Around("test()")
public void aroundShowAnimal(ProceedingJoinPoint p) throws Throwable{
System.out.println("before around");
.proceed();
pSystem.out.println("after around");
}
}
对于一个POJO类,加Aspect注解就能指定了它为AspectJ类了。然后用Pointcut来定义对什么接口进行切面,用@Before,@After和@Around来定义这些接口编程是什么。
package spring_test;
/**
* Created by fish on 2021/2/19.
*/
public class ServiceA {
private String animal;
public void setAnimal(String animal){
this.animal = animal;
}
public void showAnimal(){
System.out.println("animal:"+this.animal);
}
}
一个普通的ServiceA类
package spring_test;
import org.springframework.beans.factory.BeanFactory ;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.Assert;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
= new ClassPathXmlApplicationContext("beanFactory.xml");
ApplicationContext bf
= (ServiceA) bf.getBean("serviceA");
ServiceA serviceA .showAnimal();
serviceA}
}
最后,启动程序即可。
before around
before test...
animal:dog
after around
after test...
这个是程序输出的结果
3.2.2 Proxy和Cglib的实现
代码在这里
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
aop:aspectj-autoproxy/>
<aop:config expose-proxy="true"/>
<bean id="serviceA" class="spring_test.ServiceA">
<property name="animal">
<value>dog</value>
<property>
</bean>
</bean id="serviceB" class="spring_test.ServiceB">
<property name="place">
<value>beijing</value>
<property>
</bean>
</bean id="serviceC" class="spring_test.ServiceC"/>
<bean class="spring_test.MyAspect"></bean>
<beans> </
打开aop开关,并且设置aop的expose-proxy属性为true。
package spring_test;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
/**
* Created by fish on 2021/2/27.
*/
@Aspect
public class MyAspect {
@Pointcut("execution( * ServiceA.*(..))|| execution(* ServiceB.*(..)) || execution(* ServiceC.*(..))")
public void test(){
}
@Around("test()")
public Object aroundShowAnimal(ProceedingJoinPoint p) throws Throwable{
System.out.println("---- before around ---- ");
Object result = p.proceed();
System.out.println("---- after around ---- ");
return result;
}
}
设置一个简单的AspectJ定位的aop。
package spring_test;
/**
* Created by fish on 2021/2/19.
*/
public class ServiceA {
private String animal;
public ServiceA(){
System.out.println("serviceA ref "+this);
}
public void setAnimal(String animal){
this.animal = animal;
}
public String getAnimal(){
return this.animal;
}
public void showAnimal(){
System.out.println("ServiceA showAnimal ref : "+ System.identityHashCode(this));
System.out.println("showAnimal : "+this.animal);
}
public final void finalShowAnimal(){
System.out.println("ServiceA finalShowAnimal ref : "+ System.identityHashCode(this));
//this指针被替换为cglib的派生类,cglib派生类无法捕捉final函数,其会自动引用到空基类的final函数
// 而空基类的animal为null的,所以这样做会失败,this.animal为空
System.out.println("finalShowAnimal : "+this.animal);
}
public final void finalShowAnimal2(){
//this指针被替换为cglib的派生类,派生类是可以捕捉getAnimal方法,所以这样会成功
System.out.println("finalShowAnimal2 : "+this.getAnimal());
}
private void privateShowAnimal(){
System.out.println("privateShowAnimal : "+this.getAnimal());
}
}
然后建立一个普通的ServiceA类,由于这个类没有实现任何接口,Spring的AOP会采用Cglib库来实现。
//spring的serviceA
= (ServiceA) bf.getBean("serviceA");
ServiceA serviceA System.out.println("serviceA ref: "+System.identityHashCode(serviceA) );
.showAnimal();
serviceA.finalShowAnimal();
serviceA.finalShowAnimal2(); serviceA
所以,对于这样的一个运行而言,会输出以下的结果:
---- before around ----
ServiceA showAnimal ref : 211968962
showAnimal : dog
---- after around ----
ServiceA finalShowAnimal ref : 396883763
finalShowAnimal : null
---- before around ----
---- after around ----
finalShowAnimal2 : dog
注意,finalShowAnimal和finalShowAnimal2都没有被AOP捕捉到,而showAnimal和getAnimal方法就会被AOP捕捉到。并且,finalShowAnimal输出的animal成员变量为null,而finalShowAnimal2调用getAnimal方法获取的变量就不是null了。
package spring_test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
/**
* Created by fish on 2021/2/28.
*/
public class ServiceAExtends extends ServiceA {
private ServiceA serviceA;
public ServiceAExtends() {
this.serviceA = new ServiceA();
this.serviceA.setAnimal("cat");
}
public String getAnimal(){
System.out.println("---- before around2 ---- ");
Object result = this.serviceA.getAnimal();
System.out.println("---- after around2 ---- ");
return (String)result;
}
public void showAnimal(){
System.out.println("---- before around2 ---- ");
this.serviceA.showAnimal();
System.out.println("---- after around2 ---- ");
}
//finalShowAnimal的无法被覆写
//finalShowAnimal的无法被覆写
//privateShowAnimal的无法被覆写
}
其实CgLib的实现就是集成一个ServiceA类生成一个新的ServiceAExtends类,来返回出来的代理对象,因此无法覆写final方法,最终导致final方法会引导到一个空成员变量的基类上面。
public class ServiceAExtends extends ServiceA {
public String getAnimal(){
System.out.println("---- before around2 ---- ");
Object result = super.getAnimal();
System.out.println("---- after around2 ---- ");
return (String)result;
}
public void showAnimal(){
System.out.println("---- before around2 ---- ");
super.showAnimal();
System.out.println("---- after around2 ---- ");
}
//finalShowAnimal直接用原有的
//finalShowAnimal直接用原有的
//privateShowAnimal直接用原有的
}
你可以会有疑问,为啥ServiceAExtends调用成员变量的serviceA,而不是直接使用super.ServiceA,就像以上的这段代码。
@Configuration
public class MyConfig{
@Bean
public ServiceA serviceA(){
return new ServiceA();
}
}
原因在于:
- 使用super方式传递的时候,创建bean的流程就会被改为,先定义ServiceAExtends类,然后创建ServiceAExtends的作为ServiceA的bean。但是,这样做是不行的,因为这样会暴露了代理的实际实现类ServiceAExtends。在实际工作中,我们只会生成自己的bean,然后交给Spring来做代理增强。这个时候Spring就无法使用super转发来增强,因为bean实例已经创建好了,不能重复创建一次,只能通过成员变量转发方法来实现代理。
- Spring会非常多的运行时代理增强,每个增强都是在前一个bean的基础上,包装方法实现的增强代理。我们难以在编译时就完全计算得到最终增强bean的类是什么,运行时的动态代理增强能提高不少的灵活性。
package spring_test;
/**
* Created by fish on 2021/2/28.
*/
public class ServiceB implements IServiceB {
private String place;
public void setPlace(String place){
this.place = place;
}
public void showPlace(){
System.out.println("serviceB showPlace ref: "+ System.identityHashCode(this));
System.out.println("serviceB place : "+this.place);
}
}
然后我们尝试用实现了接口的ServiceB类作为测试。由于这个类实现了接口,Spring的AOP会采用Proxy标准库来实现。
//这样会失败,因为ServiceB被aop包绕后,用接口的情况下用Proxy实现,返回的是IServiceB类型,不是ServiceB类型
//ServiceB serviceB = (ServiceB) bf.getBean("serviceB");
//成功,返回的是Proxy的aop实现,IServiceB类型
= (IServiceB) bf.getBean("serviceB");
IServiceB serviceB System.out.println("serviceB showPlace ref: "+ System.identityHashCode(serviceB));
.showPlace(); serviceB
注意,对于实现了接口的,返回的bean不再是ServiceB类型,而是IServiceB的接口类型。
serviceB showPlace ref: 1810899357
---- before around ----
serviceB showPlace ref: 954702563
serviceB place : beijing
---- after around ----
执行结果如上,简单且容易理解。
package spring_test;
/**
* Created by fish on 2021/2/28.
*/
public class ServiceBInterface implements IServiceB {
private ServiceB serviceB;
public ServiceBInterface(){
this.serviceB = new ServiceB();
this.serviceB.setPlace("shanghai");
}
public void showPlace(){
System.out.println("---- before around3 ---- ");
this.serviceB.showPlace();
System.out.println("---- after around3 ---- ");
}
}
Proxy标准库实现的AOP也是相当直观,建立一个新的ServiceBInterface类型,实现了ServiceB的所有接口,然后将代理的实现全部引导到ServiceB实际类型上。注意,这个方法的局限是,只能代理实现了接口的类型,并且只能代理接口的方法。
package spring_test;
import org.springframework.aop.framework.AopContext;
/**
* Created by fish on 2021/3/1.
*/
public class ServiceC {
public void go1(){
System.out.println("I am go1 begin ... ");
go1_inner();
System.out.println("I am go1 end ...");
}
public void go1_inner(){
System.out.println("I am go1_inner ");
}
public void go2(){
System.out.println("I am go2 begin ... ");
((ServiceC)AopContext.currentProxy()).go2_inner();
System.out.println("I am go2 end ...");
}
public void go2_inner(){
System.out.println("I am go2_inner ");
}
}
最后,讨论一个ServiceC的代码自身调用的问题。go1直接调用自身的go1_inner方法,go2通过AopContext来调用go2_inner方法。
= (ServiceC) bf.getBean("serviceC");
ServiceC serviceC .go1();
serviceC.go2(); serviceC
测试代码如上
---- before around ----
I am go1 begin ...
I am go1_inner
I am go1 end ...
---- after around ----
---- before around ----
I am go2 begin ...
---- before around ----
I am go2_inner
---- after around ----
I am go2 end ...
---- after around ----
测试结果为,go1_inner中失去了代理能力,而使用AopContext的go2_inner就依然有代理能力。这是因为this指针,不是外部的代理类型的指针,原因是:
- 在Proxy实现里面,this是serviceB类型的地址,而不是ServiceBInterface类型的地址。
- 在Cglib实现里面,this是serviceB类型的地址,不是ServiceBExtends类型的地址,也不是ServiceBExtends基类的地址
这事情提醒我们,需要特别注意,对自身实例的其他方法的调用会失去AOP支持。并且,特别注意以上测试的两点:
- Cglib实现,调用final方法会造成空实例成员的问题,无法代理private方法。
- Proxy实现,只能代理所有接口展示的方法
3.2.3 第三方的AOP扩展
代码在这里
Spring的AOP允许以AspectJ作为切面编程的接口,也可以以其约定类型作为切面编程的接口。
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
aop:aspectj-autoproxy/>
<aop:config expose-proxy="true"/>
<bean id="serviceA" class="spring_test.ServiceA">
<bean>
</bean class="spring_test.MyAdvisor"></bean>
<beans> </
添加一个beanFactory,注意,添加了MyAdvisor类型
package spring_test;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
/**
* Created by fish on 2021/3/1.
*/
public class MyAdvisor implements PointcutAdvisor {
public Pointcut getPointcut(){
return new MyPointcut();
}
public Advice getAdvice(){
return new MyAdvise();
}
public boolean isPerInstance(){
return true;
}
}
一个切面包括两部分,Pointcut指定了对哪些方法进行切面,Advice指定了对切面执行什么操作。
package spring_test;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodMatcher;
import org.springframework.aop.Pointcut;
import org.springframework.lang.Nullable;
import java.lang.reflect.Method;
/**
* Created by fish on 2021/3/1.
*/
public class MyPointcut implements Pointcut,ClassFilter,MethodMatcher{
public ClassFilter getClassFilter(){
return this;
}
public MethodMatcher getMethodMatcher(){
return this;
}
public boolean matches(Class<?> var1){
//我们不采用类级别的检验
return true;
}
public boolean matches(Method var1, @Nullable Class<?> var2){
//我们检查方法上是否有@MyShow的注解
= var1.getAnnotation(MyShow.class);
MyShow showAnnotation return showAnnotation != null;
}
public boolean isRuntime(){
//不采用动态的需要传入Object的检查
return false;
}
public boolean matches(Method var1, @Nullable Class<?> var2, Object... var3){
//不采用动态的需要传入Object的检查
return false;
}
}
Pointcut的实现,可以在三级级别上的指定:
- 类级别上是否执行切面ClassFilter和boolean matches(Class<?> var1)方法
- 方法级别上是否执行切面getMethodMatcher和boolean matches(Method var1, @Nullable Class<?> var2)
- 运行级别的特定实例上的方法是否执行切面,isRuntime和boolean matches(Method var1, @Nullable Class<?> var2, Object… var3)。
package spring_test;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.aopalliance.intercept.MethodInterceptor;
import java.lang.reflect.Method;
import org.springframework.aop.BeforeAdvice;
import org.springframework.lang.Nullable;
/**
* Created by fish on 2021/3/1.
*/
public class MyAdvise implements MethodInterceptor,MethodBeforeAdvice,AfterReturningAdvice {
public Object invoke(MethodInvocation var1) throws Throwable{
System.out.println("--- myShow intercept begin ---");
Object result = var1.proceed();
System.out.println("--- myShow intercept end ---");
return result;
}
public void before(Method var1, Object[] var2, @Nullable Object var3) throws Throwable{
System.out.println("--- myShow before ---");
}
public void afterReturning(@Nullable Object var1, Method var2, Object[] var3, @Nullable Object var4) throws Throwable{
System.out.println("--- myShow afterReturning ---");
}
}
我们可以用MethodInterceptor来实现Around增强,MethodBeforeAdvice来实现Before增强,AfterReturningAdvice来实现AfterReturning增强。还有其他类型的增强,可以试试。
package spring_test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Created by fish on 2021/3/1.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyShow {
String value() default "";
}
定义一个特定的注解
package spring_test;
import org.springframework.aop.framework.AopContext;
/**
* Created by fish on 2021/3/1.
*/
public class ServiceA {
@MyShow("mmk")
public void go1(){
System.out.println("I am go1 ... ");
}
public void go2(){
System.out.println("I am go2 ... ");
}
}
然后在执行go1方法上的时候,就会执行我们定义在AOP切面的代码了。
3.3 原理
//P179
.registerAspectJAnnotationAutoProxyCreateorIfNecessary(parserContext,element); AopNamespaceUtils
对于aop:aspectj-autoproxy的标签,会自动注册AnnotationAwareAspectJAutoProxyCreator.class类型。该类型会继承BeanPostProcessor。显然,当任意一个bean定义的时候,就会调用它的postProcesssAfterInitialization方法。
//P182
public Object postProcessAfterInitialization(Object bean,String beanName){
...
return wrapIfNecessary(bean,beanName,cacheKey);
}
public Object warpIfNecessary(Object bean,String beanName,Object cacheKey){
...
Object[] interceptors = getAdvicesAndAdvisorsForBean(bean.getClass(),beanName,null);
...
Object proxy = createProxy(bean.getClass(),beanName,interceptors,new SingletonTargetSource(bean));
...
return proxy;
}
创建AOP的方法为,首先检查bean的类型,来确定需要执行多少种增强。如果增强器不为空,则用Cglib或者Proxy来创建新的bean实例。
//P184
public Object[] getAdvicesAndAdvisorsForBean(Class beanClass,String beanName,TargetSource targetSource){
...
//查找所有的advisor
List<Advisor> candidateAdvisors = findCandidateAdvisors();
//对每个advisor,检查它的类或者方法是否需要满足advisor
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors,beanClass,beanName);
...
return eligibleAdvisors;
}
增强器的查找分两步。首先,获取所有增强器,然后检查能满足当前类的有哪些增强器。
//P184
protected List<Advisor> findCandidateAdvisors(){
//P270,获取当前bean容器里面所有继承了Advisor.class类型的bean
List<Advisor> advisors = super.findCandidateAdvisors();
//P185,获取当前bean容器中,遍历所有的bean类型,检查它是否有@AspectJ注解,有的话,用AspectJAdvisors来包装它,生成一个新的advisors
.addAll(this.aspectJAdvisorBuilder.buildAspectJAdvisors());
advisors}
增强器分两种,要么是在bean容器的Advisor.class类型,要么是普通的POJO类型,但是有@AspectJ注解。
//P194
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors,Class beanClass,String beanName){
//检查方法和类自身是否有满足advisor要求的,主要就是调用advisor的pointcut的matches来检查。
.matches(....)
xxxx}
检查方法和类自身是否有满足advisor要求的
//P195
protected Object createProxy(){
...
= new ProxyFactory();
ProxyFactory proxyFactory
...
return proxyFactory.getProxy(this.proxyClassLoader);
}
//P198
public Object getProxy(ClassLoader classLoader){
return createAoProxy().getProxy(classLoader);
}
创建代理的过程了,这里具体看P198的代码,这里倒是不太难。
4 SpringBootApplication
4.1 功能
SpringBootApplication在ClassPathXmlApplicationContext上新增了以下功能:
- 以注解的方式定义和注入bean,并且提供条件自动注入bean的能力
- 提供统一的属性配置文件,和获取属性的方式
- 依赖的以starter的方式嵌入,自动引入依赖
4.2 例子
4.2.1 自动注入bean
代码在这里
package spring_test;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
@Component
public class ServiceADepend {
public String getAnimal(){
return "fish";
}
}
以@Component注意来定义一个bean,默认这个bean的ID为类型名称
package spring_test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
@Component
public class ServiceA {
@Autowired
private ServiceADepend serviceADepend;
public void showAnimal(){
System.out.println("showAnimal : "+this.serviceADepend.getAnimal());
}
}
然后我们以@Autowired的方式就能自动找到这个依赖,并以私有变量的方式注入进去,不需要写setter方法。注意,@Autowired的查找依赖的方式默认是以byType的方式,就是根据查找满足该ServiceADepend的类型来尝试注入进去。
package spring_test;
/**
* Created by fish on 2021/3/15.
*/
public interface ServiceBDepend {
String getPlace();
}
当依赖是一个接口类型
package spring_test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
@Component
public class ServiceBDepend1 implements ServiceBDepend{
public String getPlace(){
return "home";
}
}
然后我们提供了实现该接口的类型ServiceBDepend1
package spring_test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
@Component
public class ServiceBDepend2 implements ServiceBDepend{
public String getPlace(){
return "garden";
}
}
以及实现了该接口的另外一个类型ServiceBDepend2
package spring_test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
@Component
public class ServiceB {
@Autowired
@Qualifier("serviceBDepend1")
private ServiceBDepend serviceBDepend;
public void play(){
System.out.println("we are playing in : "+this.serviceBDepend.getPlace());
}
}
最后,我们如果尝试直接注入ServiceBDepend类型就会失败,因为满足ServiceBDepend类型要求的有两个具体类型,Spring就会抱怨有歧义,无法选择。这个时候,我们可以用Qualifier来指定bean的名称,注意,是名称,不是类型。这个时候,Spring就会使用根据名称来查找bean的方式来注入。
package spring_test;
/**
* Created by fish on 2021/3/15.
*/
public interface ServiceCDepend {
String getInstrument();
}
同理,我们再次定义一个接口类型为ServiceCDepend
package spring_test;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
@Component("guitar")
public class ServiceCDepend1 implements ServiceCDepend {
public String getInstrument(){
return "guitar";
}
}
然后定义一个ServiceCDepend1类型,满足ServiceCDepend接口,注意,Component指定了该bean的名称为guitar,而不是默认的serviceCDepend1。
package spring_test;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
@Component("piano")
public class ServiceCDepend2 implements ServiceCDepend {
public String getInstrument(){
return "piano";
}
}
然后也定义了一个ServiceCDepend2类型,并指定了它的名称为piano。
package spring_test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* Created by fish on 2021/3/15.
*/
@Component
public class ServiceC {
@Resource(name="piano")
private ServiceCDepend serviceCDepend;
@Autowired
@Qualifier("guitar")
private ServiceCDepend serviceCDepend2;
public void showMusic(){
System.out.println("He is playing : "+this.serviceCDepend.getInstrument());
System.out.println("She is playing : "+this.serviceCDepend2.getInstrument());
}
}
这个时候,我们既可以用之前的@Autowired与@Qualifier的方式结合来查找bean,也可以直接用java的标准注解@Resource来查找需要注入的bean。
4.2.2 配置类,组件扫描和条件配置
代码在这里
├── spring_test
│ ├── App.java
│ ├── ServiceA.java
│ └── package1
│ ├── ServiceB.java
│ └── inner_package1
│ └── ServiceC.java
├── spring_test2
│ └── ServiceD.java
├── spring_test3
│ ├── ConfigureTest3.java
│ ├── ServiceE.java
│ ├── ServiceF.java
│ ├── ServiceG.java
│ ├── ServiceGImpl1.java
│ └── inner_package3
│ ├── ConfigureTestInner3.java
│ └── ServiceGImpl3.java
└── spring_test4
├── ConfigureTest4.java
└── ServiceGImpl2.java
7 directories, 14 files
这是源代码的包结构
package spring_test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import spring_test.package1.ServiceB;
import spring_test.package1.inner_package1.ServiceC;
import spring_test2.ServiceD;
import spring_test3.ConfigureTest3;
import spring_test3.ServiceE;
import spring_test3.ServiceF;
import spring_test3.ServiceG;
/**
* Hello world!
*
*/
@SpringBootApplication
@ComponentScan({"spring_test2","spring_test"})
@Import(ConfigureTest3.class)
public class App implements ApplicationRunner
{
public static void main( String[] args )
{
.run(App.class,args);
SpringApplication}
@Autowired
private ServiceA serviceA;
@Autowired
private ServiceB serviceB;
@Autowired
private ServiceC serviceC;
@Autowired
private ServiceD serviceD;
@Autowired
private ServiceE serviceE;
@Autowired
private ServiceF serviceF;
@Autowired
private ServiceG serviceG;
public void run(ApplicationArguments arguments) throws Exception{
.go();
serviceA.go();
serviceB.go();
serviceC.go();
serviceD.go();
serviceE.go();
serviceF.go();
serviceG}
}
首先是,ServiceA在spring_test包下面,ServiceB在spring_test.package1下面,ServiceC在spring_test.package1.inner_package1下面。但是,他们都在入口类App的所在包spring_test下面,所以他们都不需要任何的操作,只需要写个@Component注解,就能自动被添加到bean工厂里面。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
= { @Filter(
excludeFilters = FilterType.CUSTOM,
type = {TypeExcludeFilter.class}
classes ), @Filter(
= FilterType.CUSTOM,
type = {AutoConfigurationExcludeFilter.class}
classes )}
)
public @interface SpringBootApplication {
@AliasFor(
= EnableAutoConfiguration.class
annotation )
Class<?>[] exclude() default {};
@AliasFor(
= EnableAutoConfiguration.class
annotation )
String[] excludeName() default {};
@AliasFor(
= ComponentScan.class,
annotation = "basePackages"
attribute )
String[] scanBasePackages() default {};
@AliasFor(
= ComponentScan.class,
annotation = "basePackageClasses"
attribute )
Class<?>[] scanBasePackageClasses() default {};
}
这是因为@SpringBootApplication包含了@ComponentScan子注解,该注解会默认扫描当前类所在的包,将包里面所有的@Component的类都添加到bean工厂里面。
package spring_test2;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
@Component
public class ServiceD {
public void go(){
System.out.println("serviceD go");
}
}
对于serviceD类,因为它所在的包为spring_test2与入口类的包spring_test,不一样,也不是嵌套关系。所以,无法自动添加到bean工厂里面。我们可以通过在App类,加入@ComponentScan({“spring_test2”,“spring_test”})来扫描这两个包。
package spring_test3;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import spring_test4.ConfigureTest4;
/**
* Created by fish on 2021/3/15.
*/
@Configuration
@Import(ConfigureTest4.class)
@ComponentScan
public class ConfigureTest3 {
@Bean
public ServiceF getServiceF(){
return new ServiceF("[I am tip]");
}
@Bean
@ConditionalOnMissingBean(ServiceG.class)
public ServiceG getServiceG(){
return new ServiceGImpl1();
}
}
对于spring_test3的类型,我们是通过在入口类,使用@Import(ConfigureTest3.class)注解来添加进来的。注意,添加的仅仅只是ConfigureTest3这个配置类,spring_test3包的其他类型是无法自动添加进来的。因此,我们在ConfigureTest3这个配置类使用了@ComponentScan注解,它会自动扫描spring_test3所在的以及嵌套的所有类。
另外,注意,ConfigureTest3还以方法加@Bean注解的方式来添加Bean类型。这样,我们可以在不使用@Component的方式来注册bean,同时@Bean类型可以允许注册bean的构造函数的参数是什么,具体看getServiceF函数。
最后,这个ConfigureTest3还使用了@Import(ConfigureTest4.class)来进一步导入spring_test4包的配置类。
package spring_test4;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import spring_test3.ServiceG;
/**
* Created by fish on 2021/3/15.
*/
@Configuration
@ComponentScan
public class ConfigureTest4 {
@Bean
@ConditionalOnMissingBean(ServiceG.class)
public ServiceG getServiceG(){
return new ServiceGImpl2();
}
}
这是ConfigureTest4的配置类
package spring_test4;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import spring_test3.ServiceG;
/**
* Created by fish on 2021/3/15.
*/
public class ServiceGImpl2 implements ServiceG {
public void go(){
System.out.println("serviceG go impl2");
}
}
这是ServiceGImpl2的实现,注意,没有@Component的注解。
最终,我们在三个地方定义了ServiceG的实现类:
- spring_test3的包的ConfigureTest3的@Bean注解定义,有@ConditionalOnMissingBean注解
- spring_test3的嵌套包inner_package3的ConfigureTestInner3的@Bean注解定义,同样用@ConditionalOnMissingBean注解
- spring_test4的包的ConfigureTest4的@Bean注解定义,有@ConditionalOnMissingBean注解
那么,如果我自动注入@ServiceG接口的话,会用哪个实现类呢?答案是使用inner_package3的ConfigureTestInner3的@Bean注解定义。Bean定义的执行顺序为:
- 首先运行@ComponentScan扫描当前包的或者嵌套包的bean定义
- 然后运行@Import定义其他包里面的bean定义
- 最后运行自身配置类的@Bean定义
然后,Spring定义@ConditionalOnMissingBean定义的bean是一旦存在,后面的就不会重复定义这个bean。但是,如果这三个地方都是有@Bean定义,但是没有@ConditionalOnMissingBean注解时,Spring就会认为后面定义的bean就会覆盖前面的bean,最终造成:
- 三个地方都有@Bean注解和@ConditionalOnMissingBean注解时,优先级最高的是嵌套包inner_package3的bean定义。因为最先声明bean的,后面就会忽略。
- 三个地方都有@Bean注解,但都没有@ConditionalOnMissingBean注解时,优先级最高的是自身配置类spring_test3的bean定义,因为最后声明bean的,就会覆盖前面声明的bean。
4.2.3 配置文件
代码在这里
/**
* Created by fish on 2021/3/15.
*/
spring.profiles.active = production
study.testStr = Hello world
myapp.mail.host = smtp.163.com
myapp.mail.port = 8080
myapp.mail.user = fish
myapp.mail.password = 123
myapp.mail2.name = MMK
首先在resources文件夹加入以上的application.properties文件。
/**
* Created by fish on 2021/3/16.
*/
study.testStr = Hello world development
然后在resources文件夹加入以上的application-development.properties文件。
package spring_test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
@Component
public class ServiceA {
@Value("${study.testStr}")
private String testStr;
@Value("#{serviceB.host}")
private String emailHost;
public void go(){
System.out.println("servierA go : "+this.testStr);
System.out.println("serviceA get host : "+this.emailHost);
}
}
那么,我们可以用@Value注解来获取配置文件的值,@Value注解使用$符号时,使用的是配置文件项的值。而@Value直接使用#符号时,使用的是SpEL表达式计算的值,它可以用来获取其他bean,和类的属性和方法的值。
package spring_test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/3/15.
*/
//缺少Component注解的时候,无法注册BeanFactory,更无法注入属性到该类实例中
@Component
@ConfigurationProperties(prefix="myapp.mail")
public class ServiceB {
private String host;
public void setHost(String host){
this.host = host;
}
public String getHost(){
return this.host;
}
private int port;
public void setPort(int port){
this.port = port;
}
public int getPort(){
return this.port;
}
private String user;
public void setUser(String user){
this.user = user;
}
public String getUser(){
return this.user;
}
private String password;
public void setPassword(String passowrd){
this.password = passowrd;
}
public String getPassword(){
return this.password;
}
public void sendEmail(){
System.out.printf("host:%s,port:%d,user:%s,password:%s\n",host,port,user,password);
}
}
另外一种使用ConfigurationProperties加入prefix来将整个部分的属性配置文件项都写入到bean的依赖上面。注意,这种方法,必须要写setter方法才能实现。而SpEL表达式依赖getter方法来获取bean的属性值。
package spring_test;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* Created by fish on 2021/4/12.
*/
@ConfigurationProperties(prefix="myapp.mail2")
public class ServiceC {
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
我们也可以对ServiceC不使用@Component注解,同时使用ConfigurationProperties来配置属性。
@SpringBootApplication
@EnableConfigurationProperties(ServiceC.class)
public class App implements ApplicationRunner
{
public static void main( String[] args )
{
.run(App.class,args);
SpringApplication}
@Autowired
private ServiceA serviceA;
@Autowired
private ServiceB serviceB;
@Autowired
private ServiceC serviceC;
public void run(ApplicationArguments arguments) throws Exception{
this.serviceA.go();
this.serviceB.sendEmail();
System.out.printf("serviceC name: %s\n",serviceC.getName());
}
}
但是,要在入口或者配置类中,设置EnableConfigurationProperties,将ServiceC注入到beanFactory里面。
最后,profile可以在命令行参数中指定。–spring.profiles.active=development,你可以用这种方法来覆盖属性文件文件的其他项。
4.2.4 Import的使用
代码在这里
普通的Import使用,就是指定导入一个固定的Configuration文件
4.2.4.1 ImportSelector
ImportSelector的意义在于,可以可以根据注解的不同来选择导入一个不同的Configuration文件,
package spring_test;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* Created by fish on 2021/4/14.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ShowMsgSelector.class)
public @interface ShowMsg {
String value() default "";
}
定义一个ShowMsg注解,并且该注解定义导入一个ShowMsgSelector
package spring_test;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* Created by fish on 2021/4/14.
*/
public class ShowMsgSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata){
= AnnotationAttributes
AnnotationAttributes attributes .fromMap(importingClassMetadata.getAnnotationAttributes(
.class.getName(), false));
ShowMsg
String name = attributes.getString("value");
String[] result = {name+".MyConfiguration"};
return result;
}
}
ShowMsgSelector继承于ImportSelector,导入的时候,Spring会执行它的selectImports方法,importingClassMetadata就是使用@Import(ShowMsgSelector.class)注解所在类的元信息。从代码中我们可以看出,我们根据@ShowMsg的value值来导入指定的MyConfiguration配置文件。
//尝试将ShowMsg的value从spring_test2改为spring_test3
@ShowMsg("spring_test2")
public class App implements ApplicationRunner{
}
因此使用@ShowMsg就是导入spring_test2.MyConfiguration的配置文件,或者你可以改为导入spring_test3.MyConfiguration的配置文件
4.2.4.2 DeferredImportSelector
package spring_test;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* Created by fish on 2021/4/14.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ShowHelloSelector.class)
public @interface ShowHello {
String value() default "";
}
定义一个@ShowHello注解,依然导入ShowHelloSelector
package spring_test;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
/**
* Created by fish on 2021/4/14.
*/
//尝试将接口DeferredImportSelector改为ImportSelector
public class ShowHelloSelector implements DeferredImportSelector {
public String[] selectImports(AnnotationMetadata importingClassMetadata){
String[] result = {"spring_test4.MyConfiguration"};
return result;
}
}
ShowHelloSelector改为继承于DeferredImportSelector,而不是ImportSelector。它的区别在于,该selector的执行在配置类之后,而不是配置类之前。
@ShowHello
public class App implements ApplicationRunner{
}
因为@ShowHello定义在入口配置类App中,正常情况是先执行Import的ShowHelloSelector,再执行配置类App。但是因为ShowHelloSelector继承于DeferredImportSelector,所以会先执行入口配置类App,才执行ShowHelloSelector。
4.2.4.3 ImportBeanDefinitionRegistrar
package spring_test5;
import java.lang.annotation.*;
/**
* Created by fish on 2021/4/14.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyComponent {
}
定义一个@MyComponent注解
package spring_test5;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
/**
* Created by fish on 2021/4/14.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ImportMyComponentRegistrar.class)
public @interface EnableMyComponent {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
}
然后定义一个@EnableMyComponent开关的注解,它使用了@Import的ImportMyComponentRegistrar。
package spring_test5;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by fish on 2021/4/14.
*/
public class ImportMyComponentRegistrar implements
, ResourceLoaderAware {
ImportBeanDefinitionRegistrarprivate ResourceLoader resourceLoader;
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(
,
AnnotationMetadata importingClassMetadata) {
BeanDefinitionRegistry registry= AnnotationAttributes
AnnotationAttributes annotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(
.class.getName(), false));
EnableMyComponent
String[] basePackages = annotationAttributes.getStringArray("basePackages");
List<String> basePackageList = new ArrayList<String>(Arrays.asList(basePackages));
if( basePackages.length == 0){
//当没有输入包名的时候,就用注解所在类的包
try {
String annotationClass = importingClassMetadata.getClassName();
String importPackage = Class.forName(annotationClass).getPackage().getName();
.add(importPackage);
basePackageList}catch(Exception e){
.printStackTrace();
e}
}
System.out.println(basePackageList);
//开始扫描包并添加到工厂
= new ClassPathBeanDefinitionScanner(registry,false);
ClassPathBeanDefinitionScanner scanner .setResourceLoader(resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(MyComponent.class));
scanner.scan(basePackageList.toArray(new String[]{}));
scanner}
}
ImportMyComponentRegistrar实现了ImportBeanDefinitionRegistrar,然后Spring在Import的时候,就会执行它的registerBeanDefinitions方法。我们在里面使用自己的方法来做最彻底的动态bean注册。例如,我们在这里进行包扫描,将所有含有@MyComponent的类都加入到beanFactory里面。
@EnableMyComponent
public class App implements ApplicationRunner{
}
我们在入口类中使用EnableMyComponent就能打开开关,默认无传入basePackages参数的时候,就会扫描注解所在类App的所在的包
4.2.4.4 根据注解生成bean
package spring_test6;
import java.lang.annotation.*;
/**
* Created by fish on 2021/4/14.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRepository {
}
依然是先定义@MyRepository注解
package spring_test6;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AliasFor;
import java.lang.annotation.*;
/**
* Created by fish on 2021/4/14.
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ImportMyRepositoryRegistrar.class)
public @interface EnableMyRepository {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
}
设置一个EnableMyRepository注解的开关,使用ImportMyRepositoryRegistrar的Import
package spring_test6;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.AssignableTypeFilter;
import org.springframework.util.StringUtils;
import spring_test6.MyRepository;
import spring_test6.MyRepositoryFactory;
import spring_test6.MyRepositoryScanner;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* Created by fish on 2021/4/14.
*/
public class ImportMyRepositoryRegistrar implements
, ResourceLoaderAware {
ImportBeanDefinitionRegistrarprivate ResourceLoader resourceLoader;
private Logger logger = LoggerFactory.getLogger(getClass());
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@Override
public void registerBeanDefinitions(
,
AnnotationMetadata importingClassMetadata) {
BeanDefinitionRegistry registry= AnnotationAttributes
AnnotationAttributes annotationAttributes .fromMap(importingClassMetadata.getAnnotationAttributes(
.class.getName(), false));
EnableMyRepository
String[] basePackages = annotationAttributes.getStringArray("basePackages");
List<String> basePackageList = new ArrayList<String>(Arrays.asList(basePackages));
if( basePackageList.size() == 0){
//当没有输入包名的时候,就用注解所在类的包
try {
//以下这句用的是入口类的所在的包
//basePackageList = AutoConfigurationPackages.get(this.beanFactory);
String annotationClass = importingClassMetadata.getClassName();
String importPackage = Class.forName(annotationClass).getPackage().getName();
.add(importPackage);
basePackageList}catch(Exception e){
.printStackTrace();
e}
}
//开始扫描包并添加到工厂
.info("info ImportMyRepositoryRegistrar begin ... {}",basePackageList);
logger= new MyRepositoryScanner(registry);
MyRepositoryScanner scanner .setResourceLoader(resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(MyRepository.class));
scanner.scan(basePackageList.toArray(new String[]{}));
scanner}
}
ImportMyRepositoryRegistrar代码类似,但是使用了MyRepositoryScanner
package spring_test6;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import java.util.Arrays;
import java.util.Set;
/**
* Created by fish on 2021/4/14.
*/
public class MyRepositoryScanner extends ClassPathBeanDefinitionScanner {
private Logger logger = LoggerFactory.getLogger(getClass());
static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
public MyRepositoryScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
.warn("No MyRepository was found in {} package. Please check your configuration.",Arrays.toString(basePackages));
logger} else {
System.out.println(beanDefinitions);
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
;
AbstractBeanDefinition definition= getRegistry();
BeanDefinitionRegistry registry for (BeanDefinitionHolder holder : beanDefinitions) {
= (AbstractBeanDefinition) holder.getBeanDefinition();
definition String beanClassName = definition.getBeanClassName();
.debug( "Creating MapperFactoryBean with name {} and {} mapperInterface",holder.getBeanName() ,beanClassName);
logger
.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
definition.setBeanClass(MyRepositoryFactory.class);
definition//参考代码:https://github.com/mybatis/spring-boot-starter/issues/475
.setAttribute(FACTORY_BEAN_OBJECT_TYPE, beanClassName);
definition}
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
@Override
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
if (super.checkCandidate(beanName, beanDefinition)) {
return true;
} else {
.warn( "Skipping MapperFactoryBean with name {} and mapperInterface {}. Bean already defined with the same name!" , beanName , beanDefinition.getBeanClassName() );
loggerreturn false;
}
}
}
这个类继承于ClassPathBeanDefinitionScanner,我们重写了doScan,使得在扫描类以后,我们有机会可以更改bean的beanClass。因为,我们目标是扫描接口,而不是具体的类,所以,还要重写isCandidateComponent,使得可以接受接口上面的注解作为扫描的目标。注意processBeanDefinitions的代码,它指定的模板的参数,和beanClass为MyRepositoryFactory.
package spring_test6;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import spring_test.ServiceA;
import java.lang.reflect.Proxy;
/**
* Created by fish on 2021/4/14.
*/
public class MyRepositoryFactory<T> implements FactoryBean<T> ,BeanClassLoaderAware{
//可以正常使用Autowired
@Autowired
private ServiceA serviceA;
private ClassLoader classLoader;
private Class<T> mapperInterface;
public MyRepositoryFactory(Class<T> mapperInterface){
System.out.println("factory create");
this.mapperInterface = mapperInterface;
}
@Override
public void setBeanClassLoader(ClassLoader var1){
this.classLoader = var1;
}
@Nullable
public T getObject() throws Exception{
System.out.println("factory getObject");
.showMsg();
serviceA
= new MyRepositoryInvocationHandler();
MyRepositoryInvocationHandler handler Class<?>[] classes = new Class<?>[]{mapperInterface};
return (T)Proxy.newProxyInstance(this.classLoader,classes,handler);
}
@Nullable
public Class<?> getObjectType(){
return mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
}
MyRepositoryFactory就是一个模板,而且实现了FactoryBean。getObject的实现就是从接口生成的AOP类。
@EnableMyRepository
public class App implements ApplicationRunner{
}
我们在入口类使用一个注解@EnableMyRepository即可。
package spring_test;
import spring_test6.MyRepository;
/**
* Created by fish on 2021/4/14.
*/
@MyRepository
public interface RepositoryA {
void findById(int a);
}
那么这个包下,任意的含有@MyRepository注解的接口,都会有有一个默认的实现,相当方便。这个就是MyBatis和JPA,在只写接口的情况,Spring就会自动提供实现的原理了。
4.3 原理
4.3.1 自动注册ConfigurationClassPostProcessor
在SpringApplication里面,会固定注册一个ConfigurationClassPostProcessor。它是一个BeanFactoryPostProcessor,所以,在spring工厂启动以后,会回调它的postProcessBeanDefinitionRetistry方法。(详情看书本的P406页)
然后在postProcessBeanDefinitionRetistry里面,以入口函数SpringApplication.run(App.class,args)传入的App.class作为配置类,执行自动导入bean的流程。(详情看书本的P409页)
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
= { @Filter(
excludeFilters = FilterType.CUSTOM,
type = {TypeExcludeFilter.class}
classes ), @Filter(
= FilterType.CUSTOM,
type = {AutoConfigurationExcludeFilter.class}
classes )}
)
public @interface SpringBootApplication {
@AliasFor(
= EnableAutoConfiguration.class
annotation )
Class<?>[] exclude() default {};
@AliasFor(
= EnableAutoConfiguration.class
annotation )
String[] excludeName() default {};
@AliasFor(
= ComponentScan.class,
annotation = "basePackages"
attribute )
String[] scanBasePackages() default {};
@AliasFor(
= ComponentScan.class,
annotation = "basePackageClasses"
attribute )
Class<?>[] scanBasePackageClasses() default {};
}
对于入口类App.class,我们都会添加一个@SpringBootApplication的注解。首先,这个注解会继承@SpringBootConfiguration注解。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Configuration;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
@SpringBootConfiguration注解也是包含有@Configuration注解的,因此,@SpringBootApplication的注解包含了入口类也是配置类的意思。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.autoconfigure.AutoConfigurationImportSelector;
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
然后,@SpringBootApplication的注解包含了@EnableAutoConfiguration注解,该注解其实是一个@Import注解,它的类型AutoConfigurationImportSelector.class的作用是,自动导入所有依赖包里面,固定META-INF/spring.factories里面指定的bean。这是spring-starter在maven自动导入以后,会自动添加到Spring工厂的原因。
@ComponentScan(
= { @Filter(
excludeFilters = FilterType.CUSTOM,
type = {TypeExcludeFilter.class}
classes ), @Filter(
= FilterType.CUSTOM,
type = {AutoConfigurationExcludeFilter.class}
classes )}
)
最后,@SpringBootApplication也包含了ComponentScan注解,因此,入口类所在的包,以及它嵌套的包的bean都会被自动扫描后,添加到Spring工厂里面。
4.3.2 配置类的解析
我们谈到了在postProcessBeanDefinitionRetistry里面,会解析以入口类(例如是App.class)作为配置类的配置。它最终会调用到一个关键的doProcessCofingurationClass函数里面。
//P410
protected final SourceClass doProcessConfigureationClass(ConfiguationClass configClass, SourceClass sourceClass){
//Process Any @PropertySource annotations
//Process any @ComponentScan annotations
//Process any @Import annotations
//Process any @ImportResource annotations
//Process @Bean methods
}
从这个代码中就可以看出,在配置类的解析时,按照以下顺序执行
- 首先,深度优先遍历该配置类的当前包和其他包,的其他配置类或者bean
- 然后,使用@Import指定来导入其他包的bean
- 最后,执行本配置类的@Bean方法来注册bean
显然,这个顺序,和我们之前在4.2.2讨论的顺序一致。
//P404
public String[] selectImports(AnnotationMetadata annotationMetadata){
return ....
}
在使用Import导入其他配置类时,如果该类实现了org.springframework.context.annotation.ImportSelector接口。那么,导入的类就不看成是配置类,而是执行该类的selectImports方法来获取具体的bean的类名称作为导入。对于在@SpringBootApplication注解类中,包含的@Import({AutoConfigurationImportSelector.class})注解中,AutoConfigurationImportSelector就是这样的一个类。它实现了ImportSelector接口,然后它就能在selectImports里面扫描固定路径的资源文件,来获取各个starter的类添加到bean工厂里面。
Spring Boot的这个思路相当巧妙,使用约定的方法来导入bean,避免了繁琐的依赖包bean的添加工作。
//P419
void processConfigurationClass(ConfigationClass configClass){
///
}
最后,如果配置类自身是有@Conditional配置的,那么它就会在解析前,先执行一次@Conditional测试,只有条件配置测试通过以后,才能导入该配置类的信息。这就是Spring Boot的条件配置bean的实现原理了。
5 事务
5.1 事务
Spring提供了以注解的方式使用事务,实现了
- 自动提交回滚
- 方便指定数据库的隔离模式,回滚的异常模式
- 指定事务嵌套的传递方式(这点重要)
5.2 例子
5.2.1 基础选项
代码在这里
dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<dependency>
</dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<dependency> </
依赖里面要打开aop和jdbc选项
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class App implements ApplicationRunner
{
public static void main( String[] args )
{
.run(App.class,args);
SpringApplication}
}
在启动类里面,打开AOP的exposeProxy选项,因为Spring的事务依赖于AOP的实现,不打开的话,会出现无法在当前bean中获取代理对象的问题。
//数据被回滚了
@Transactional
public void addOneAndHaveRuntimeException(){
= new User();
User user .setName("cat");
user.setAge(700);
user.save(user);
userService
throw new RuntimeException("Throw by me");
}
简单的用法,有RuntimeException,所以数据被回滚了
//数据没有被回滚,因为Transaction标签默认只捕捉RunTimeException
@Transactional
public void addOneAndHaveNormalException()throws Exception{
= new User();
User user .setName("cat");
user.setAge(700);
user.save(user);
userService
throw new Exception("Throw by me2");
}
数据没有被回滚,因为Spring事务默认只回滚RuntimeException异常。
//数据被回滚了,因为Transaction标签指定了遇到Exception的时候都要回滚
@Transactional(rollbackFor=Exception.class)
public void addOneAndHaveNormalExceptionAndHaveRollBackForLabel()throws Exception{
= new User();
User user .setName("cat");
user.setAge(700);
user.save(user);
userService
throw new Exception("Throw by me2");
}
我们可以用rollbackFor标签,来要求事务遇到Exception异常的时候也要回滚
//直接使用this来调用自身的其他方法,会绕过AOP实现,导致事务注解没有开启
public void addOneWithThis(){
this.addOneAndHaveRuntimeException();
}
//应该AopContext来调用自身的其他方法,事务注解依然会开启
public void addOneWithAopContextThis()throws Exception{
((UserAo)AopContext.currentProxy()).addOneAndHaveRuntimeException();
}
注意,事务依赖于Spring的AOP的实现,在3.2.2节我们提到了,在当前bean类的方法中,要用AopContext.currentProxy()才能获取到真正的代理对象。
//对于readOnly为true,在应用层层面,会禁止修改操作,并且去掉脏数据检查.在数据库层面,会避免数据上锁.
@Transactional(readOnly = true)
public void addOneWithReadOnly(){
= new User();
User user .setName("cat");
user.setAge(700);
user.save(user);
userService}
我们可以设置readOnly为true,打开只读模式。这会产生优化,会禁止写入操作。
//可以指定数据库的隔离级别,这个需要在并发环境下才能测试到不同的地方
@Transactional(isolation = Isolation.READ_COMMITTED)
public void addOneWithIsolation(){
this.addOneAndHaveRuntimeException();
}
可以方便地指定数据库的隔离级别。
5.2.2 事务同步管理器
代码在这里
事务同步管理器十分方便地设置了,在一个事务里面,当事务提交或者回滚前后的通知,以及当前事务的信息。
//数据正常提交
@Transactional()
public void addOne(){
this.showTransactionInfo();
= new User();
User user .setName("cat");
user.setAge(700);
user.save(user);
userService
this.addTransactionNotify();
}
private void showTransactionInfo(){
String transactionName = TransactionSynchronizationManager.getCurrentTransactionName();
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
Integer isolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
System.out.printf("transactionName:%s,isReadOnly:%s,isolationLevel:%s\n",transactionName,isReadOnly,isolationLevel);
}
private void addTransactionNotify(){
.registerSynchronization(new TransactionSynchronizationAdapter(){
TransactionSynchronizationManager
//只有可以commit才会回调,在提交前回调
@Override
public void beforeCommit(boolean readOnly) {
System.out.printf("before Commit,isReadOnly :%s\n",readOnly);
}
//只有可以commit才会回调,在提交后回调
@Override
public void afterCommit() {
System.out.println("afterCommit");
}
//总是回调,没有参数,在完成前回调
@Override
public void beforeCompletion() {
System.out.println("beforeCompletion");
}
//总是回调,status为0是提交成功,status为1是提交失败,在完成后回调
@Override
public void afterCompletion(int status) {
System.out.printf("afterCompletion status:%s\n",status);
}
});
}
一个简单的事务同步管理器的使用,它就是一个简单的线程级变量的实现。
[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7]]
:spring_test.UserAo.addOne,isReadOnly:false,isolationLevel:null
transactionName,isReadOnly :false
before Commit
beforeCompletion
afterCommit:0
afterCompletion status[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700]]
这是输出结果
//数据有异常,被回滚
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void addOneAndHaveRuntimeException(){
this.showTransactionInfo();
= new User();
User user .setName("cat");
user.setAge(700);
user.save(user);
userService
this.addTransactionNotify();
throw new RuntimeException("Throw by me");
}
我们看一下有异常需要回滚的时候,会产生什么输出。
[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700]]
:spring_test.UserAo.addOneAndHaveRuntimeException,isReadOnly:false,isolationLevel:4
transactionName
beforeCompletion:1
afterCompletion status[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700]]
没有了beforeCommit和afterCommit的通知了,而且afterCompletion的status变量也不同。
//没有使用同步管理器,可以看到不会有输出.同步管理器只在当前的事务里面是单次有效的.
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void addOneAndNoSyncTransaction(){
= new User();
User user .setName("cat");
user.setAge(700);
user.save(user);
userService}
最后,如果在一个事务中没有调用事务同步管理器,就什么输出也没有。
[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700]]
[User[id:1,name:cat,age:6], User[id:2,name:fish,age:7], User[id:23,name:cat,age:700], User[id:25,name:cat,age:700]]
这是输出
5.2.3 事务嵌套
代码在这里
如果有一段过程中经过了多个有事务注解的方法,就称为事务嵌套。这个问题很容易就会踩坑,要十分注解。在实际开发中,尽可能避免使用事务嵌套。
//默认的事务传播为REQUIRED,执行成功
@Transactional(propagation = Propagation.REQUIRED)
public void mod1_AgeOne(){
= new User();
User user .setId(1);
user.setAge(1);
user.mod(user);
userService}
这是默认的没有嵌套的情况
//默认的事务传播为REQUIRED,执行失败
@Transactional(propagation = Propagation.REQUIRED)
public void mod2_AgeTwo(){
= new User();
User user .setId(1);
user.setAge(2);
user((UserAo)AopContext.currentProxy()).mod2_AgeTwo_inner();
}
//默认的事务传播为REQUIRED,当已经有事务的时候,就沿用原来的事务,没有的话就重新开事务
@Transactional(propagation = Propagation.REQUIRED)
public void mod2_AgeTwo_inner(){
= new User();
User user .setId(1);
user.setAge(3);
user.mod(user);
userService
throw new RuntimeException("throw by me");
}
这是mod2中嵌套了mod2_inner方法的情况,由于使用了Propagation.REQUIRED配置,所以,mod2_inner方法会沿用mod2的同一个事务。
//默认的事务传播为REQUIRED,
@Transactional(propagation = Propagation.REQUIRED)
public void mod3_AgeThree(){
= new User();
User user .setId(1);
user.setAge(3);
user//错误用法,因为mod3_AgeThree_inner沿用已经同一个事务,已经捕捉到了RunTimeException,该事务被标注了回滚状态,但是依然企图对外部事务执行commit操作
try {
((UserAo) AopContext.currentProxy()).mod3_AgeThree_inner();
}catch(Exception e){
}
}
@Transactional(propagation = Propagation.REQUIRED)
public void mod3_AgeThree_inner(){
= new User();
User user .setId(1);
user.setAge(3);
user.mod(user);
userService
throw new RuntimeException("throw by me");
}
这是一个经常犯错的例子,因为mod3_inner使用了Propagation.REQUIRED,所以,它会沿用mod3的同一个事务。但是,在mod3_inner中已经抛出了RuntimeException异常,所以,被Spring事务标注了这个事务为回滚状态。但是,在mod3强行按住了异常不放出来,所以mod3就会发出警报,因为该事务已经标注了回滚状态,但是没有收到异常。
.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
org.springframework.transaction.support.AbstractPlatformTransactionManager.processRollback(AbstractPlatformTransactionManager.java:873)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:710)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:532)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at org.UserAo$$EnhancerBySpringCGLIB$$928f914d.mod3_AgeThree(<generated>)
at spring_test.App.test3(App.java:85) at spring_test
这个是上述错误的时候,产生的日志,网上一大堆这类犯错的。
@Transactional(propagation = Propagation.REQUIRED)
public void mod4_AgeFour(){
= new User();
User user .setId(1);
user.setAge(4);
user.mod(user);
userService
//正确用法,因为mod4_AgeThree_inner总是新建一个事务,所以它的事务是否回滚与外部事务没有关系
try {
((UserAo) AopContext.currentProxy()).mod4_AgeFive_inner();
}catch(Exception e){
}
}
//使用REQUIRES_NEW的事务传播,表达总是使用新事务来执行代码,当异常发生的时候会触发回滚
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void mod4_AgeFive_inner(){
= new User();
User user //当这里的id改为1的时候就会产生死锁。
.setId(2);
user.setAge(5);
user.mod(user);
userService
throw new RuntimeException("throw by me");
}
正确的做法是,让mod4_inner使用REQUIRES_NEW的事务传播模式,而不是REQUIRED的事务传播模式。它的含义是,mod4总是自己新建一个事务来执行代码,并挂起原来的事务。所以,当mod4的事务收到异常要回滚的时候,它并不影响外部事务是否回滚。
但是,这个方法会产生新的问题!因为,mod4是处于事务执行状态的,同时它又在等待mod4_inner方法的执行完成。所以,如果mod4_inner在修改mod4已有的数据行的时候,它就会在数据库层面等待mod4数据的释放。这显然,产生了死锁。而且这种死锁十分隐蔽,mod4对mod4_inner的依赖是应用层面的,而mod4_inner对mod4的依赖是数据库层面的,因此这类死锁数据库是不会报错的!它只会在等待锁超时后发出警告而已。
//默认的事务传播为REQUIRED,
@Transactional(propagation = Propagation.REQUIRED)
public void mod5_AgeSix(){
= new User();
User user .setId(1);
user.setAge(6);
user.mod(user);
userService
//正确用法,因为mod4_AgeThree_inner总是新建一个事务,所以它的事务是否回滚与外部事务没有关系
try {
((UserAo) AopContext.currentProxy()).mod5_AgeSeven_inner();
}catch(Exception e){
}
}
//使用NOT_SUPPORTED的事务传播,总是用无事务状态来执行操作,当异常发生的时候没有回滚
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void mod5_AgeSeven_inner(){
= new User();
User user .setId(2);
user.setAge(7);
user.mod(user);
userService
throw new RuntimeException("throw by me");
}
最后,我们看一个有用的NOT_SUPPORTED的事务传播模式,它的意思是,mod5_inner总是使用无事务的状态来执行代码,并将原来的事务挂起。
5.3 原理
在AOP原理的一节中,我们看到,Spring的AOP会自动添加所有满足Advisor接口的bean作为增强器,所以依赖于AOP的事务实现,也会添加自己的Advisor。
这个就是TransactionAttributeSourceAdvisor(P272页),然后该Advisor会使用TransactionAttributeSourcePointcut(P273页)来查找该类是否有@Transactional注解来决定是否开启事务增强代理。最后,对于有@Transactional注解的方法,会使用TransactionInterceptor(P277页)来执行代理。
所以,原理关键在TransactionInterceptor的实现,具体可以看一下书本,难度不高。主要是事务嵌套那里的实现要特别注意各个之间有什么不同。
6 总结
第一次这么系统地看一个大框架的代码,收获良多,简直是Java代码的典范。在开发上,我收获到了:
- 输入对象的接口抽象,像Spring对于不同资源的抽象,P27页。
- 实现过程中,使用接口来解耦对不同具体对象的实现,像AOP中对不同Advisor的抽象,P190页
- 使用接口作为需要不同功能的开关。像Aware接口,实现了不同的Aware接口相当于指定对当前bean打开不同的功能开关。
- 逐文件作为类隔离局部变量
- 并发控制,两次检查缓存。在获取单例上,首先有一次无锁地检查一次缓存(P94页)。当检查到没有数据以后,依然需要在有锁的状态下检查一次缓存(P98页)。这样是为了考虑并发环境下,单例可能造成创建多次的问题,这个知识点值得注意。
- 在循环状态下捕捉异常,并重试的逻辑。如果在循环中不需要重试的话,就直接抛出异常的就好了。但是在循环中需要重试的话,你还需要将之前的异常都保存起来,形成异常链,这样返回给调用端的错误提示才是完整的。具体看P115页。
- 缓存中其实空有两个意思,这个key还没有开始查缓存,和,这个key查了缓存以后依然永久为空。这点也很漂亮,Spring中使用null和NULL_OBJECT来区分。P94页。
- 面向对象是自底向上的思维,接口让不同差异的实现归入到同一个接口上。
参考资料:
- 本文作者: fishedee
- 版权声明: 本博客所有文章均采用 CC BY-NC-SA 3.0 CN 许可协议,转载必须注明出处!