购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

3.2 动态代理机制与Spring AOP

在Java的世界中,实现AOP的主流方式是采用动态代理(Dynamic Proxy)机制,这点对于Spring AOP也一样。代理机制的主要目的就是为其他对象提供一种代理以控制对当前对象的访问,用于消除或缓解直接访问对象带来的问题。通过这种手段,一个对象就代表另一个对象的部分功能,我们创建包含当前对象的对象,以便向外界提供功能接口。本节将关注目前主流的动态代理实现技术,并分析Spring AOP中的代理实现方式。

在Spring中,采用的代理机制有两种,即JDK动态代理和CGLIB动态代理。为了介绍动态代理机制,这里我们引入一个具体的应用场景。考虑一个Account接口,它包含一个用于图片展示的open()方法,如代码清单3-5所示。

代码清单3-5 Account接口代码

public interface Account{
    void open();
}

然后针对该接口有一个实现类RealAccount,其中的方法实现只用于模拟,不包含具体业务,如代码清单3-6所示。

代码清单3-6 Account接口实现类代码

public class RealAccount implements Account {
    private String name;

    public RealAccount(String name) {
        this.name = name;
    }

    @Override
    public void open() {
        System.out.println("Open the account for:" + name);
    }
}

现在,假设需要在执行RealAccount的open()方法的前后分别打印日志信息。我们接下来讨论如何分别基于JDK动态代理和CGLIB动态代理来实现这一目标。

3.2.1 JDK动态代理

在JDK自带的动态代理中存在一个InvocationHandler接口,我们首先要做的就是提供一个该接口的实现类,如代码清单3-7所示。

代码清单3-7 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[] arg)
            throws Throwable {
        Object result = null;
        doBefore();
        result = method.invoke(obj, arg);
        doAfter();
        return result;
    }

    public void doBefore() {
        System.out.println("开户前");
    }

    public void doAfter() {
        System.out.println("开户后");
    }
}

InvocationHandler接口中包含一个invoke()方法,我们必须实现这一方法。在该方法中,通常需要调用method.invoke()方法执行原有对象的代码逻辑,然后可以在该方法前后添加相应的代理实现。在上述代码中,我们只是简单打印了日志。

然后,编写测试类来验证上述AccountHandler类的执行效果,如代码清单3-8所示。

代码清单3-8 AccountHandler测试类代码

public class AccountTest {
    public static void main(String[] args) {
        Account account = new RealAccount("tianyalan");
        InvocationHandler handler = new AccountHandler(account);

        Account proxy = (Account)Proxy.newProxyInstance(
            account.getClass().getClassLoader(),
            account.getClass().getInterfaces(),
            handler);
        proxy.open();
    }
}

这里的Proxy.newProxyInstance()方法的作用就是生成代理类。当该方法被调用时,RealAccount类的实例被传入。然后当代理类的open()方法被调用时,AccountHandler中invoke()方法就会被触发,从而实现代理机制。这里的类层次结构如图3-4所示。

066-1

图3-4 JDK动态代理类层次结构示意图

仔细分析上述代码结构,可以发现其遵循“设计并实现业务接口→实现Handler→创建代理类”这一流程,然后在Handler中构建具体的代理逻辑。上述流程也是代表了基本的代理机制实现流程。联想一下,很多基于AOP机制的拦截器底层实际上就是类似的原理。

3.2.2 CGLIB动态代理

CGLIB是一个Java字节码生成库,提供了易用的API对Java字节码进行创建和修改。我们现在尝试用CGLIB来代理前面的RealAccount类,如代码清单3-9所示。

代码清单3-9 AccountCglibProxy类代码

public class AccountCglibProxy implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();

    public Object getProxy(Class<?> clazz) {
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before");
        Object object = proxy.invokeSuper(obj, args);
        System.out.println("after");
        return object;
    }
}

上述代码中的Enhancer类是CGLIB中最常用的一个类,类似于前面介绍的JDK动态代理中的Proxy类。和Proxy只能代理接口不同,Enhancer既能够代理接口,也能够代理普通类,但不能拦截final类和方法。在这里,我们实现了MethodInterceptor中的intercept()方法以提供代理逻辑。

AccountCglibProxy类的使用方法也比较简单,如代码清单3-10所示。

代码清单3-10 AccountCglibProxy类使用方法代码

AccountCglibProxy proxy = new AccountCglibProxy();
RealAccount account = (RealAccount) proxy.getProxy(RealAccount.class);
account.open();

作为对比,我们用表3-1展示了JDK动态代理和CGLIB动态代理之间的区别。

表3-1 JDK动态代理和CGLIB动态代理对比

067-1

3.2.3 ProxyFactoryBean

JDK自带的动态代理以及基于CGLIB的动态代理在Spring框架中都得到了应用,最典型的应用场景就是实现AOP。Spring专门提供了一个ProxyFactoryBean类用于手动创建对象代理,并将创建的代理对象作为目标对象的AOP代理。

ProxyFactoryBean提供了一组配置属性用于指定代理的执行行为,比较常见的包括proxyTargetClass和exposeProxy。如果proxyTargetClass属性为true,则仅使用CGLIB创建代理。如果该属性未设置,那么有两种情况:如果目标类实现了接口,则将使用JDK创建代理;反之,将使用CGLIB创建代理。而exposeProxy属性用于设置是否将当前代理暴露给ThreadLocal。如果该属性为true,那么开发人员可以使用AopContext.currentProxy()方法来获取代理对象。

接下来,我们演示如何使用ProxyFactoryBean来创建和管理代理对象。我们继续沿用3.1.2节中所介绍的案例场景。现在让我们为MethodBeforeAdvice接口提供一个实现类。显然从命名上看,这个实现类是方法执行前通知的,如代码清单3-11所示。

代码清单3-11 MethodBeforeAdvice接口实现类代码

public class AccountTransactionInterceptor implements MethodBeforeAdvice{
    private static final Logger LOGGER = Logger.getLogger(AccountTransactionInterceptor.class);

    @Override
    public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
        LOGGER.info("账户交易被拦截");
    }
}

接着,我们通过Java代码创建一个通知,实现方式如代码清单3-12所示。

代码清单3-12 Advisor实现类代码

@Bean
public Advisor accountServiceAdvisor() {
    AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
    pointcut.setExpression("execution(* com.springboot.aop.service.AccountService.doAccountTransaction(..))");
    return new DefaultPointcutAdvisor(pointcut, new AccountTransactionInterceptor());
}

最后,我们创建一个ProxyFactoryBean实例,并设置相关属性,如代码清单3-13所示。

代码清单3-13 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()方法来获取这个代理对象,示例代码如代码清单3-14所示。

代码清单3-14 AccountService实现类代码 uq/Fl7QJRz+3n0kFAAsA9mvtuc0hOnbL/Ry2poRFTleB/+MQRh7QoTsLeNzXeaGL

public class AccountServiceImpl implements AccountService{
    ...
    @Override
    public boolean doAccountTransaction(Account source, Account dest, int amount) {
        ((AccountService)(AopContext.currentProxy())).doAccountTransaction(source, dest, amount);
        return true;
    }
}
点击中间区域
呼出菜单
上一章
目录
下一章
×