写点什么

大牛一次带你彻底搞懂 Spring 核心容器的动态代理机制与 SpringAOP

  • 2023-06-17
    湖南
  • 本文字数:3093 字

    阅读完需:约 10 分钟

动态代理机制与 Spring AOP

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


本节将关注目前主流的动态代理实现技术,并分析 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;  }	@Override	public void open() {		System.out.println("Open the account for:" + name);	}}
复制代码

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

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[] 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 类的执行效果,代码如下所示:

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()方法就会被触发,从而实现代理机制。这里的类层次结构如下图所示:

仔细分析上述代码结构,可以发现其遵循“设计并实现业务接口→实现 Handler→创建代理类”这一流程,然后在 Handler 中构建具体的代理逻辑。


上述流程也是代表了基本的代理机制实现流程。联想一下,很多基于 AOP 机制的拦截器底层实际上就是类似的原理。

CGLIB 动态代理

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

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 类的使用方法也比较简单,代码如下所示:

AccountCglibProxy proxy = new AccountCglibProxy();RealAccount account = (RealAccount) proxy.getProxy(RealAccount.class);account.open();
复制代码

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

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 接口提供一个实现类。显然从命名上看,这个实现类是方法执行前通知的,代码如下所示:

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 代码创建一个通知,实现方式如下所示:

@Beanpublic Advisor accountServiceAdvisor() {	AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();	pointcut.setExpression("execution(*com.springboot.aop.service.AccountService.doAccountTransaction(..))");	return new DefaultPointcutAdvisor(pointcut, new AccountTransactionInterceptor());}
复制代码

最后,我们创建一个 ProxyFactoryBean 实例,并设置相关属性,代码如下所示:

@Beanpublic 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 所示:

public class AccountServiceImpl implements AccountService{	...	@Override	public boolean doAccountTransaction(Account source, Account dest,int amount) {		((AccountService)(AopContext.currentProxy())).doAccountTransaction(source, dest,amount);		return true;	}}
复制代码


用户头像

加VX:bjmsb02 凭截图即可获取 2020-06-14 加入

公众号:程序员高级码农

评论

发布
暂无评论
大牛一次带你彻底搞懂Spring核心容器的动态代理机制与SpringAOP_互联网架构师小马_InfoQ写作社区