1 概述
在Java的世界中,实现AOP的主流方式是采用动态代理机制,这点对于Spring AOP也一样。代理机制的主要目的就是为其他对象提供一种dialing以控制对当前对象的访问,用于消除或缓解直接访问对象带来的问题。通过这种手段,一个对象就代表另一个对象的部分功能,我们创建包含当前对象的对象,以便向外界提供功能接口。本篇将关注目前主流的动态代理实现技术,并分析Spring AOP中的代理实现方式。
在Spring中,采用的代理机制有两种,即JDK动态代理和CGLIB动态代理。为了介绍动态代理机制,引入一个具体的应用场景。考虑一个Account接口,它包含一个用于图片展示的open方法,代码如下:
public interface Account {
void open();
}
然后针对该接口有一个实现类RealAccount,其中的方法只用于模拟,不包括具体业务,代码如下:
public class RealAccount implements Account {
private String name;
public RealAccount(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void open() {
System.out.println("打开:" + name + ",账户");
}
}
现在,假设需要在执行RealAccount的open()方法的前后分别打印日志信息。接下来讨论如何分别基于JDK动态代理和CGLIB动态代理来实现这个目标。
2 JDK动态代理
在JDK自带的动态代理中存在一个InvocationHandler接口,首先要做的就是提供一个该接口的实现类,代码如下:
public class AccountHandler implements InvocationHandler {
private Object obj;
public AccountHandler(Object obj) {
super();
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null;
doBefore();
result = method.invoke(obj,args);
doAfter();
return result;
}
private void doAfter() {
System.out.println("开户后");
}
private void doBefore() {
System.out.println("开户前");
}
}
InvocationHandler接口中包含一个invoke()方法,必须实现这个方法。在这个方法中,通常需要调用method.invoke()方法执行原有对象的代码逻辑,然后可以在该方法前后添加相应的代理实现。在上述代码中,只是简答的打印了日志。然后编写测试类来验证执行结果,测试类代码如下:
public class Test {
public static void main(String[] args) {
Account account = new RealAccount("zhangsan");
AccountHandler handler = new AccountHandler(account);
Account proxy = (Account) Proxy.newProxyInstance(
account.getClass().getClassLoader(),
account.getClass().getInterfaces(),
handler
);
proxy.open();
}
}
运行结果如下:
这里的Proxy.newProxyInstance()方法的作用就是生成代理类。当该方法被调用时,RealAccount类的实例被传入。然后当代理类的open()方法被调用时,AccountHandler中invoke方法就会被触发,从而实现代理机制。这里的类层次结构图如下:
咨询分析上述代码结构,可以发现其遵循“设计并实现业务接口——实现Handler——创建代理类”这个流程,然后在Handler中构建具体的代理逻辑。上述流程也是代表了最基本的代理机制实现流程。联想一下,很多基于AOP机制的拦截器底层实际上就是类似的原理。
3 CGLIB动态代理
CGLIB是一个Java字节码生成库,提供了易用的API对Java字节码进行创建和修改。现在尝试拥CGLIB来代理前面的RealAccount类,代码清单如下:
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class AccountCglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class<?> clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("之前");
methodProxy.invokeSuper(o,objects);
System.out.println("之后");
return o;
}
}
上述代码中的Enhancer类是CGLIB中最常用的一个类,类似于前面介绍的JDK动态代理中的Proxy类。和Proxy只能代理接口不同,Enhancer既能够代理类接口,也能够代理不同类,但不能拦截final类和方法。在这里,我们实现了MethoInterceptor中的intercept()方法以提供代理逻辑。AccountCglibProxy类的使用方法也比较简单,测试代码如下:
public class CglibProxyTest {
public static void main(String[] args) {
AccountCglibProxy proxy = new AccountCglibProxy();
RealAccount account = (RealAccount) proxy.getProxy(RealAccount.class);
account.open();
}
}
作为对比,下表展示了JDK动态代理和CGLIB动态代理之间的区别。
4 ProxyFactoryBean
JDK自带的动态代理以及基于CGLIB的动态代理在Spring框架中都得到了应用,最典型的应用场景就是实现AOP。Spring专门提供了一个ProxyFactoryBean类用于手动创建对象代理,并将创建的代理对象作为目标对象的AOP代理。
ProxyFactoryBean提供了一组配置属性用于指定代理的执行行为,比较常见的包括proxyTargetClass和exposeProxy。如果proxyTargetClass属性为true,则仅使用CGLIB创建代理。如果该属性未设置,那么有两种情况:如果目标实现了接口,则将使用JDK创建代理;反之,将使用CGLIB创建代理。而exposeProxy属性用于设置是否将当前代理暴露给ThreadLocal。如果该属性为true,那么开发人员可以使用AopContext.currentProxy()方法来获取代理对象。
接下来,将演示如何使用ProxyFactoryBean来创建和管理对象。继续使用前面的场景,现在为MethodBeforeAdvice接口提供了一个实现类。显然从命名上看,这个实现类是方法执行前通知的,代码清单如下:
import com.sun.istack.internal.logging.Logger;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class AccountTransactionInterceptor implements MethodBeforeAdvice {
private static final Logger LOGGER = Logger.getLogger(AccountTransactionInterceptor.class);
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
LOGGER.info("账户交易被拦截");
}
}
接着通过Java代码创建一个通知,实现方式如下:
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
public class MyAdvisor {
@Bean
public Advisor accountServiceAdvisor(){
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("execution(*com.jay.aop.service.AccountService.doAccountTransaction(..))");
return new DefaultPointcutAdvisor(pointcut,new AccountTransactionInterceptor());
}
}
最后,创建一个ProxyFactoryBean实例,并设置相关属性,代码如下:
@Bean
public ProxyFactoryBean accountService(){
ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
proxyFactoryBean.setTarget(new AccountServiceImpl());
proxyFactoryBean.addAdvisor(accountServiceAdvisor());
proxyFactoryBean.setExposeProxy(true);
return proxyFactoryBean;
}
注意,这里设置目标类为AccountService接口的实现类AccountServiceImpl,并把exposeProxy属性设置为true。这样,在AccountServiceImpl中就可以使用Spring AOP提供的AopContext.currentProxy方法来获取这个代理对象,实例代码如下:
public class AccountServiceImpl implements AccountService {
private static final Logger LOGGER = Logger.getLogger(AccountServiceImpl.class);
@Override
public boolean doAccountTransaction(Account source, Account dest, int amount) throws MinimumAccountException {
((AccountService)(AopContext.currentProxy())).doAccountTransaction(source,dest,amount);
return true;
}
}