15-Spring AOP的底層實現原理JDKProxy&CGLIB

前路無畏發表於2020-11-03

前一篇:14-Spring AOP程式設計https://blog.csdn.net/fsjwin/article/details/109480097
前面我們討論動靜態理的時候說過,想要實現動態代理需要滿足三個條件:

  1. 目標物件
  2. 介面 代理物件和目標物件的相同介面
  3. 增強功能

1.JDK proxy動態代理原理

先來對jdk的proxy底層原理進行分析。
通過下面一幅圖,應該可以對jdkproxy的代理細節有個深入的認識:
在這裡插入圖片描述

2.JDK proxy程式碼

JDKProxy.java

package proxy.jdk;


import proxy.service.UserService;
import proxy.service.impl.UserServiceImpl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author yuhl
 * @Date 2020/11/3 22:23
 * @Classname JDKProxy
 * @Description JDK動態代理
 */
public class JDKProxy {


    public static void main(String[] args) {
        //目標物件
        UserService userService = new UserServiceImpl();

        InvocationHandler invocationHandler = new InvocationHandler() {
            /**
             *
             * @param proxy 代理物件,暫時不用
             * @param method 需要被增強的方法
             * @param args 方法的返回值後
             * @return 原方法的返回值
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("JDKProxy.invoke 前增強");
                Object ret = method.invoke(userService, args);
                System.out.println("JDKProxy.invoke 後增強");

                return ret;
            }
        };
        UserService userServiceProxy = (UserService)Proxy.newProxyInstance(userService.getClass().getClassLoader(), userService.getClass().getInterfaces(), invocationHandler);
        userService.login("yuhl", "1111");
        userServiceProxy.login("yuhl", "1111");
    }
}

3.JDK proxy學習這個動態代理有什麼意義呢?

前面我們學習了MethodInterceptor,他和jdkproxy什麼關係呢?一個是sun對動態dialing的實心,另一個是aop聯盟的實現。aop聯盟的實現更為友好吧。
在這裡插入圖片描述
學習這個jdkproxy有什麼意義呢?
意義就在於spring的底層就是使用的這個方法生成的proxy物件返回給我們的。這樣我們就知道了spring生成代理物件的原始碼。對於門寫程式碼有很好的的撐腰作用。

4.CGlib動態代理

和JDK動態代理一樣,cglib可以對一般的類驚醒動態代理,但是他的條件適應的,他的底層是整合了當前類,然後用super.login()方法,然後在前後進行加強。
也需要三個必要條件:

  1. 目標類
  2. 父類物件(介面)
  3. 增強程式碼
    這點和jdkproxy一模一樣,唯一不一樣的是一個是介面、一個是一般的類

5.CGlib程式碼

  1. 目標類
package proxy.cglib;

import proxy.service.User;

/**
 * @author yuhl
 * @Date 2020/11/3 23:00
 * @Classname StudentService
 * @Description TODO
 */
public class StudentService {
    public boolean login(String name,String password){
        System.out.println("StudentService.login");
        return true;
    }

    public void register(User user) {
        System.out.println("StudentService.register");
    }
}

  1. 測試類
package proxy.cglib;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @author yuhl
 * @Date 2020/11/3 23:01
 * @Classname CjlibTest
 * @Description TODO
 */
public class CjlibTest {
    public static void main(String[] args) {
        //目標類
        StudentService studentService = new StudentService();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(studentService.getClass());
        enhancer.setClassLoader(CjlibTest.class.getClassLoader());

        MethodInterceptor methodInterceptor= new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("CjlibTest.intercept 前");
                Object rev = method.invoke(studentService,objects);
                System.out.println("CjlibTest.intercept 後");
                return rev;
            }
        };

        enhancer.setCallback(methodInterceptor);

        StudentService studentServiceProxy = (StudentService) enhancer.create();

        studentServiceProxy.login("zhangsan", "211111");
        //studentServiceProxy.login("yuhl", "3333");
    }
}

  1. 測試結果
CjlibTest.intercept 前
StudentService.login
CjlibTest.intercept 後

6.另外一個大問題,那麼這個代理物件實在什麼時候建立的呢?

我們回頭想想應該不難想到肯定實在BeanPostProcessor的時候被建立的,看下圖:
在這裡插入圖片描述

  1. ProxyBeanPostProcessor.java程式碼
package proxy.service;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author yuhl
 * @Date 2020/11/3 23:31
 * @Classname ProxyBeanPostProcessor
 * @Description 模擬spring底層生成代理物件的過程
 */
public class ProxyBeanPostProcessor implements BeanPostProcessor {
    proxy.service.UserService userService = new proxy.service.impl.UserServiceImpl();
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    /**
     *
     * @param bean 被修改的bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), userService.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("ProxyBeanPostProcessor.invoke 前----------》");
                Object rev = method.invoke(userService, args);
                System.out.println("ProxyBeanPostProcessor.invoke 後----------》");
                return rev;
            }
        });
       // return bean;
    }
}

  1. 配置檔案
<?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:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       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/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="userService" class="proxy.service.impl.UserServiceImpl"/>

    <!--模擬spring生成代理物件的過程-->
    <bean id="proxyBeanPostProcessor" class="proxy.service.ProxyBeanPostProcessor"/>

</beans>
  1. 測試
    @Test
    public void test5() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext7.xml");
        proxy.service.UserService userService = (proxy.service.UserService)ctx.getBean("userService");
        userService.login("zhangsan","111111");
        userService.regester(new User(2, "222222"));
    }
  1. 測試結果
    @Test
    public void test5() {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("/applicationContext7.xml");
        proxy.service.UserService userService = (proxy.service.UserService)ctx.getBean("userService");
        userService.login("zhangsan","111111");
        userService.regester(new User(2, "222222"));
    }

7.總結

到此我們明白了jdk代理和cglib代理的全部過程,代理均需要滿足三個條件:

  1. 代理隊形
  2. 介面(父類)
  3. 增強功能,
    同時模擬了工廠在何時建立了代理物件就是在postProcessAfterInitialization的after方法的時候建立的。

相關文章