Spring源碼------手寫體驗AOP
目錄
1、前言
本篇博客並非是對Spring源碼的深入研究。過程中主要是體驗Spring AOP的過程。那麼這篇博客涉及到的知識點大致有以下幾點:
- 如何自定義註解,如何通過反射機制去賦予註解強大的功能(說白了,就是體驗在反射機制下,註解功能是多麼的強大)
- Spring Ioc容器的實現原理
- Spring DI 註解注入
- SpringMVC AOP
- Java反射機制
- Java I/O流(加載配置文件,讀取配置文件信息)
- 正則表達式
2、AOP的實現原理
相信大家或多或少的瞭解過AOP,都知道它是面向切面編程,在網上搜索可以找到很多的解釋。這裏一句話來總結j就是:AOP是能夠讓我們在不影響原有功能的前提下,爲軟件橫向擴展功能。 那麼橫向擴展怎麼理解呢,我們在WEB項目開發中,通常都遵守三層原則,包括控制層(Controller)->業務層(Service)->數據層(dao),那麼從這個結構下來的爲縱向,它具體的某一層就是我們所說的橫向。我們的AOP就是可以作用於這某一個橫向模塊當中的所有方法。
我們在來看一下AOP和OOP的區別:AOP是OOP的補充,當我們需要爲多個對象引入一個公共行爲,比如日誌,操作記錄等,就需要在每個對象中引用公共行爲,這樣程序就產生了大量的重複代碼,使用AOP可以完美解決這個問題。
接下來介紹一下提到AOP就必須要了解的知識點:
- 切面(Aspect):攔截器類,其中會定義切點以及通知
- 切點(PointCut):具體攔截的某個業務點。
- 通知(Advice):切面當中的方法,聲明通知方法在目標業務層的執行位置,通知類型如下:
- 前置通知:@Before 在目標業務方法執行之前執行
- 後置通知:@After 在目標業務方法執行之後執行
- 返回通知:@AfterReturning 在目標業務方法返回結果之後執行
- 異常通知:@AfterThrowing 在目標業務方法拋出異常之後
- 環繞通知:@Around 功能強大,可代替以上四種通知,還可以控制目標業務方法是否執行以及何時執行
其中SpringAop 的實現主要是通過動態代理思想實現的。主要有兩種實現的方式:jdk動態代理和Cglib字節碼代理
具體可以閱讀我之前寫的文章:《設計模式------代理模式詳解》
3、核心代碼
主要類結構圖:
.......
//創建真正的實例對象
private Object instantiateBean(String beanName,ZPSBeanDefinition beanDefinition) {
String className = beanDefinition.getBeanClassName();
Object instance = null;
try {
if(this.factoryBeanObjectCache.containsKey(beanName)){
instance = this.factoryBeanObjectCache.get(beanName);
}else {
Class<?> clazz = Class.forName(className);
//默認的類名首字母小寫
instance = clazz.newInstance();
//AOP是在這裏開始進行的
//首先需要讀取配置文件,然後進行解析
ZPSAdviceSupport config = instantionAopConfig(beanDefinition);
config.setTargetClass(clazz);
config.setTarget(instance);
//判斷是否需要生成代理類,如果需要就覆蓋原生對象
//如果不需要,就不做任何處理
if(config.pointCutMatch()){
instance = new ZPSJdkDynamicAopProxy(config).getProxy();
}
this.factoryBeanObjectCache.put(beanName, instance);
}
}catch (Exception e){
e.printStackTrace();
}
return instance;
}
.......
/**
* @description: 封裝通知Advice信息
* @author: zps
* @create: 2020-05-09 22:51
**/
@Data
public class ZPSAdvice {
private Object aspect;
private Method adviceMethod;
private String throwName;
public ZPSAdvice(Object aspect , Method adviceMethod){
this.aspect = aspect;
this.adviceMethod = adviceMethod;
}
}
/**
* @description: 封裝讀取到的配置文件信息
* @author: zps
* @create: 2020-05-09 22:54
**/
@Data
public class ZPSAopConfig {
private String pointCut;
private String aspectClass;
private String aspectBefore;
private String aspectAfter;
private String aspectAfterThrow;
private String aspectAfterThrowingName;
}
/**
* @description: AOP配置文件解析工具類
*
* @author: zps
* @create: 2020-05-09 23:02
**/
public class ZPSAdviceSupport {
//從配置文件獲取的AOP相關信息
private ZPSAopConfig config;
private Object target;
private Class targetClass;
private Pattern pointCutClassPattern;
//保存被代理方法的通知
//Mehhod:被代理的方法 Map<String , ZPSAdvice> String : 需要織入的通知類型,如before , ZSPAdvice:通知對象
private Map<Method , Map<String , ZPSAdvice>> methodCache;
public ZPSAdviceSupport(ZPSAopConfig config){
this.config = config;
}
private void parse(){
//把Spring的Excpress變成Java能夠識別的正則表達式
String pointCut = config.getPointCut()
.replaceAll("\\.", "\\\\.")
.replaceAll("\\\\.\\*", ".*")
.replaceAll("\\(", "\\\\(")
.replaceAll("\\)", "\\\\)");
//保存專門匹配Class的正則
String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));
//享元的共享池
methodCache = new HashMap<Method, Map<String, ZPSAdvice>>();
//保存專門匹配方法的正則
Pattern pointCutPattern = Pattern.compile(pointCut);
try {
//獲取切面類
Class aspectClass = Class.forName(this.config.getAspectClass());
//保存切面的方法
Map<String , Method> aspectMethods = new HashMap<String, Method>();
for(Method method : aspectClass.getMethods()){
aspectMethods.put(method.getName() , method);
}
for(Method method : this.targetClass.getMethods()){
//將方法化爲String
String methodString = method.toString();
if(methodString.contains("throws")){
methodString = methodString.substring(0 , methodString.lastIndexOf("throws")).trim();
}
Matcher matcher = pointCutPattern.matcher(methodString);
if(matcher.matches()){ //若匹配,保存一對一的映射關係
Map<String , ZPSAdvice> advices = new HashMap<String, ZPSAdvice>();
if(!(config.getAspectBefore() == null ||
config.getAspectBefore().equals(""))){
advices.put("before" , new ZPSAdvice(aspectClass.newInstance() ,
aspectMethods.get(config.getAspectBefore())));
}
if(!(null == config.getAspectAfter() || "".equals(config.getAspectAfter()))){
advices.put("after",new ZPSAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfter())));
}
if(!(null == config.getAspectAfterThrow() || "".equals(config.getAspectAfterThrow()))){
ZPSAdvice advice = new ZPSAdvice(aspectClass.newInstance(),aspectMethods.get(config.getAspectAfterThrow()));
advice.setThrowName(config.getAspectAfterThrowingName());
advices.put("afterThrow",advice);
}
//跟目標代理類的業務方法和Advices建立一對多個關聯關係,以便在Porxy類中獲得
methodCache.put(method , advices);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
//根據一個目標代理類的方法,獲得其對應的通知
public Map<String,ZPSAdvice> getAdvices(Method method, Object o) throws Exception {
//享元設計模式的應用
Map<String,ZPSAdvice> cache = methodCache.get(method);
if(null == cache){
Method m = targetClass.getMethod(method.getName(),method.getParameterTypes());
cache = methodCache.get(m);
this.methodCache.put(m,cache);
}
return cache;
}
//判斷是否需要生成代理類
public boolean pointCutMatch() {
return pointCutClassPattern.matcher(this.targetClass.toString())
.matches();
}
public void setTargetClass(Class<?> clazz) {
this.targetClass = clazz;
parse();
}
public void setTarget(Object instance) {
this.target = instance;
}
public Class getTargetClass() {
return targetClass;
}
public Object getTarget() {
return target;
}
}
**
* @description: AOP工具類:主要是解析配置文件
* @author: zps
* @create: 2020-05-09 22:59
**/
public class ZPSJdkDynamicAopProxy implements InvocationHandler {
private ZPSAdviceSupport config;
public ZPSJdkDynamicAopProxy(ZPSAdviceSupport config) {
this.config = config;
}
public Object getProxy() {
return Proxy.newProxyInstance(this.getClass().getClassLoader() , this.config
.getTargetClass().getInterfaces() , this);
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Map<String, ZPSAdvice> advices = config.getAdvices(method,null);
Object returnValue;
try {
invokeAdivce(advices.get("before"));
returnValue = method.invoke(this.config.getTarget(),objects);
invokeAdivce(advices.get("after"));
}catch (Exception e){
invokeAdivce(advices.get("afterThrow"));
throw e;
}
return returnValue;
}
private void invokeAdivce(ZPSAdvice advice) {
try {
advice.getAdviceMethod().invoke(advice.getAspect());
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
運行結果:
4、總結
手寫體驗Spring總結如下:
Spring初始化的大致流程:IOC—>AOP—>DI—>MVC
- IOC(控制反轉)
1、創建整個容器的主體ApplicationContext。
2、BeanDefinitionReader讀取配置文件(properties、xml、yml),掃描相關的類,將配置信息存儲到BeanDefinition。
3、初始化IOC容器,將實例化對象存儲到BeanWrapper。
4、ApplicationContext getBean()採用的單例bean,容器式(Lazy)延時加載。
- AOP(面向切面編程)
1、getBean()—>instantiateBean() 判斷是否要生成Proxy代理對象。
(1)加載AOP的配置文件,調用instantionAopConfig(beanDefinition),生成AdvisedSupport。
AdvisedSupport Map<Method,Map<String,GPAdvice>> methodCache存儲了目標代理類的業務方法和Advices建立的 一對多的關聯關係。
AdvisedSupport getAdvices()可根據一個目標代理類的方法,獲得其對應的通知。
(2)判斷規則,要不要生成代理類GPJdkDynamicAopProxy,如果要就覆蓋原生對象,如果不要就不做任何處理,返回原 生對象。
- DI(依賴注入)
1、getBean()—>instantiiateBean()用反射初始化Bean對象。
2、populateBean()完成依賴注入,用反射注入。
3、循環依賴注入:用緩存去解決依賴注入的問題。
- MVC
1、DisparcherServlet init()初始化9大組件。
2、initStrategers()初始化HandlerMapping,HandlerAdapter,ViewResolver。
(1)一個HandlerMapping對應一個HandlerAdapter。
(2)ViewResolver涉及模板,一個ModelAndView關聯一個ViewResolver。
3、根據HandlerAdapter得到一個ModelAndView。
(1)如不返回頁面則直接responser.getWriter().write()輸出到瀏覽器上。
(2)如返回頁面,啓用模板引擎生成一個View,通過View render()渲染(即:讀取模板文件內容,用正則替換佔位符),最後輸出到瀏覽器上。