Spring源碼------手寫體驗AOP

Spring源碼------手寫體驗AOP

目錄

Spring源碼------手寫體驗AOP

1、前言

2、AOP的實現原理

3、核心代碼

4、總結


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()渲染(即:讀取模板文件內容,用正則替換佔位符),最後輸出到瀏覽器上。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章