Spring框架系列(11) - Spring AOP實現原理詳解之Cglib代理實現

語言: CN / TW / HK

我們在前文中已經介紹了SpringAOP的切面實現和建立動態代理的過程,那麼動態代理是如何工作的呢?本文主要介紹Cglib動態代理的案例和SpringAOP實現的原理。@pdai

引入

我們在前文中已經介紹了SpringAOP的切面實現和建立動態代理的過程,那麼動態代理是如何工作的呢?本文主要介紹Cglib動態代理的案例和SpringAOP實現的原理。

要了解動態代理是如何工作的,首先需要了解

  • 什麼是代理模式?
  • 什麼是動態代理?
  • 什麼是Cglib?
  • SpringAOP和Cglib是什麼關係?

動態代理要解決什麼問題?

什麼是代理?

代理模式(Proxy pattern): 為另一個物件提供一個替身或佔位符以控制對這個物件的訪問

舉個簡單的例子:

我(client)如果要買(doOperation)房,可以找中介(proxy)買房,中介直接和賣方(target)買房。中介和賣方都實現買賣(doOperation)的操作。中介就是代理(proxy)。

什麼是動態代理?

動態代理就是,在程式執行期,建立目標物件的代理物件,並對目標物件中的方法進行功能性增強的一種技術。

在生成代理物件的過程中,目標物件不變,代理物件中的方法是目標物件方法的增強方法。可以理解為執行期間,物件中方法的動態攔截,在攔截方法的前後執行功能操作。

什麼是Cglib? SpringAOP和Cglib是什麼關係?

Cglib是一個強大的、高效能的程式碼生成包,它廣泛被許多AOP框架使用,為他們提供方法的攔截。

  • 最頂層是位元組碼,位元組碼相關的知識請參考 JVM基礎 - 類位元組碼詳解
  • ASM是操作位元組碼的工具
  • cglib基於ASM位元組碼工具操作位元組碼(即動態生成代理,對方法進行增強)
  • SpringAOP基於cglib進行封裝,實現cglib方式的動態代理

Cglib代理的案例

這裡我們寫一個使用cglib的簡單例子。@pdai

pom包依賴

引入cglib的依賴包

```xml

tech-pdai-spring-demos tech.pdai 1.0-SNAPSHOT 4.0.0

<artifactId>007-spring-framework-demo-aop-proxy-cglib</artifactId>

<properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
    <!-- http://mvnrepository.com/artifact/cglib/cglib -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.3.0</version>
    </dependency>
</dependencies>

```

定義實體

User

```java package tech.pdai.springframework.entity;

/* * @author pdai / public class User {

/**
 * user's name.
 */
private String name;

/**
 * user's age.
 */
private int age;

/**
 * init.
 *
 * @param name name
 * @param age  age
 */
public User(String name, int age) {
    this.name = name;
    this.age = age;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

public int getAge() {
    return age;
}

public void setAge(int age) {
    this.age = age;
}

@Override
public String toString() {
    return "User{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
}

} ```

被代理的類

即目標類, 對被代理的類中的方法進行增強

```java package tech.pdai.springframework.service;

import java.util.Collections; import java.util.List;

import tech.pdai.springframework.entity.User;

/* * @author pdai / public class UserServiceImpl {

/**
 * find user list.
 *
 * @return user list
 */
public List<User> findUserList() {
    return Collections.singletonList(new User("pdai", 18));
}

/**
 * add user
 */
public void addUser() {
    // do something
}

} ```

cglib代理

cglib代理類,需要實現MethodInterceptor介面,並指定代理目標類target

```java package tech.pdai.springframework.proxy;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;

/* * This class is for proxy demo. * * @author pdai / public class UserLogProxy implements MethodInterceptor {

/**
 * 業務類物件,供代理方法中進行真正的業務方法呼叫
 */
private Object target;

public Object getUserLogProxy(Object target) {
    //給業務物件賦值
    this.target = target;
    //建立加強器,用來建立動態代理類
    Enhancer enhancer = new Enhancer();
    //為加強器指定要代理的業務類(即:為下面生成的代理類指定父類)
    enhancer.setSuperclass(this.target.getClass());
    //設定回撥:對於代理類上所有方法的呼叫,都會呼叫CallBack,而Callback則需要實現intercept()方法進行攔
    enhancer.setCallback(this);
    // 建立動態代理類物件並返回
    return enhancer.create();
}

// 實現回撥方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
    // log - before method
    System.out.println("[before] execute method: " + method.getName());

    // call method
    Object result = proxy.invokeSuper(obj, args);

    // log - after method
    System.out.println("[after] execute method: " + method.getName() + ", return value: " + result);
    return null;
}

} ```

使用代理

啟動類中指定代理目標並執行。

```java package tech.pdai.springframework;

import tech.pdai.springframework.proxy.UserLogProxy; import tech.pdai.springframework.service.UserServiceImpl;

/* * Cglib proxy demo. * * @author pdai / public class ProxyDemo {

/**
 * main interface.
 *
 * @param args args
 */
public static void main(String[] args) {
    // proxy
    UserServiceImpl userService = (UserServiceImpl) new UserLogProxy().getUserLogProxy(new UserServiceImpl());

    // call methods
    userService.findUserList();
    userService.addUser();
}

} ```

簡單測試

我們啟動上述類main 函式,執行的結果如下:

bash [before] execute method: findUserList [after] execute method: findUserList, return value: [User{name='pdai', age=18}] [before] execute method: addUser [after] execute method: addUser, return value: null

Cglib代理的流程

我們把上述Demo的主要流程畫出來,你便能很快理解

更多細節:

  • 在上圖中,我們可以通過在Enhancer中配置更多的引數來控制代理的行為,比如如果只希望增強這個類中的一個方法(而不是所有方法),那就增加callbackFilter來對目標類中方法進行過濾;Enhancer可以有更多的引數類配置其行為,不過我們在學習上述主要的流程就夠了。
  • final方法為什麼不能被代理?很顯然final方法沒法被子類覆蓋,當然不能代理了。
  • Mockito為什麼不能mock靜態方法?因為mockito也是基於cglib動態代理來實現的,static方法也不能被子類覆蓋,所以顯然不能mock。但PowerMock可以mock靜態方法,因為它直接在bytecode上工作,更多可以看Mockito單元測試。(pdai: 通了沒?是不是so easy...)

SpringAOP中Cglib代理的實現

SpringAOP封裝了cglib,通過其進行動態代理的建立。

我們看下CglibAopProxy的getProxy方法

```java @Override public Object getProxy() { return getProxy(null); }

@Override public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { logger.trace("Creating CGLIB proxy: " + this.advised.getTargetSource()); }

try { Class<?> rootClass = this.advised.getTargetClass(); Assert.state(rootClass != null, "Target class must be available for creating a CGLIB proxy");

// 上面流程圖中的目標類
Class<?> proxySuperClass = rootClass;
if (rootClass.getName().contains(ClassUtils.CGLIB_CLASS_SEPARATOR)) {
  proxySuperClass = rootClass.getSuperclass();
  Class<?>[] additionalInterfaces = rootClass.getInterfaces();
  for (Class<?> additionalInterface : additionalInterfaces) {
    this.advised.addInterface(additionalInterface);
  }
}

// Validate the class, writing log messages as necessary.
validateClassIfNecessary(proxySuperClass, classLoader);

// 重點看這裡,就是上圖的enhancer,設定各種引數來構建
Enhancer enhancer = createEnhancer();
if (classLoader != null) {
  enhancer.setClassLoader(classLoader);
  if (classLoader instanceof SmartClassLoader &&
      ((SmartClassLoader) classLoader).isClassReloadable(proxySuperClass)) {
    enhancer.setUseCache(false);
  }
}
enhancer.setSuperclass(proxySuperClass);
enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised));
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(classLoader));

// 設定callback回撥介面,即方法的增強點
Callback[] callbacks = getCallbacks(rootClass);
Class<?>[] types = new Class<?>[callbacks.length];
for (int x = 0; x < types.length; x++) {
  types[x] = callbacks[x].getClass();
}
// 上節說到的filter
enhancer.setCallbackFilter(new ProxyCallbackFilter(
    this.advised.getConfigurationOnlyCopy(), this.fixedInterceptorMap, this.fixedInterceptorOffset));
enhancer.setCallbackTypes(types);

// 重點:建立proxy和其例項
return createProxyClassAndInstance(enhancer, callbacks);

} catch (CodeGenerationException | IllegalArgumentException ex) { throw new AopConfigException("Could not generate CGLIB subclass of " + this.advised.getTargetClass() + ": Common causes of this problem include using a final class or a non-visible class", ex); } catch (Throwable ex) { // TargetSource.getTarget() failed throw new AopConfigException("Unexpected AOP exception", ex); } } ```

獲取callback的方法如下,提幾個理解的要點吧,具體讀者在學習的時候建議把我的例子跑一下,然後打一個斷點進行理解。

  • rootClass: 即目標代理類
  • advised: 包含上文中我們獲取到的advisor增強器的集合
  • exposeProxy: 在xml配置檔案中配置的,背景就是如果在事務A中使用了代理,事務A呼叫了目標類的的方法a,在方法a中又呼叫目標類的方法b,方法a,b同時都是要被增強的方法,如果不配置exposeProxy屬性,方法b的增強將會失效,如果配置exposeProxy,方法b在方法a的執行中也會被增強了
  • DynamicAdvisedInterceptor: 攔截器將advised(包含上文中我們獲取到的advisor增強器)構建配置的AOP的callback(第一個callback)
  • targetInterceptor: xml配置的optimize屬性使用的(第二個callback)
  • 最後連同其它5個預設的Interceptor 返回作為cglib的攔截器鏈,之後通過CallbackFilter的accpet方法返回的索引從這個集合中返回對應的攔截增強器執行增強操作。

```java private Callback[] getCallbacks(Class<?> rootClass) throws Exception { // Parameters used for optimization choices... boolean exposeProxy = this.advised.isExposeProxy(); boolean isFrozen = this.advised.isFrozen(); boolean isStatic = this.advised.getTargetSource().isStatic();

// Choose an "aop" interceptor (used for AOP calls). Callback aopInterceptor = new DynamicAdvisedInterceptor(this.advised);

// Choose a "straight to target" interceptor. (used for calls that are // unadvised but can return this). May be required to expose the proxy. Callback targetInterceptor; if (exposeProxy) { targetInterceptor = (isStatic ? new StaticUnadvisedExposedInterceptor(this.advised.getTargetSource().getTarget()) : new DynamicUnadvisedExposedInterceptor(this.advised.getTargetSource())); } else { targetInterceptor = (isStatic ? new StaticUnadvisedInterceptor(this.advised.getTargetSource().getTarget()) : new DynamicUnadvisedInterceptor(this.advised.getTargetSource())); }

// Choose a "direct to target" dispatcher (used for // unadvised calls to static targets that cannot return this). Callback targetDispatcher = (isStatic ? new StaticDispatcher(this.advised.getTargetSource().getTarget()) : new SerializableNoOp());

Callback[] mainCallbacks = new Callback[] { aopInterceptor, // targetInterceptor, // invoke target without considering advice, if optimized new SerializableNoOp(), // no override for methods mapped to this targetDispatcher, this.advisedDispatcher, new EqualsInterceptor(this.advised), new HashCodeInterceptor(this.advised) };

Callback[] callbacks;

// If the target is a static one and the advice chain is frozen, // then we can make some optimizations by sending the AOP calls // direct to the target using the fixed chain for that method. if (isStatic && isFrozen) { Method[] methods = rootClass.getMethods(); Callback[] fixedCallbacks = new Callback[methods.length]; this.fixedInterceptorMap = CollectionUtils.newHashMap(methods.length);

// TODO: small memory optimization here (can skip creation for methods with no advice)
for (int x = 0; x < methods.length; x++) {
  Method method = methods[x];
  List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, rootClass);
  fixedCallbacks[x] = new FixedChainStaticTargetInterceptor(
      chain, this.advised.getTargetSource().getTarget(), this.advised.getTargetClass());
  this.fixedInterceptorMap.put(method, x);
}

// Now copy both the callbacks from mainCallbacks
// and fixedCallbacks into the callbacks array.
callbacks = new Callback[mainCallbacks.length + fixedCallbacks.length];
System.arraycopy(mainCallbacks, 0, callbacks, 0, mainCallbacks.length);
System.arraycopy(fixedCallbacks, 0, callbacks, mainCallbacks.length, fixedCallbacks.length);
this.fixedInterceptorOffset = mainCallbacks.length;

} else { callbacks = mainCallbacks; } return callbacks; } ```

可以結合除錯,方便理解

示例原始碼

http://github.com/realpdai/tech-pdai-spring-demos

更多文章

首先, 從Spring框架的整體架構和組成對整體框架有個認知。

  • Spring基礎 - Spring和Spring框架組成
  • Spring是什麼?它是怎麼誕生的?有哪些主要的元件和核心功能呢? 本文通過這幾個問題幫助你構築Spring和Spring Framework的整體認知。

其次,通過案例引出Spring的核心(IoC和AOP),同時對IoC和AOP進行案例使用分析。

基於Spring框架和IOC,AOP的基礎,為構建上層web應用,需要進一步學習SpringMVC。

  • Spring基礎 - SpringMVC請求流程和案例
  • 前文我們介紹了Spring框架和Spring框架中最為重要的兩個技術點(IOC和AOP),那我們如何更好的構建上層的應用呢(比如web 應用),這便是SpringMVC;Spring MVC是Spring在Spring Container Core和AOP等技術基礎上,遵循上述Web MVC的規範推出的web開發框架,目的是為了簡化Java棧的web開發。 本文主要介紹SpringMVC的請求流程和基礎案例的編寫和執行。

Spring進階 - IoC,AOP以及SpringMVC的原始碼分析

  • Spring進階 - Spring IOC實現原理詳解之IOC體系結構設計
  • 在對IoC有了初步的認知後,我們開始對IOC的實現原理進行深入理解。本文將幫助你站在設計者的角度去看IOC最頂層的結構設計
  • Spring進階 - Spring IOC實現原理詳解之IOC初始化流程
  • 上文,我們看了IOC設計要點和設計結構;緊接著這篇,我們可以看下原始碼的實現了:Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的
  • Spring進階 - Spring IOC實現原理詳解之Bean例項化(生命週期,迴圈依賴等)
  • 上文,我們看了IOC設計要點和設計結構;以及Spring如何實現將資源配置(以xml配置為例)通過載入,解析,生成BeanDefination並註冊到IoC容器中的;容器中存放的是Bean的定義即BeanDefinition放到beanDefinitionMap中,本質上是一個ConcurrentHashMap<String, Object>;並且BeanDefinition介面中包含了這個類的Class資訊以及是否是單例等。那麼如何從BeanDefinition中例項化Bean物件呢,這是本文主要研究的內容?
  • Spring進階 - Spring AOP實現原理詳解之切面實現
  • 前文,我們分析了Spring IOC的初始化過程和Bean的生命週期等,而Spring AOP也是基於IOC的Bean載入來實現的。本文主要介紹Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor,為後續交給代理增強實現做準備的過程)。
  • Spring進階 - Spring AOP實現原理詳解之AOP代理
  • 上文我們介紹了Spring AOP原理解析的切面實現過程(將切面類的所有切面方法根據使用的註解生成對應Advice,並將Advice連同切入點匹配器和切面類等資訊一併封裝到Advisor)。本文在此基礎上繼續介紹,代理(cglib代理和JDK代理)的實現過程。
  • Spring進階 - Spring AOP實現原理詳解之Cglib代理實現
  • 我們在前文中已經介紹了SpringAOP的切面實現和建立動態代理的過程,那麼動態代理是如何工作的呢?本文主要介紹Cglib動態代理的案例和SpringAOP實現的原理。
  • Spring進階 - Spring AOP實現原理詳解之JDK代理實現
  • 上文我們學習了SpringAOP Cglib動態代理的實現,本文主要是SpringAOP JDK動態代理的案例和實現部分。
  • Spring進階 - SpringMVC實現原理之DispatcherServlet初始化的過程
  • 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第一篇:DispatcherServlet的初始化過程的原始碼解析。
  • Spring進階 - SpringMVC實現原理之DispatcherServlet處理請求的過程
  • 前文我們有了IOC的原始碼基礎以及SpringMVC的基礎,我們便可以進一步深入理解SpringMVC主要實現原理,包含DispatcherServlet的初始化過程和DispatcherServlet處理請求的過程的原始碼解析。本文是第二篇:DispatcherServlet處理請求的過程的原始碼解析。