一 Spring-AOP
1.對SpringAOP理解
????????AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生泛型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
AOP(Aspect-Oriented Programming:面向切面編程):將哪些與業務無關,卻為業務模塊所共同調用的邏輯(例如事務處理,日志管理,權限控制)封裝抽成一個可重用的模塊,這個模塊被命名為“切面”,便于減少系統的重復代碼,降低模塊之間的耦合度,并有利于未來的可拓展性和可維護性。
SpringAOP基于動態代理實現:
- ? ? ? ? 如果被代理的對象,已經實現某個接口,則SpringAOP會使用JDK Proxy(反射),基于接口的方式,創建代理對象(JDK動態代理的核心是InvocationHandler接口和Proxy類);
- ????????如果被代理的對象,沒有實現某個接口,就無法使用JDK Proxy去處理代理了,這時候Spring會使用Cglib,基于繼承的方式,生成一個被代理類對象的子類來作為代理(Cglib動態代理的核心是MethodInterceptor接口和Enhancer類)。
2.AOP通知
? ? ? ? 概念:AOP將抽取出來的共性功能稱為通知;
????????通知類型:以通知在上下文中的具體位置作為劃分
? ? ? ? 解釋:通知就是需要增強的方法內容以及執行位置的結合。
? ? ? ? 前置通知-before,返回通知-after-returning,異常通知-after-throwing,后置通知-after,環繞通知-around.
<!-- aop配置--><aop:config>
<!-- 配置切面--><aop:aspect id="mian" ref="logger">
<!-- 配置切點--><aop:pointcut id="cut" expression="execution(public * com.itheima.service.*.*(..))"></aop:pointcut>
<!-- 配置通知-->
<!-- 前置通知--><aop:before method="beforeLog" pointcut-ref="cut"></aop:before>
<!-- 后置通知--><aop:after method="afterLog" pointcut-ref="cut"></aop:after>
<!-- 返回通知--><aop:after-returning method="afterReturningLog" pointcut-ref="cut"></aop:after-returning>
<!-- 異常通知--><aop:after-throwing method="afterThrowingLog" pointcut-ref="cut"></aop:after-throwing><aop:around method="aroundLog" pointcut-ref="cut"></aop:around></aop:aspect></aop:config>
3.AOP連接點
? ? ? ? AOP將所有的方法都視為連接點,不管是接口里面的抽象方法,還是實現類里面的重寫方法,都是連接點。
? ? ? ? 解釋:具備添加通知能力的方法位置,就是連接點,也就是所有類的所有方法
4.AOP切點
? ? ? ? AOP將可能被抽取共性功能的方法稱為切入點,切入點是連接點的子集。
? ? ? ? 解釋:成功添加了通知方法的位置,就是切點。
<aop:pointcut id="dian" expression="execution(public * com.apesource.service.*.*(..))"/>
5.AOP目標對象
????????就是挖掉功能的方法對應的類生的對象,這種對象是無法直接完成最終工作的
? ? ? ? 解釋:被代理對象,就是目標對象
6.AOP織入
? ? ? ? 就是將挖掉的功能回填的動態過程
? ? ? ? 解釋:將通知添加到切點的過程ing,就是織入。
補充:AOP切面:切點+通知
7.SpringAOP+AspectJ實現
????????1.坐標
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.7</version></dependency>
????????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" xmlns:aop="http://www.springframework.org/schema/aop"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.xsd"><bean id="accountSErviceImp" class="com.itheima.service.AccountServiceImp"></bean><bean id="logger" class="com.itheima.util.Logger"></bean>
<!-- aop配置--><aop:config>
<!-- 配置切面--><aop:aspect id="mian" ref="logger">
<!-- 配置切點--><aop:pointcut id="cut" expression="execution(public * com.itheima.service.*.*(..))"></aop:pointcut>
<!-- 配置通知-->
<!-- 前置通知--><aop:before method="beforeLog" pointcut-ref="cut"></aop:before>
<!-- 后置通知--><aop:after method="afterLog" pointcut-ref="cut"></aop:after>
<!-- 返回通知--><aop:after-returning method="afterReturningLog" pointcut-ref="cut"></aop:after-returning>
<!-- 異常通知--><aop:after-throwing method="afterThrowingLog" pointcut-ref="cut"></aop:after-throwing><aop:around method="aroundLog" pointcut-ref="cut"></aop:around></aop:aspect></aop:config>
</beans>
切點表達式配置語法:
? ? ? ? execution(修飾符 返回值 包名稱.類名稱.方法名稱(參數列表))
1.修飾符可以省略代表任意
execution(返回值 包名稱.類名稱.方法名稱(參數列表))
2.返回值可以使用“*”代表任意
execution(* 包名稱.類名稱.方法名稱(參數列表))
3.包名可以使用“*”代表任意名稱
execution(* *.*.*.類名稱.方法名稱(參數列表))
eg:execution(void *.*.*.ServiceImp.findAll())
4.包名可以使用“..”代表任意個數
execution(* *..類名稱.方法名稱(參數列表))
eg:execution(void *..ServiceImp.findAll())
5.類名與方法名可以使用“*”代表任意
execution(* *...*.*(參數列表))
6.參數列表可以使用".."代表任意個數任意類型
execution(* *...*.*(..))? ? 如果有參數
int======>int
String===>java.lang.String
二 Spring中bean的一生
1.bean實例化的基本流程-底層
? ? ? ? Spring容器在進行初始化時,會將xml配置的信息封裝成一個BeanDefinition對象,所有的BeanDefinition存儲到一個名為beanDefinitionMap的Map集合中去,Spring框架在對該Map進行遍歷,使用反射創建bean實例對象,創建號的Bean對象存儲在一個名為singletonObject的Map集合中,當調用getBean方法時,則最終從該Map集合中取出Bean實例對象返回。
? Bean 實例化的基本流程
加載xml配置文件,解析獲取配置中的每個的信息,封裝成一個個的BeanDe?nition對象;
將BeanDe?nition存儲在一個名為beanDe?nitionMap的Map中;
ApplicationContext底層遍歷beanDe?nitionMap,創建Bean實例對象; 創建好的Bean實例對象,被存儲到一個名為singletonObjects的Map中;
當執行applicationContext.getBean(beanName)時,從singletonObjects去匹配Bean實例返回。
2.Spring 還有其他類型的 “后處理器”
后處理器接口 | 作用階段 | 核心功能 |
---|---|---|
BeanFactoryPostProcessor | Bean 定義加載后,Bean 實例化前 | 修改 BeanDefinition(Bean 的元信息),例如: - 動態修改 Bean 的屬性值、作用域 - 新增 Bean 定義(如 Spring 的 PropertyPlaceholderConfigurer 用于解析${} 占位符) |
BeanDefinitionRegistryPostProcessor | BeanFactoryPostProcessor 的增強版,更早執行 | 向容器中注冊新的 BeanDefinition(比BeanFactoryPostProcessor 有更高的優先級),例如:- 動態掃描并注冊 Bean(Spring Boot 的 @ComponentScan 底層用到類似機制) |
InstantiationAwareBeanPostProcessor | Bean 實例化前后(比BeanPostProcessor 更早) | 干預 Bean 的實例化過程,例如: - 阻止默認實例化,返回自定義 Bean 對象 - 處理屬性注入前的邏輯(如 @Autowired 的依賴查找) |
DestructionAwareBeanPostProcessor | Bean 銷毀前 | 處理 Bean 銷毀前的邏輯,例如: - 資源釋放、狀態清理等 |
Spring的后處理器是Spring對外開放的重要擴展點,允許我們介入到Bean的整個實例化流程中來,以達到動態注冊BeanDefinition,以及動態修改Bean的作用。
Spring主要有兩種后處理器
- ? ? ? ? BeanFactoryPostProcessor:Bean工廠后處理器,在BeanDefinitionMap填充完畢,Bean實例話之前執行;
- BeanPostProcessor:Bean后處理器,一般在Bean實例化之后,填充到單例池singletonObjects之前執行;
3.Bean工廠后處理器 – BeanFactoryPostProcessor
BeanFactoryPostProcessor是一個接口規范,實現了該接口的類只要交由Spring容器管理的話,那么 Spring就會 回調該接口的方法,用于對BeanDe?nition注冊和修改的功能。
修改:
????????1. 創建BeanFactoryPostProcessor實現類并重寫方法
????????2. 注入實現類
注冊:
????????1. 創建BeanFactoryPostProcessor實現類并重寫方法
????????2. 注入實現類
Spring 提供了一個BeanFactoryPostProcessor的子接口BeanDe?nitionRegistryPostProcessor專門用 于注冊BeanDe?nition操作
public class MyBeanFactoryPostProcessor2 implements
BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory
configurableListableBeanFactory) throws BeansException {}@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry
beanDefinitionRegistry) throws BeansException {BeanDefinition beanDefinition = new RootBeanDefinition();beanDefinition.setBeanClassName("com.apesource.pojo.Student");beanDefinitionRegistry.registerBeanDefinition("stu",beanDefinition);}
}
4.Bean后處理器-BeanPostProcessor
? ? ? ? Bean被實例化后,到最終緩存到名為singletonObjects單例池之前,中間會經過Bean的初始化過程。
public class MyBeanPostProcessor implements BeanPostProcessor{public Object postProcessBeforeInitialization(Object bean, String beanName){System.out.println("初始化之前執行");return bean;}public Object postProcessAfterInitialization(Object bean, String beanName){System.out.println("初始化之后執行");return bean;}
5.spring-bean的生命周期
? ? ? ? 從Bean實例化之后,及通過反射創建出對象之后,到Bean成為一個完整對象最終存儲到單例池中,這個過程被成為SPringBean的生命周期。
大致分為三個階段
- Bean的實例化階段:spring框架會取出BeanDefinition的信息進行判斷當前bean的范圍是否是singleton的,是否不是延遲加載的,是否不是FactoryBean等,最終將一個普通的singleton的Bean通過反射進行實例化。
- Bean的初始化階段:Bean創建之后還僅僅是個”半成品“,還需要對Bean的實例進行填充,執行一些aware接口方法,執行BeanPostProcessor方法,執行InitializingBean接口的初始化方法。執行自定義初始化init方法等。該階段是Spring最具技術含量和復雜度的階段。
- Bean的完成階段:經過初始化階段,Bean就成為了一個完整的Spring Bean,被存儲到單例池sinletonObjects去了,及完成了SPringBean的整個生命周期。
6.Spring Bean的初始化過程涉及如下幾個過程:
Bean實例的屬性填充
Aware接口屬性注入
BeanPostProcessor的before()方法回調
InitializingBean接口的初始化方法回調 自定義初始化方法init回調
BeanPostProcessor的after()方法回調
8.常用的Aware接口
????????Aware接口是一種框架輔助屬性注入的一種思想,其他框架中也可以看到類似的接口。框架具備高度封裝性,我們接 觸到的一般都是業務代碼,一個底層功能API不能輕易的獲取到,但是這不意味著永遠用不到這些對象,如果用到了 ,就可以使用框架提供的類似Aware的接口,讓框架給我們注入該對象。
三 循環依賴-解決
? ? ? 1.? Spring在進行屬性注入時,會分為如下幾種情況:
????????????????注入普通屬性,String、int或存儲基本類型的集合時,直接通過set方法的反射設置進去;
????????????????注入單向對象引用屬性時,從容器中getBean獲取后通過set方法反射設置進去,如果容器中沒有,則先創建被注入對象Bean實例(完成整個生命周期)后,在進行注入操作;
????????????????注入雙向對象引用屬性時,就比較復雜了,涉及了循環引用(循環依賴)。
循環依賴
????????含義:多個實體之間相互依賴并形成閉環的情況就叫做"循環依賴",也叫做"循環引用"。
????????Spring提供了 三級緩存 存儲完整Bean實例和半成品Bean實例,用于解決循環引用問題
????????在DefaultListableBeanFactory的上四級父類DefaultSingletonBeanRegistry中提供如下三個Map
public class DefaultSingletonBeanRegistry ... {//1、最終存儲單例Bean成品的容器,即實例化和初始化都完成的Bean,稱之為"一級緩存" Map<String, Object> singletonObjects = new ConcurrentHashMap(256);//2、早期Bean單例池,緩存半成品對象,且當前對象已經被其他對象引用了,稱之為"二級緩存" Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);//3、單例Bean的工廠池,緩存半成品對象,對象未被引用,使用時在通過工廠創建Bean,稱之為"三級緩存"
Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}
注:將對象保存至三級緩存的時候,會包裝成ObjectFactory對象錄入,未來通過此接口對應的get方法再次提取對象.
UserService和UserDao循環依賴的過程結合上述三級緩存描述一下
- UserService 實例化對象,但尚未初始化,將UserService存儲到三級緩存;
- UserService 屬性注入,需要UserDao,從緩存中獲取,沒有UserDao;
- UserDao實例化對象,但尚未初始化,將UserDao存儲到到三級緩存;
- UserDao屬性注入,需要UserService,從三級緩存獲取UserService,UserService從三級緩存移入二級緩存;
- UserDao執行其他生命周期過程,最終成為一個完成Bean,存儲到一級緩存,刪除二三級緩存; UserService 注入UserDao;
- UserService執行其他生命周期過程,最終成為一個完成Bean,存儲到一級緩存,刪除二三級緩存。