Spring注解驅動開發
- 1 IOC容器
-
- 1.1 元件注冊
-
- @Configuration與@Bean注解
- @ComponentScan
- @Filter注解
- @Scope(“prototype”)
- @Lazy 懶加載:
- @Conditional
- @Import
- FactoryBean
- 總結注冊元件的方式
- 1.2 Bean的生命周期
-
- 1.2.1 Bean生命周期的定義
- 1.2.2 Bean生命周期流程
- 1.2.3 操控Bean生命周期各個階段的方法
- 1.2.4 BeanPostProcessor的工作原理
- 1.2.5 Spring底層對于BeanPostProcessor的應用
- 1.3 Bean屬性指派
-
- 1.3.1 @Value
- 1.4 自動裝配
-
- 1.4.1 @Autowire自動注入
- 1.4.2 @Autowire使用位置
- 1.4.3 Aware接口
- 1.4.4 @Profile
- 2 AOP(面向切面程式設計)
-
- 2.1 功能測試
- 2.2 AOP原理探究
-
- 2.2.1 @EnableAspectJAutoProxy注解定義
- 2.2.2 AnnotationAwareAspectJAutoProxyCreator類的作用
- 3 聲明式事務
-
- 3.1 @Transactional注解
- 3.2 事務的傳播機制
- 4 拓展原理
-
- 4.1 BeanFactoryPostProcessor
- 4.2 BeanDefinitionRegistryPostProcessor
- 4.3 ApplicationListener
- 4.4 @EventListener
- 5 Spring容器的啟動流程
1 IOC容器
1.1 元件注冊
@Configuration與@Bean注解
與以往使用xml作為spring的配置檔案不同,現在我們可以直接手寫一個配置類來為spring容器注入元件,具體使用方法如下:
//首先我們需要手寫一個配置類,這個類就相當于我們之前的xml檔案
//@Configration注解就标注了這個類是一個配置類
@Configuration
public class MyConfiguration{
//使用@Bean注解來為容器中注入元件,括号中的字元串為注入元件的id
@Bean("person")
public Person person(){
return new Peson();
}
}
//開啟基于注解的ioc容器的方法,使用AnnotationConfigApplicationContext
public class Mytest {
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
}
}
@ComponentScan
掃描特定包下的所有元件,可以配置一定的過濾規則,指定掃描哪些元件或者指定排除哪些元件,通過@Filter注解來實作,可選的屬性如下:
- value:指定要掃描的包
- excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}}:過濾規則,排除被@Controller和@Service标注的類
- includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class, Service.class}}:掃描的時候隻包含哪些元件,隻掃描被@Controller和@Service标注的類
- useDefaultFilters = false : 不使用預設的掃描政策,在自定義掃描政策的時候,需要将此屬性置為false
//掃描com.huhx.java包下的所有元件(被@Component、@Controller、@Service、@Repository标注的)
@ComponentScan(value = "com.huhx.java")
@Configuration
public class MyConfiguration{
//使用@Bean注解來為容器中注入元件,括号中的字元串為注入元件的id
@Bean("person")
public Person person(){
return new Peson();
}
}
@Filter注解
type屬性的可選值:
- FilterType.ANNOTATION:按照注解過濾
- FilterType.ASSIGNABLE_TYPE:按照給定的類型過濾
- FilterType.ASPECTJ:使用ASPECTJ表達式過濾(不常用)
- FilterType.REGEX:使用正規表達式過濾(不常用)
-
FilterType.CUSTOM:使用自定義規則過濾(重點)
自定義過濾規則的方法:
- 自己實作一個類去實作TypeFilter接口
public class MyTypeFilter implements TypeFilter{ /** * metadataReader:讀取到的目前正在掃描的類的資訊 * metadataReaderFactory:可以擷取到其他任何類資訊 */ @Override public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException { //擷取目前類注解的資訊 AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata(); //擷取目前正在掃描的類的類資訊 ClassMetadata classMetadata = metadataReader.getClassMetadata(); //擷取目前類資源(如類的路徑) Resource resource = metadataReader.getResource(); //去掃描類名中包含“er”的類進ioc容器 String className = classMetadata.getClassName(); if(className.contains("er")){ return true; } return false; }
- 然後去在@ComponentScan中去配置上該過濾規則
- 自己實作一個類去實作TypeFilter接口
@Scope(“prototype”)
ioc中的元件預設是單執行個體的,使用@Scope可以用來改變bean的作用域,我們可以配置成其他的值
1、ConfigurableBeanFactory#SCOPE_PROTOTYPE(prototype 多執行個體)
ioc容器啟動時并不會調用方法建立對象放到ioc容器中,而是在第一次使用bean的時候完成初始化并将對象放入ioc容器
@Configuration
public class MyConfiguration{
@Scope("prototype")
@Bean("person")
public Person person(){
return new Peson();
}
}
2、ConfigurableBeanFactory#SCOPE_SINGLETON(singleton 單執行個體-預設)
ioc容器啟動時會調用方法建立對象放到ioc容器中
@Lazy 懶加載:
單執行個體bean,預設在容器啟動時建立對象,使用懶加載模式,容器在啟動時不會建立對象,而是在第一次使用(擷取Bean)時建立對象并初始化。
@Configuration
public class MyConfiguration{
@Lazy
@Bean("person")
public Person person(){
return new Peson();
}
}
@Conditional
SpringBoot底層大量使用的注解,可以按照一定的條件進行判斷,滿足條件給容器中注冊bean
使用方式:
這個注解可以加在類上,也可以加在方法上,如果該條件注解放在類上,則滿足目前條件,這個類中配置的所有bean都會生效,如果放在方法上,則對單獨的類的裝載起到限定作用,SpringBoot中已經提供了很多條件類,同時我們也可以自己實作自定義的條件類,我們需要定義一個條件類并實作Condition接口:
public class MyCondition implement Condition{
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){
//能擷取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//能擷取類加載器
ClassLoader classLoader = context.getClassLoader();
//擷取目前環境資訊
Environment environment = context.getEnvironment();
//擷取到bean定義的注冊類
BeanDefinitionRegistry registy = context.getBeanDefinitionRegistry();
/**
* 在這裡可以用上面擷取到的beanFactory、classLoader、environment、registy來配合實作自己的判斷邏輯
*/
return false;
}
}
定義好了我們的條件類之後,在主配置類注入bean時加上@Conditional注解,就可以按照條件注入了:
@Configuration
public class MyConfiguration{
@Bean("person")
@Conditional(MyCondition.class)
public Person person(){
return new Peson();
}
}
@Import
- 直接給容器中導入一個元件
@Configuration @Import({Color.class, Person.class}) public class MyConfiguration{ }
- 給容器中導入一個MyImportSelector,該Selector是一個實作了ImportSelector接口的類
@Configuration @Import(MyImportSelector.class) public class MyConfiguration{ } public class MyImportSelector implements ImportSelector { //重寫該方法,傳回需要導入到容器中的元件的全類名數組 @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { return new String[0]; } }
- 給容器中導入一個MyImportBeanRegistry,該類是一個實作了ImportBeanDefinitionRegistrar接口的類,可以實作手動向容器中注冊一個Bean
@Configuration @Import(MyImportBeanRegistry.class) public class MyConfiguration{ } public class MyImportBeanRegistry implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }
FactoryBean
建立一個Spring定義的FactoryBean,并将其加入到容器中
@Configuration
public class MyConfiguration{
@Bean
public UserFactoryBean userFactoryBean(){
return new UserFactoryBean();
}
}
public class UserFactoryBean implements FactoryBean<User> {
//傳回一個User對象,該對象會添加到容器中
@Override
public User getObject() throws Exception {
return null;
}
//傳回對象的類型
@Override
public Class<?> getObjectType() {
return null;
}
}
雖然向容器中添加的是工廠bean(UserFactoryBean),但是實際上調用getObject方法時得到的是User對象,如果想要拿到工廠bean本身,需要在getObject()方法中傳入id時加上‘&’字首,如getObject(‘&userFactoryBean’)
總結注冊元件的方式
- @ComponentScan包掃描+元件标注注解(@Controller/@Service/@Repository/@Component)【主要用于我們自己寫的類】
- @Bean【導入的第三方包裡面的元件】
- @Import(MyClass.class)【快速給容器中導入一個元件】
- 使用方式:@Import(Color.class, Person.class),給容器中導入Color類和Person類
- 導入元件的名稱預設為其全限定類名
- @Import(MyImportSelector.class)
- 使用方式:定義一個類MyImportSelector去實作ImportSelector接口,重寫其中的方法
- 這種方式傳回需要導入的元件的全類名的數組,然後會根據傳回的數組中的類名将類加載至ioc容器
- @Import(MyImportBeanDefinitionRegistrar.class)
- 使用方式:定義一個類MyImportBeanDefinitionRegistrar去實作ImportBeanDefinitionRegistrar接口,重寫其中的方法
- 這種方式是直接向ioc容器中注入元件,需要我們自己定義BeanDefination,并使用BeanDefinationRegistrar向ioc的bean系統資料庫裡直接添加bean元件
- 使用Spring提供的FactoryBean(工廠Bean)
- 使用方式:定義一個類如ColorFactoryBean去實作FactoryBean,重寫其中的方法,主要有getObject、getObjectType、isSingleton(如果要建立單例傳回true、多例的傳回false)
- 需要将我們定義的工廠類ColorFactoryBean加入到ioc容器中,在主配置類裡去配置
- 雖然我們注冊的是ColorFactoryBean,但是我們通過容器去擷取ColorFactoryBean這個Bean的時候,真實傳回的卻是Color類,我們如果想要擷取工廠bean本身,需要通過application.getBean("&ColorFactoryBean")來擷取,在Bean的id前方加上“&”字首
1.2 Bean的生命周期
1.2.1 Bean生命周期的定義
Bean的生命周期指的是bean建立——>初始化——>銷毀的過程
Spring容器負責管理bean的生命周期
我們可以自定義初始化和銷毀方法,容器在bean進行到目前生命周期的時候來調用我們自定義的初始化和銷毀方法
這裡說的初始化方法并不是平時我們所說的類初始化,這個初始化方法是在類的構造器函數調用之後才會執行的。
1.2.2 Bean生命周期流程
1、構造(對象建立,調用構造函數)
- 單執行個體:在容器啟動的時候建立對象
- 多執行個體:在每次擷取的時候建立對象
2、BeanPostProcessor.postProcessBeforeInitialization()方法調用
3、初始化:對象建立完成,并指派好,調用自定義初始化方法
4、BeanPostProcessor.postProcessAfterInitialization()方法調用
5、銷毀:
- 單執行個體:容器關閉的時候銷毀bean
- 多執行個體:容器不會管理這個bean,容器不會調用銷毀方法
//bean生命周期展示
cat....建立 //Bean的構造器執行,先建立對象
[email protected] //BeanPostProcessor.postProcessBeforeInitialization()方法調用
cat...initialization //執行自定義的初始化邏輯
[email protected]//BeanPostProcessor.postProcessAfterInitialization()
容器建立完成
cat....destory //容器銷毀時調用自定義的銷毀方法
1.2.3 操控Bean生命周期各個階段的方法
1、指定初始化和銷毀方法:
- 在需要注入的類中加入相應的初始化和銷毀方法,然後在主配置類中進行配置時,在@Bean注解中指定相應的初始化方法和銷毀方法
public class Car{ Car(){} public void init(){} //這是我們自定義的初始化方法 public void destroy(){}//這是我們自定義的銷毀方法 } @Configuration public class MyConfiguration{ @Bean(initMethod = "init", destroyMethod = "destroy") public Person Car(){ return new Car(); } }
- 通過讓Bean實作InitializingBean接口(定義初始化邏輯),實作DisposableBean接口(定義銷毀邏輯)并重寫裡邊的方法
public class Cat implements InitializingBean, DisposableBean { @Override public void destroy() throws Exception { System.out.println("定義你的銷毀邏輯"); } @Override public void afterPropertiesSet() throws Exception { System.out.println("定義你的初始化邏輯"); } } @Configuration public class MyConfiguration{ @Bean("cat") public Person Cat(){ return new Cat(); } }
- 可以使用JSR250提供的注解
- @PostConstruct,在bean建立完成并且屬性指派完成,來執行初始化方法
- @PreDestroy,在bean将要被移除銷毀之前通知我們進行清理工作
public class Car{ Car(){} @PostConstruct public void init(){} @PreDestroy public void destroy(){} }
2、指定Bean後置處理器的方法
- postProcessBeforeInitialization():在bean初始化方法(不是類的初始化)之前調用該方法
- postProcessAfterInitialization():在bean完成初始化方法(不是類的初始化)之後調用該方法
@Component public class MyBeanProcessor implements BeanPostProcessor { /** * @param bean : the new bean instance * @param beanName :the name of the bean * @return * @throws BeansException */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("定義初始化方法調用之前的處理邏輯"); return null; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("定義初始化方法調用之後的處理邏輯"); return null; } }
1.2.4 BeanPostProcessor的工作原理
- 在所有初始化工作開始之前完成對bean的屬性指派(完成bean的建立)populateBean(beanName, mbd, instanceWrapper)
- 開始bean的初始化工作:initializeBean(beanName, exposedObject, mbd)
- applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessBeforeInitialization(result, beanName); if (current == null) { return result; } result = current; }
- invokeInitMethods(beanName, wrappedBean, mbd):自己定義的初始化方法
- applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
for (BeanPostProcessor processor : getBeanPostProcessors()) { Object current = processor.postProcessAfterInitialization(result, beanName); if (current == null) { return result; } result = current; }
- applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
1.2.5 Spring底層對于BeanPostProcessor的應用
Spring底層關于Bean的指派、注入其他元件、@Autowired,生命周期注解功能、@Async等等功能都使用到了BeanPostProcessor,我們去看一下BeanPostProcessor接口的實作類

可以舉例研究幾個,例如ApplicationContextAwareProcessor,他的postProcessBeforeInitialization方法如下所示:
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
//這裡首先判斷下bean屬不屬于xxxAware類型的,如果不屬于就直接傳回了
if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware)){
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;
}
主要邏輯代碼在invokeAwareInterfaces()方法中,如下:
就是判斷bean屬于哪個xxxAware類型,然後給這個bean注入相應的依賴,例如可以給bean傳入目前的ioc容器,傳入目前的Environment等等…
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 ApplicationContextAware) {
((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
}
}
我們的bean要想使用這個類提供的前置處理來為自己注入ioc容器的依賴,隻需要讓我們的bean去實作ApplicationContextAware接口,然後重寫其中的方法,比如我們想要為我們的bean注入目前的ioc容器,可以如下操作
@Component
public class Cat implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
其餘各種大體的研究方式都差不多,有興趣可以自己多去研究幾個。
1.3 Bean屬性指派
在我們自己定義的bean中,如果我們不給類的屬性指派的話,一般來說Spring通過類的無參構造器建立出來的對象,是具有預設值的(引用類型為null,基本類型為基本類型的預設值)
```java
//定義一個元件,重寫toString方法,以便後續檢視屬性指派的情況
@Component
public class Cat {
private String name;
private String master;
private int age;
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", master='" + master + '\'' +
", age=" + age +
'}';
}
}
//啟動容器之後,我們來列印測試一下,spring預設建立出來的對象其屬性的預設值是什麼
public class Mytest {
@Test
public void test(){
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
System.out.println("容器建立完成");
Cat cat = (Cat) context.getBean("cat");
System.out.println(cat);
context.close();
}
}
上面代碼列印情況如下:
容器建立完成
Cat{name='null', master='null', age=0}
可以看到,引用類型預設指派為null,普通類型預設指派為其規定的預設值
1.3.1 @Value
@Value("mike")
private String name;
@Value("huhx")
private String master;
@Value("#{30 - 6}")
private int age;
列印結果為:
Cat{name='mike', master='huhx', age=24}
- @Value(“張三”) 使用基本内容
- @Value(#{20 - 8}) 使用使用EL表達式傳入值,格式==#{}==
- @Value(${person.name}) 傳入配置檔案中配置的值【properties檔案】
- 在主配置類上使用一個注解來導入配置檔案@PropertySource(value = {“classpath:/xxx.properties”}
1.4 自動裝配
Spring利用依賴注入完成對IOC容器中各個元件的依賴關系指派
1.4.1 @Autowire自動注入
- 預設優先按照類型去容器中找對應的元件
- 如果找到多個相同類型的元件,再将屬性的名稱作為元件的id去容器中查找
- @Qualifier:使用該注解指定需要裝配的元件的id,而不是使用屬性名
- @Primary,讓Spring進行自動裝配的時候,預設使用首選的bean進行裝配,同時也可以進一步使用@Qualifier來限定裝配bean的名稱
@Service
public class BookService {
@Autowire
private BookDao bookDao;
}
@Repository
public class BookDao {
}
1.4.2 @Autowire使用位置
-
構造器
如果隻有一個有參構造器,這個有參構造器的@Autowire可以省略,參數為止的元件還是可以從容器中擷取
@Bean标注的方法建立對象的時候,方法參數的值從容器中直接擷取,不需要标注@Autowire
@Component public class Boss { private Car car; @Autowire public Boss(Car car) { this.car = car; } public Car getCar() { return car; } public void setCar(Car car) { this.car = car; } }
- 方法
@Component public class Boss { private Car car; public Boss(Car car) { this.car = car; } public Car getCar() { return car; } @Autowire public void setCar(Car car) { this.car = car; } }
- 參數
1.4.3 Aware接口
當我們注入容器中的類想要擷取ioc容器的執行個體對象、擷取運作時環境等等參數的時候,Spring也提供了Aware接口來使bean可以實時擷取到自己想要的内容。
xxxAware,他們各自的功能是通過xxxProcessor來實作的
1.4.4 @Profile
Profile:Spring為我們提供的可以根據目前環境,動态的激活和切換一系列元件的功能。
開發環境、測試環境、生産環境中的元件是不太相同的,我們希望在不作大量改動的情況下來實作動态切換。
@Profile:指定元件在哪個環境的情況下才能被注冊到容器中,預設是default環境。
@Configuration
public class MyConfiguration{
@Bean
@Profile("test") //生産環境辨別
public Person person(){
return new Peson();
}
}
@Profile還可以寫在配置類上,隻有是在指定的環境時候,整個配置類裡面的所有的配置才能開始生效
沒有标注環境辨別的bean,在認識環境下都是加載的
Question:如何來切換生産環境?
- 通過代碼
//1.建立一個applicationcontext //2.設定需要激活的環境 applicationContext.getEnvironment().setActiveProfiles("test"); //3.注冊主配置類 applicationContext.register(MainConfig.class); //4.啟動重新整理容器 applicationContext.refresh();
- 使用指令行動态參數,在虛拟機參數位置加載
-Dspring.profiles.active=test
2 AOP(面向切面程式設計)
AOP:指的是在程式運作期間動态的将某段代碼切入到指定方法指定位置進行運作的程式設計方式
2.1 功能測試
- 導入aop子產品:Spring AOP,(spring-aspects)
- 定義一個業務邏輯類,假設我們現在要在在業務邏輯運作的時候實作将日志進行列印(方法之前,方法運作之後……)
public class MathCalculator { public int div(int i, int j) { System.out.println("div方法執行,參數為:" + i + "," + j); return i / j; } }
- 定義一個日志切面類,切面類中的方法需要動态感覺到業務類中的方法執行的不同階段,并在不同階段進行代碼切入
@Aspect public class LogAspectJ { @Pointcut("execution(public int com.huhx.blog.other.MathCalculator.*(..))") public void pointCut() {} @Before("pointCut()") public void logStart() { System.out.println("方法執行前"); } @After("pointCut()") public void logEnd() { System.out.println("方法執行後"); } @AfterReturning("pointCut()") public void logReturn() { System.out.println("方法傳回後"); } @AfterThrowing("pointCut()") public void logThrow() { System.out.println("方法出現異常"); } }
其中利用**@Pointcut**注解抽取了一個公共切面
(代碼切入)通知方式:
- 前置通知(@Before):在目标業務方法執行前執行切面類中的某個方法
- 後置通知(@After):在目标業務方法執行後執行切面類中的某個方法
- 傳回通知(@AfterReturning):在目标業務方法執行傳回後執行切面類中的某個方法
- 異常通知(@AfterThrowing):在目标業務方法執行出現異常時執行切面類中的某個方法
- 環繞通知(@Around):動态代理,手動推進目标業務方法運作
- 給切面類中的各個方法标注何時何地運作(通知注解)
- 将切面類和業務邏輯類都加入到IOC容器中
@EnableAspectJAutoProxy @Configuration public class MyAopConfig { @Bean public MathCalculator mathCalculator() { return new MathCalculator(); } @Bean public LogAspectJ logAspectJ() { return new LogAspectJ(); } }
- 告訴spring哪個是切面類(給切面類上加上一個注解**@Aspect**)
- 在配置類上加上**@EnableAspectJAutoProxy**注解【開啟基于注解的aop功能】
功能測試結果,建立一個測試類,調用容器中MathCalculator中的div方法,得到如下結果:
方法執行前
div方法執行,參數為:1,1
方法傳回後
方法執行後
如果方法出現異常,得到如下結果:
方法執行前
div方法執行,參數為:1,0
方法出現異常
方法執行後
可以在切面類的各個方法中傳入**JoinPoint參數,來擷取方法業務邏輯方法的名稱等資訊
同時在@AfterReturning、@AfterThrowing注解中分别傳入returning與throwing**參數可以分别擷取到方法的傳回結果以及抛出異常的詳細資訊。
@Aspect
public class LogAspectJ {
@Pointcut("execution(public int com.huhx.blog.other.MathCalculator.*(..))")
public void pointCut() {}
@Before("pointCut()")
public void logStart(JoinPoint joinPoint) {
System.out.println(joinPoint.getSignature().getName() + "方法執行, 目前在方法執行前");
}
@After("pointCut()")
public void logEnd(JoinPoint joinPoint) {
System.out.println("參數清單為:" + Arrays.asList(joinPoint.getArgs()) + " 方法執行後");
}
@AfterReturning(value = "pointCut()", returning = "result")
public void logReturn(Object result) {
System.out.println("傳回值為:" + result + " 方法傳回後");
}
@AfterThrowing(value = "pointCut()", throwing = "exception")
public void logThrow(Exception exception) {
System.out.println("方法出現異常, 異常資訊為:" + exception);
}
}
測試結果:
//正常傳回時
div方法執行, 目前在方法執行前
div方法執行,參數為:1,1
傳回值為:1 方法傳回後
參數清單為:[1, 1]方法執行後
//出現異常時
div方法執行, 目前在方法執行前
div方法執行,參數為:1,0
方法出現異常, 異常資訊為:java.lang.ArithmeticException: / by zero
參數清單為:[1, 0] 方法執行後
2.2 AOP原理探究
2.2.1 @EnableAspectJAutoProxy注解定義
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
/**
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
* to standard Java interface-based proxies. The default is {@code false}.
*/
boolean proxyTargetClass() default false;
/**
* Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
* for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
* Off by default, i.e. no guarantees that {@code AopContext} access will work.
* @since 4.3.1
*/
boolean exposeProxy() default false;
}
可以看到該注解上利用**@Import注解給IOC容器中注入了一個AspectJAutoProxyRegistrar**元件,注入模式是采用@Import注解的第三種注入模式,直接向容器中添加bean的定義資訊,我們來看一下AspectJAutoProxyRegistrar中複寫的方法:
@Override
public void registerBeanDefinitions(
AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy =
AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
if (enableAspectJAutoProxy != null) {
if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
}
if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
}
}
}
核心在第一句,我們來看一下這個方法中向容器中注入了什麼元件,追蹤代碼,來到AopConfigUtil類下的這個方法中:
AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
||
||
\/
registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
||
||
\/
registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
||
||
\/
@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(
Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
//AUTO_PROXY_CREATOR_BEAN_NAME =
// "org.springframework.aop.config.internalAutoProxyCreator"
//如果registy中包含有上面那個名稱定義的類,直接傳回null
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
//如果沒有上面那個類的定義,則建立一個該類的defination,名稱為internalAutoProxyCreator
//真實的類為AnnotationAwareAspectJAutoProxyCreator
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
也就是說,通過使用@EnableAspectJAutoProxy注解,我們給容器中注入了一個==AnnotationAwareAspectJAutoProxyCreator==這個類,下面我們來研究下這個類起到了什麼作用。
2.2.2 AnnotationAwareAspectJAutoProxyCreator類的作用
類的結構:
可以看到該類實作了SmartInstantiationAwareBeanPostProcessor接口與BeanFactoryAware接口
3 聲明式事務
3.1 @Transactional注解
- 要支援事務功能,需要在配置類上使用**@EnableTransactionManagement**
- 給方法上标注@Transactional注解,表示目前方法是一個事務方法,如果該方法中涉及對資料庫的修改操作,若該方法出現異常的時候,資料庫會進行復原。
- 在配置類中配置事務管理器(xxxTransactionManage)
3.2 事務的傳播機制
- REQUIRED:目前方法需要在一個事務中運作,如果方法運作時已經處在一個事務中,則加入到目前事務,否則就自己建立一個事務
- SUPPORTS:該方法在某個事務範圍内被調用,則方法成為該事務的一部分。如果方法在該事務範圍外調用,該方法就在沒有事務的環境下執行
- MANDATORY:目前方法必須被已有事務的方法進行調用,否則會抛出異常
- REQUIRES_NEW:不管是否存在事務,目前方法總會為自己發起一個新的事務。如果方法已經運作在一個事務中,則原有事務挂起,新的事務被建立
- NOT_SUPPORTED:目前方法運作不需要事務,如果方法沒有關聯事務,則不會為他開啟事務,如果目前方法在一個事務中被調用,該事務會被挂起,調用結束後,原先事務會恢複執行
- NEVER:該方法不能在事務範圍内執行。如果在就抛出異常,隻有方法沒有關聯到任何事務,才能正常執行
- NESTED:如果一個活動的事務存在,則運作在一個嵌套的事務中。如果沒有活動事務,則按照REQUIRED屬性執行。它使用了一個單獨的事務,這個事務擁有多個可以復原的儲存點。内部事務的復原不會對外部事務造成影響。它隻對DataSourceTransactionManager事務管理器起效。
4 拓展原理
4.1 BeanFactoryPostProcessor
- **BeanPostProcessor:**bean的後置處理器
- BeanFactoryPostProcessor:beanFactory的後置處理器,在BeanFactory的标準初始化之後調用,所有的bean定義已經儲存加載到了beanFactory,但是bean的執行個體還未建立
我們來測試一下BeanFactoryPostProcessor這個接口
- 首先定義一個類來實作BeanFactoryPostProcessor
- 重寫其中的方法
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("MyBeanFactoryPostProcessor.....");
int count = beanFactory.getBeanDefinitionCount();
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
System.out.println("bean count: " + count);
System.out.println("bean names: " + Arrays.asList(beanDefinitionNames));
}
}
我們來看一下postProcessBeanFactory()這個方法的調用時機,它其實是在對象還沒有建立之前調用的,定義一個Cat元件
@Component
public class Cat {
@Value("mike")
private String name;
@Value("huhx")
private String master;
@Value("#{30 - 6}")
private int age;
public Cat(){
System.out.println("cat init.......");
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", master='" + master + '\'' +
", age=" + age +
'}';
}
}
啟動容器後的執行結果為:
MyBeanFactoryPostProcessor.....
bean count: 9
bean names: [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, myConfig, cat, myBeanFactoryPostProcessor, org.springframework.aop.config.internalAutoProxyCreator]
cat init.......
容器建立完成
可以看到,在Cat元件初始化之前,postProcessBeanFactory()方法已經被調用了,并且傳回了目前容器中的bean數量已經bean的名稱,但是這些bean都還沒有被建立出來,隻是有定義而已
【原理】
- ioc容器建立對象
- 重新整理容器,調用refresh()方法中invokeBeanFactoryPostProcessors(beanFactory)方法。
- 直接在BeanFacory中找到所有類型是BeanFactoryPostProcessor的元件,并執行他們的方法
- 在初始化建立其他元件前面執行
4.2 BeanDefinitionRegistryPostProcessor
這個接口是BeanFactoryPostProcessor的子接口,它内部有一個方法:
postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
這個方法在所有bean定義資訊将要被加載,但是bean執行個體還未建立的時候執行,是以這個方法調用的時機比BeanFactoryPostProcessor接口中的方法要早,我們可以利用它來給容器中再額外添加一些元件。
BeanDefinitionRegistry是bean注冊資訊的儲存中心,以後BeanFactory就是按照BeanDefinationRegistry裡面儲存的每一個bean定義資訊建立bean執行個體。
簡單測試一下:我們直接寫一個類去實作BeanDefinitionRegistryPostProcessor 接口,重寫裡邊的兩個方法:
@Component
public class MyBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
System.out.println("postProcessBeanFactory.....");
int count = beanFactory.getBeanDefinitionCount();
String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();
System.out.println("bean count: " + count);
System.out.println("bean names: " + Arrays.asList(beanDefinitionNames));
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
System.out.println("postProcessBeanDefinitionRegistry......");
System.out.println("目前容器中bean的個數 : " + registry.getBeanDefinitionCount());
//向容器中新注冊一個元件
BeanDefinition beanDefinition = new RootBeanDefinition(Div.class);
registry.registerBeanDefinition("hello", beanDefinition);
System.out.println("添加後容器中bean的個數:" + registry.getBeanDefinitionCount());
}
}
啟動容器後的測試結果:
postProcessBeanDefinitionRegistry......
目前容器中bean的個數 : 10
添加後容器中bean的個數:11
postProcessBeanFactory.....
bean count: 11
bean names: [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, myConfig, cat, div, myBeanFactoryPostProcessor, org.springframework.aop.config.internalAutoProxyCreator, hello]
cat init.......
容器建立完成
可以看到明顯postProcessBeanDefinitionRegistry()方法的執行是在postProcessBeanFactory()方法之前的,并且我們新加的一個bean,“hello”也被添加到了容器中
【原理】
- 建立ioc容器
- refresh() -> invokeBeanFactoryPostProcessors()
- 從容器中擷取到所有的BeanDefinitionRegistryPostProcessor元件
- 依次觸發所有的postProcessBeanDefinitionRegistry()方法
- 再來觸發BeanFactoryPostProcessor中的postProcessBeanFactory()方法
4.3 ApplicationListener
用于監聽容器中釋出的事件。完成事件驅動模型開發
監聽ApplicationEvent及其下面的子事件,當有事件釋出的時候,其中onApplicationEvent()方法就會被調用。
使用步驟:
- 寫一個監聽器來監聽某個事件(ApplicationEvent及其下面的子事件),實作ApplicationListener接口并且實作裡邊的方法
- 把監聽器加入容器
- 隻要容器中有相關事件的釋出,我們就能監聽到這個事件:
- ContextRefreshedEvent:容器重新整理完成(所有的bean都完成建立)會釋出這個事件
- ContextClosedEvent:關閉容器會釋出這個事件
- 釋出一個事件:applicationContext.publishEvent()
【事件釋出的流程】:
1、容器建立對象:refresh()
2、finishRefresh():容器重新整理完成
3、publishEvent(new ContextRefreshedEvent(this));
- 擷取事件的多點傳播器(派發器):getApplicationEventMulticaster()
- multicastEvent派發事件
- 擷取到所有的ApplicationListener
- 如果有Executor,可以支援使用Executor進行異步派發
executor.execute(() -> invokeListener(listener, event));
- 否則,同步的方式直接執行listener方法,invokeListener(listener, event);拿到listener回調onApplicationEvent()方法
4.4 @EventListener
該注解可以讓任何方法來監聽spring容器中的事件,跟實作Listener接口是一樣的
原理:
5 Spring容器的啟動流程
- prepareRefresh():重新整理前的預處理
- initPropertySources() :初始化一些屬性設定;留給子類去重寫的方法,可以自定義的去進行一些屬性設定
- getEnvironment().validateRequiredProperties() :檢驗屬性的合法等
- this.earlyApplicationEvents = new LinkedHashSet<>() :儲存容器中的一些早期事件
- obtainFreshBeanFactory():擷取BeanFactory;
-
refreshBeanFactory():重新整理【建立】beanFactory
在GenericApplicationContext對象構造時,beanFactory就已經被構造出來了:this.beanFactory=new DefaultListableBeanFactory(),在這一步隻是設定了一個序列化的id
- getBeanFactory();傳回剛才GenericApplicationContext建立的BeanFactory【DefaultListableBeanFactory】對象
-
- prepareBeanFactory(beanFactory):BeanFactory的預準備工作(對BeanFactory進行一些設定)
- 設定BeanFactory的類加載器、支援表達式解析器
- 添加部分BeanPostProcessor【ApplicationContextAwareProcessor】
- 設定忽略的自動裝配的接口EnvironmentAware、EmbeddedValueResolverAware
- 注冊可以解析的自動裝配;我們能直接在任何元件中自動注入:BeanFactory、ResourceLoader、ApplicationEventPublisher、ApplicationContext
- 添加BeanPostProcessor【ApplicationListenerDetector】
- 添加編譯時的AspectJ
-
給BeanFactory中注冊一些能用的元件:
environment【ConfigurableEnvironment】、systemProperties【Map<String,Object>】、systemEnvironment【Map<String,Object>】
- postProcessBeanFactory(beanFactory):BeanFactory準備工作完成後進行的後置處理工作
- 子類通過重寫這方法在BeanFactory建立并預準備完成以後做進一步的設定
++++++++++++++++++++++以上是BeanFactory的建立及預準備工作+++++++++++++++++++++++++++++
- invokeBeanFactoryPostProcessors(beanFactory):執行BeanFactoryPostProcessor的方法
BeanFactoryPostProcessor:BeanFactory的後置處理器,在BeanFactory标準初始化之後執行兩個接口:BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor接口
- 執行BeanFactoryPostProcessor的方法:
- 擷取所有BeanDefinitionRegistryPostProcessor
- 先執行實作了PriorityOrdered優先級接口的、再執行實作了Ordered的接口的、最後執行其它的
- 擷取所有BeanFactoryPostProcessor
- 先執行實作了PriorityOrdered優先級接口的、再執行實作了Ordered的接口的、最後執行其它的
- 執行BeanFactoryPostProcessor的方法:
- registerBeanPostProcessors(beanFactory):注冊BeanPostProcessor(Bean的後置處理器)【攔截Bean的建立過程】
不同類型的BeanPostProcessor,在Bean建立前後的執行時機是不一樣的
有如下幾類: BeanPostProcessor、DestructionAwareBeanPostProcessor、InstantiationAwareBeanPostProcessor、SmartInstantiationAwareBeanPostProcessor、MergedBeanDefinitionPostProcessor【internalPostProcessors】
-
擷取所有的BeanPostProcessor;
後置處理器都預設可以通過PriorityOrdered、Ordered來指定優先級
-
先注冊PriorityOrdered優先級接口的BeanPostProcessor
把每一個BeanPostProcessor添加到BeanFactory中,beanFactory.addBeanPostProcessor(postProcessor);
- 再注冊了實作Ordered接口的
- 最後注冊其它的
- 最終注冊MergedBeanDefinitionPostProcessor類型的
- 注冊一個ApplicationListenerDetector;來在Bean建立完成後檢查是否是ApplicationListener:addApplicationListener((ApplicationListener<?>) bean);
-
- initMessageSource():初始化MessageSource元件(做國際化功能;消息綁定;消息解析等功能)
- 擷取BeanFactory
-
看容器中是否有id為messageSource,類型是MessageSource的元件,如果有指派給messageSource,如果沒有自己建立一個DelegatingMessageSource;
MessageSource:取出國際化配置檔案中的某個key的值;能按照區域資訊擷取;
- 把建立好的MessageSource注冊在容器中,以後擷取國際化配置檔案的值的時候,可以自動注入MessageSource;調用其方法可以獲得相關配置屬性:beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
- initApplicationEventMulticaster():初始化事件派發器【多點傳播器】
- 擷取BeanFactory
- 從BeanFactory擷取applicationEventMulticaster的元件
- 如果上一步沒有配置;建立一個SimpleApplicationEventMulticaster
- 将建立的ApplicationEventMulticaster添加到BeanFactory中,以後其他元件直接自動注入
- onRefresh()—留給子容器(子類)
- 子類重寫這個方法,在容器重新整理的時候可以自定義邏輯——SpringMVC九大元件的初始化;
- registerListeners():将所有項目裡面的ApplicationListener注冊到容器中來
- 從容器中拿到所有ApplicationListener元件
- 将每個監聽器添加到事件派發器中:getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
- 派發之前步驟産生的事件;
- finishBeanFactoryInitialization(beanFactory):初始化所有剩下的單執行個體bean
- beanFactory.preInstantiateSingletons() 初始化剩下的單執行個體bean
1)、擷取容器中的所有BeanDefination,依次進行初始化和建立對象
List beanNames = new ArrayList<>(this.beanDefinitionNames);
2)、周遊beanNames擷取bean 的定義資訊;
3)、Bean不是抽象的,是單執行個體的,是懶加載;
- 判斷是否是FactoryBean;是否是實作FactoryBean接口的Bean;
- 如果是,利用工廠方法建立對象
- 不是工廠Bean,利用getBean(過程如下)(beanName);建立對象
- 判斷是否是FactoryBean;是否是實作FactoryBean接口的Bean;
- getBean()方法流程
- AbstractBeanFactory.doGetBean();
- 先擷取緩存中儲存的單執行個體bean,如果能擷取到,說明這Bean之前被建立過(所有建立過的單執行個體Bean都會被緩存起來)從singletonObjects=new ConcurrentHashMap<String,Object>中擷取到
- 緩存中擷取不到,開始Bean的建立對象流程;
- 标記目前Bean已經被建立,markBeanAsCreated(beanName);
- 擷取Bean的定義資訊
- 擷取目前Bean依賴的其它Bean;如果有,按照getBean()把依賴的Bean先建立出來
- 啟動單執行個體Bean的建立流程
- createBean(beanName,mbd,args);
- 先讓BeanPostProcessor【InstantiationAwareBeanPostProcessor】先攔截傳回代理對象,先觸發所有該接口的postProcessBeforeInstantiation()方法,如果有傳回對象,調用applyBeanPostProcessorsAfterInitialization(),即會執行所有的BeanPostProcessor的postProcessAfterInitialization()方法,将bean傳回
- 如果沒有傳回bean,調用doCreateBean();
- 利用工廠方法或者對象的構造器等建立bean執行個體
- 調用MergedBeanDefinitionPostProcessor的postProcessMergedBeanDefinition()
- 為bean的屬性指派
- 拿到InstantiationAwareBeanPostProcessor類型的後置處理器,執行postProcessAfterInstantiation();
- 拿到InstantiationAwareBeanPostProcessor類型的後置處理器,執行postProcessProperties();
- 應用Bean屬性的值,為屬性利用setter方法等進行指派
- Bean初始化
- 執行xxxAware接口的方法
invokeAwareMethods(beanName, bean); BeanNameAware\BeanClassLoaderAware\BeanFactoryAware
- 執行後置處理器在初始化之前
applyBeanPostProcessorsBeforeInitialization(); BeanPostProcessor.postProcessBeforeInitialization();
- 執行初始化方法
- 判斷是否是InitializingBean接口的實作;執行接口規定的初始化
- 是否自定義初始化方法
- 執行後置處理器在初始化之後
- 執行xxxAware接口的方法
- 注冊Bean的銷毀方法
- Bean的執行個體建立完成,将bean添加到緩存中
- 所有Bean都利用getBean建立完成以後;檢查所有的Bean是否是SmartInitializingSingleton接口類型的,如果是就執行 afterSingletonsInstantiated()方法
- createBean(beanName,mbd,args);
- beanFactory.preInstantiateSingletons() 初始化剩下的單執行個體bean
- finishRefresh():完成BeanFactory初始化建立工作;IOC容器就建立完成
- initLifecycleProcessor();初始化聲明周期有關的後置處理器
允許我們寫一個LifecycleProcessor的實作類,可以在BeanFactory進行到特定生命周期時進行調用 預設從容器中找是否有LifeCycleProcessor的元件,如果沒有,預設會建立一個 new DefaultLifecycleProcessor();然後加入到容器中
- getLifecycleProcessor().onRefresh() 拿到所有前面定義的生命周期處理器回調onRefresh()
- publishEvent(new ContextRefreshedEvent(this)) 釋出容器重新整理完成事
- LiveBeansView.registerApplicationContext(this);
- initLifecycleProcessor();初始化聲明周期有關的後置處理器