Spring 容器AOP的实现原理——动态代理

本文深入探讨Spring AOP的实现原理,即动态代理技术,包括JDK动态代理和cglib动态代理。重点介绍了JDK动态代理的实现过程,通过示例代码展示了如何使用InvocationHandler和Proxy生成动态代理类。

其实,Spring AOP的实现原理就是动态代理,动态代理其实并不是什么新鲜的东西,学过设计模式的人都应该知道代理模式,代理模式是一种静态代理,而动态代理就是利用反射和动态编译将代理模式变成动态的。原理跟动态注入一样,代理模式在编译的时候就已经确定代理类将要代理谁,而动态代理在运行的时候才知道自己要代理谁。

Spring的动态代理有两种:一是JDK的动态代理;另一个是cglib动态代理(通过修改字节码来实现代理)。今天咱们主要讨论JDK动态代理的方式。JDK的代理方式主要就是通过反射跟动态编译来实现的,下面咱们就通过代码来看看它具体是怎么实现的。

假设我们要对下面这个用户管理进行代理:

//用户管理接口  
package com.tgb.proxy;  

public interface UserMgr {  
    void addUser();  
    void delUser();  
}  

//用户管理的实现  
package com.tgb.proxy;  

public class UserMgrImpl implements UserMgr {  

    @Override  
    public void addUser() {  
        System.out.println("添加用户.....");  
    }  

    @Override  
    public void delUser() {  
        System.out.println("删除用户.....");  
    }  

}  

按照代理模式的实现方式,肯定是用一个代理类,让它也实现UserMgr接口,然后在其内部声明一个UserMgrImpl,然后分别调用addUser和delUser方法,并在调用前后加上我们需要的其他操作。但是这样很显然都是写死的,我们怎么做到动态呢?别急,接着看。 我们知道,要实现代理,那么我们的代理类跟被代理类都要实现同一接口,但是动态代理的话我们根本不知道我们将要代理谁,也就不知道我们要实现哪个接口,那么要怎么办呢?我们只有知道要代理谁以后,才能给出相应的代理类,那么我们何不等知道要代理谁以后再去生成一个代理类呢?想到这里,我们好像找到了解决的办法,就是动态生成代理类!

这时候我们亲爱的反射又有了用武之地,我们可以写一个方法来接收被代理类,这样我们就可以通过反射知道它的一切信息——包括它的类型、它的方法等等(如果你不知道怎么得到,请先去看看我写的反射的博客《反射一》《反射二》)。

JDK动态代理的两个核心分别是InvocationHandler和Proxy,下面我们就用简单的代码来模拟一下它们是怎么实现的:

InvocationHandler接口:

package com.tgb.proxy;  

import java.lang.reflect.Method;  

public interface InvocationHandler {  
    public void invoke(Object o, Method m);  
}  

实现动态代理的关键部分,通过Proxy动态生成我们具体的代理类:

package com.tgb.proxy;  

import java.io.File;  
import java.io.FileWriter;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.Method;  
import java.net.URL;  
import java.net.URLClassLoader;  
import javax.tools.JavaCompiler;  
import javax.tools.StandardJavaFileManager;  
import javax.tools.ToolProvider;  
import javax.tools.JavaCompiler.CompilationTask;  

public class Proxy {  
    /** 
     *  
     * @param infce 被代理类的接口 
     * @param h 代理类 
     * @return 
     * @throws Exception 
     */  
    public static Object newProxyInstance(Class infce, InvocationHandler h) throws Exception {   
        String methodStr = "";  
        String rt = "\r\n";  

        //利用反射得到infce的所有方法,并重新组装  
        Method[] methods = infce.getMethods();    
        for(Method m : methods) {  
            methodStr += "    @Override" + rt +   
                         "    public  "+m.getReturnType()+" " + m.getName() + "() {" + rt +  
                         "        try {" + rt +  
                         "        Method md = " + infce.getName() + ".class.getMethod(\"" + m.getName() + "\");" + rt +  
                         "        h.invoke(this, md);" + rt +  
                         "        }catch(Exception e) {e.printStackTrace();}" + rt +                          
                         "    }" + rt ;  
        }  

        //生成Java源文件  
        String srcCode =   
            "package com.tgb.proxy;" +  rt +  
            "import java.lang.reflect.Method;" + rt +  
            "public class $Proxy1 implements " + infce.getName() + "{" + rt +  
            "    public $Proxy1(InvocationHandler h) {" + rt +  
            "        this.h = h;" + rt +  
            "    }" + rt +            
            "    com.tgb.proxy.InvocationHandler h;" + rt +                           
            methodStr + rt +  
            "}";  
        String fileName =   
            "d:/src/com/tgb/proxy/$Proxy1.java";  
        File f = new File(fileName);  
        FileWriter fw = new FileWriter(f);  
        fw.write(srcCode);  
        fw.flush();  
        fw.close();  

        //将Java文件编译成class文件  
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();  
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);  
        Iterable units = fileMgr.getJavaFileObjects(fileName);  
        CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);  
        t.call();  
        fileMgr.close();  

        //加载到内存,并实例化  
        URL[] urls = new URL[] {new URL("file:/" + "d:/src/")};  
        URLClassLoader ul = new URLClassLoader(urls);  
        Class c = ul.loadClass("com.tgb.proxy.$Proxy1");  

        Constructor ctr = c.getConstructor(InvocationHandler.class);  
        Object m = ctr.newInstance(h);  

        return m;  
    }  

}  

这个类的主要功能就是,根据被代理对象的信息,动态组装一个代理类,生成$Proxy1.java文件,然后将其编译成$Proxy1.class。这样我们就可以在运行的时候,根据我们具体的被代理对象生成我们想要的代理类了。这样一来,我们就不需要提前知道我们要代理谁。也就是说,你想代理谁,想要什么样的代理,我们就给你生成一个什么样的代理类。

然后,在客户端我们就可以随意的进行代理了。

package com.tgb.proxy;  

public class Client {  
    public static void main(String[] args) throws Exception {  
        UserMgr mgr = new UserMgrImpl();  

        //为用户管理添加事务处理  
        InvocationHandler h = new TransactionHandler(mgr);  
        UserMgr u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h);  

        //为用户管理添加显示方法执行时间的功能  
        TimeHandler h2 = new TimeHandler(u);  
        u = (UserMgr)Proxy.newProxyInstance(UserMgr.class,h2);  

        u.addUser();  
        System.out.println("\r\n==========华丽的分割线==========\r\n");  
        u.delUser();  
    }  
}  

运行结果:

开始时间:2014年-07月-15日 15时:48分:54秒  
开启事务.....  
添加用户.....  
提交事务.....  
结束时间:2014年-07月-15日 15时:48分:57秒  
耗时:3秒  

==========华丽的分割线==========  

开始时间:2014年-07月-15日 15时:48分:57秒  
开启事务.....  
删除用户.....  
提交事务.....  
结束时间:2014年-07月-15日 15时:49分:00秒  
耗时:3秒  

这里我写了两个代理的功能,一个是事务处理,一个是显示方法执行时间的代理,当然都是非常简单的写法,只是为了说明这个原理。当然,我们可以想Spring那样将这些AOP写到配置文件,因为之前那篇已经写了怎么通过配置文件注入了,这里就不重复贴了。 到这里,你可能会有一个疑问:你上面说,只要放到容器里的对象,都会有容器的公共服务,我怎么没看出来呢?好,那我们就继续看一下我们的代理功能:

事务处理:

package com.tgb.proxy;  

import java.lang.reflect.Method;  

public class TransactionHandler implements InvocationHandler {  

    private Object target;  

    public TransactionHandler(Object target) {  
        super();  
        this.target = target;  
    }  

    @Override  
    public void invoke(Object o, Method m) {  
        System.out.println("开启事务.....");  
        try {  
            m.invoke(target);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        System.out.println("提交事务.....");  
    }  

}  

从代码中不难看出,我们代理的功能里没有涉及到任何被代理对象的具体信息,这样有什么好处呢?这样的好处就是将代理要做的事情跟被代理的对象完全分开,这样一来我们就可以在代理和被代理之间随意的进行组合了。也就是说同一个功能我们只需要一个。同样的功能只有一个,那么这个功能不就是公共的功能吗?不管容器中有多少给对象,都可以享受容器提供的服务了。这就是容器的好处。


原文链接:http://wiki.jikexueyuan.com/project/ssh-noob-learning/dynamic-proxy.html

### Spring AOP 实现原理 #### 一、AOP 基本概念 面向切面编程(Aspect-Oriented Programming, AOP)是一种增强型的程序设计范式,旨在解决横切关注点的问题。在传统的 OOP 编程中,业务逻辑和通用功能(如日志记录、事务管理等)往往交织在一起,难以分离。而 AOP 提供了一种方法,可以将这些通用功能从业务逻辑中解耦出来。 Spring AOP 是基于代理模式实现的,它允许开发者定义切入点(Pointcut)、通知(Advice)以及连接点(Join Point),从而实现对特定行为的拦截和扩展[^2]。 --- #### 二、Spring AOP 的核心机制 Spring AOP 的核心在于通过代理对象来实现代理模式的功能。以下是其实现的关键部分: 1. **代理的选择** 在创建代理时,Spring 会根据目标类是否实现了接口来决定使用哪种代理技术: - 如果目标类实现了接口,则默认使用 JDK 动态代理。 - 如果目标类没有实现任何接口,则使用 CGLIB 来生成代理类[^4]。 2. **织入过程** 织入是指将切面应用到目标对象的过程。Spring AOP 支持运行时代理的方式完成织入操作,而不是像 AspectJ 那样支持编译期或加载期织入[^1]。 3. **主要组件** Spring AOP 主要由以下几个组件构成: - 切入点(Pointcut):用于匹配连接点的一组规则。 - 连接点(Join Point):程序执行过程中能够被拦截的具体位置。 - 通知(Advice):定义了切面的行为,在连接点上执行的操作。 - 切面(Aspect):包含了切入点和通知的组合体。 - Advisor 和 Introduction:进一步封装的通知形式。 --- #### 三、源码分析 以 `ConfigBeanDefinitionParser` 为例,当配置文件中的 `<aop:config>` 节点被解析时,最终会在 Spring 容器中注册一个名为 `AspectJAwareAdvisorAutoProxyCreator` 的 Bean。这个类的作用如下: - 它是一个特殊的 `BeanPostProcessor`,能够在 Bean 初始化前后对其进行处理。 - 当检测到某个 Bean 存在一个或多个切面时,它会自动为目标 Bean 创建代理实例,并将切面逻辑织入其中[^3]。 下面展示了一个简单的 AOP 源码片段,说明如何判断并选择合适的代理策略: ```java if (targetClass.getInterfaces().length > 0) { // 使用 JDK 动态代理 } else { // 使用 CGLIB 代理 } ``` --- #### 四、生活化比喻 为了更好地理解 Spring AOP 的工作流程,可以用现实生活中的例子进行类比。假设你在一家餐厅用餐,服务员负责为你提供服务(比如送餐、结账)。你可以把服务员看作是“代理”,他们帮助厨师完成了与顾客交互的工作,而厨师则专注于烹饪食物这一核心职责。这种分工协作的思想正是 AOP 所倡导的理念——让不同的角色承担各自的责任。 --- ### 总结 Spring AOP实现依赖于代理技术和织入机制。通过对目标对象的动态代理,它可以灵活地将切面逻辑注入到指定的方法调用或其他连接点处。无论是 JDK 动态代理还是 CGLIB 技术,都为 Spring 提供了强大的底层支撑,使得开发人员可以在不修改原有代码的情况下轻松实现各种跨领域需求。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值