Spring——静态代理、动态代理、拦截器思想

在这里插入图片描述

Spring系列

  1. Spring — Spring简介、入门、配置 , IoC和DI思想
  2. Spring — IoC核心(基于XML)、DI核心(基于XML)
  3. Spring — 使用IoC和DI模拟注册案例、注解配置IoC和DI
  4. Spring — 静态代理、动态代理、拦截器思想
  5. Spring — AOP思想、AOP开发、Pointcut语法、注解配置AOP
  6. Spring — DAO层、Spring JDBC、Spring事务控制
  7. Spring — XML配置事务、注解+XML、纯注解的配置方式
  8. Spring整合MyBatis
  9. Spring Java Config — 组件注册相关注解
  10. Spring Java Config — 常用注解

一、案例分析

1、引出问题

之前我们在Service中写的业务逻辑方法, 对有些方法都要做事务管理,比如save和update操作距离,没有加上事务控制的代码如下:

public class EmployeeServiceImpl implement EmployeeService{
	public void save(){
		// 保存操作
	}
}

加上事务控制之后

public class EmployeeServiceImpl implements EmployeeService{
	public void save(){
		// 打开资源
		// 开启事务
		try{
			// 保存操作
			// 提交事务
		} catch (Exception e) {
			// 回滚事务
		} finally {
			// 释放资源
		}
	}
}

上述问题: 我们在业务层的方法都得处理事务(繁琐的try-catch)
设计上存在的问题:

  • 责任不分离,业务方法应该只需要关系如何完成业务功能;不需要去关心事务管理/日志管理/权限管理等
  • 代码结构重复,维护成本大

2、租房子问题

情况一: 直接联系房东
在这里插入图片描述
情况二: 通过中介
在这里插入图片描述

二、静态代理

代理设计模式

客户端直接使用的都是代理对象,不知道真实对象是谁, 此时代理对象可以在客户端和真实对象之间起到中介的作用;

  • 代理对象完全包含真实对象, 客户端使用的都是代理对象的方法, 和真实对象没有直接关系;
  • 代理模式的职责: 把不是真实对象做的事情从真实对象上撇开----责任清晰;

1、静态代理概述

在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了. 我们成为静态代理
在这里插入图片描述
在这里插入图片描述

2、静态代理的实现

代码

// domain
public class Employee {
}

// ---------------------------------------------

// dao层
public interface EmployeeDao {
    void save(Employee emp);
    void update(Employee emp);
}
public class EmployeeDaoImpl implements EmployeeDao {
    public void save(Employee emp) {
        System.out.println("保存员工");
    }

    public void update(Employee emp) {
        System.out.println("修改员工信息");;
    }
}

// ---------------------------------------------

// service层
public interface EmployeeService {
    void save(Employee emp);
    void update(Employee emp);
}
public class EmployeeServiceImpl implements EmployeeService {

    private EmployeeDao dao;

    public void setDao(EmployeeDao dao) {
        this.dao = dao;
    }

    public void save(Employee emp) {
        dao.save(emp);
        System.out.println("保存成功");
    }

    public void update(Employee emp) {
        dao.update(emp);
        throw new RuntimeException("故意出错");
    }
}

// ---------------------------------------------

// tx包,模拟事务管理器
public class TransactionManager {
    public void begin(){
        System.out.println("开启事务");
    }

    public void commit(){
        System.out.println("提交事务");
    }

    public void rollback(){
        System.out.println("回滚事务");
    }
}

// ---------------------------------------------

// proxy类
public class EmployeeServiceProxy implements EmployeeService {

    private TransactionManager tx; // 事务管理器
    private EmployeeService target; // 真实对象/委托对象

    public void setTarget(EmployeeService target) {
        this.target = target;
    }

    public void setTx(TransactionManager tx) {
        this.tx = tx;
    }

	// 被增强的方法
    public void save(Employee emp) {
        // 开启事务_对save方法的增强
        tx.begin();
        try {
            target.save(emp);
            tx.commit();
        } catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }
    }
	// 被增强的方法
    public void update(Employee emp) {
        tx.begin();
        try {
            target.update(emp);
            tx.commit();
        } catch (Exception e){
            e.printStackTrace();
            tx.rollback();
        }
    }
}

staticProxy.xml

    <bean id="employeeDao" class="com.sunny._01_static_proxy.dao.impl.EmployeeDaoImpl"/>
    <!--构造真实类对象-->
    <!--<bean id="employeeService" class="com.sunny._01_static_proxy.service.impl.EmployeeServiceImpl">
        <property name="dao" ref="employeeDao"/>
    </bean>-->
    <bean id="transactionManager" class="com.sunny._01_static_proxy.tx.TransactionManager"/>

    <!--Spring容器构造代理对象-->
    <bean id="employeeServiceProxy" class="com.sunny._01_static_proxy.proxy.EmployeeServiceProxy">
        <property name="tx" ref="transactionManager"/>
        <property name="target">
            <!--内部bean,真实类对象-->
            <bean class="com.sunny._01_static_proxy.service.impl.EmployeeServiceImpl">
                <property name="dao" ref="employeeDao"/>
            </bean>
        </property>
    </bean>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class StaticProxyTest {

    // class com.sunny._01_static_proxy.proxy.EmployeeServiceProxy
    @Autowired // 注入代理对象
    private EmployeeServiceProxy service;

    @Test
    public void testSave(){
        System.out.println(service.getClass());
        service.save(new Employee());
    }

    @Test
    public void testUpdate1(){
        service.update(new Employee());
    }
}

在这里插入图片描述
在这里插入图片描述

3、静态代理的优缺点

静态代理:在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。

优点:

  • 业务类只需要关注业务逻辑本身,保证了业务类的重用性。
  • 把真实对象隐藏起来了保护真实对象

缺点:

  • 代理对象的某个接口只服务于某一种类型的对象,也就是说每一个真实对象都得创建一 个代理对象。
  • 如果需要代理的方法很多,则要为每一种方法都进行代理处理。
  • 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。

这样就需要使用动态代理了

三、动态代理

  • 静态代理:在程序运行前就已经存在代理类的字节码文件,代理对象和真实对象的关系在运行前就确定了。
  • 动态代理:动态代理类是在程序运行期间由JVM通过反射等机制动态的生成的,所以不存在代理类的字节码文件,代理对象和真实对象的关系是在程序运行时期才确定的。

如何实现动态代理

  • 针对有接口:使用JDK动态代理
  • 针对无接口:使用CGLIB或Javassist组件

1、字节码动态加载

静态代理步骤 :
在这里插入图片描述
在这里插入图片描述
动态代理步骤 :

在这里插入图片描述
如何动态的加载一份字节码:

  • 由于JVM通过字节码的二进制信息加载类的,如果我们在运行期系统中,遵循Java编译系统组织.class文件的格式和结构,生成相应的二进制数据,然后再把这个二进制数据加载转换成对应的类。如此就完成了动态创建一个类的能力;

2、JDK动态代理

在这里插入图片描述
代码

// dao包同上

// service包
public interface EmployeeService1 {
    void save(Employee1 emp);

    void update(Employee1 emp);

    // 测试增加功能不需要对增强类做操作
    void delete(Long l);

    // JDK动态反射,最小单位是类,类中的方法都会被拦截
    void queryAll();
}
public class EmployeeServiceImpl1 implements EmployeeService1 {

    private EmployeeDao1 dao1;

    public void setDao1(EmployeeDao1 dao1) {
        this.dao1 = dao1;
    }

    public void save(Employee1 emp) {
        dao1.save(emp);
        System.out.println("保存成功");
    }

    public void update(Employee1 emp) {
        dao1.update(emp);
        throw new RuntimeException("故意出错");
    }

    public void delete(Long l) {
        System.out.println("删除成功");
    }

    public void queryAll() {
        System.out.println("查询全部");
    }
}

// ---------------------------------------------

// tx包,模拟事务管理器
public class TransactionManager1 {
    public void begin(){
        System.out.println("开启事务");
    }

    public void commit(){
        System.out.println("提交事务");
    }

    public void rollback(){
        System.out.println("回滚事务");
    }
}

// ---------------------------------------------


// tx包, 事务管理器增强
// 事务的增强操作
@SuppressWarnings("all")
public class TransactionManagerAdvice implements java.lang.reflect.InvocationHandler{

    // 代理对象中包含的真实对象(对谁做增强)
    private Object target; // 这里是对EmployeeService作增强
    private TransactionManager1 tx;

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTx(TransactionManager1 tx) {
        this.tx = tx;
    }

    // 创建一个代理对象
    public <T> T getProxyObject() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),// 类加载器,一般跟上真实对象的类加载器
                target.getClass().getInterfaces(),// 真实对象所实现的接口(JDK动态代理必须要求真实对象有接口)
                this);  // 如何做事务增强的对象
    }

    // 如何为真实对象的方法做增强的具体操作
    /*
		proxy: 代理对象
		method: 原始方法(需要被增强的方法)
		args: 原始方法的参数
	*/
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 过滤某些方法(对某些方法不增强)
        if (method.getName().equals("queryAll")){
           return method.invoke(target, args);
        }

        Object ret = null;
        tx.begin(); // 开启事务_ 增强操作
        try {
            //------------------------
            ret = method.invoke(target, args); //调用真实对象的方法
            //------------------------
            tx.commit(); // 增强操作
        } catch (Exception e){
            e.printStackTrace();
            tx.rollback(); // 增强操作
        }
        return ret;
    }

    /*class Xx implements InvocationHandler{
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return null;
        }
    }*/
}

dynamicProxy.xml

    <bean id="employeeDao1" class="com.sunny._02_dynamic_proxy.dao.impl.EmployeeDaoImpl1"/>

    <bean id="transactionManager1" class="com.sunny._02_dynamic_proxy.tx.TransactionManager1"/>

    <bean id="employeeService1" class="com.sunny._02_dynamic_proxy.service.impl.EmployeeServiceImpl1">
        <property name="dao1" ref="employeeDao1"/>
    </bean>

    <!--配置一个事务增强的类-->
    <bean id="transactionManagerAdvice1" class="com.sunny._02_dynamic_proxy.tx.TransactionManagerAdvice">
        <property name="target" ref="employeeService1"/>
        <property name="tx" ref="transactionManager1"/>
    </bean>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class JdkDynamicProxyTest {

    // com.sun.proxy.$Proxy13
    @Autowired
    private TransactionManagerAdvice advice;

    @Test
    public void testSave(){
        // 获取到代理对象
        EmployeeService1 proxy = advice.getProxyObject();
        //System.out.println(proxy.getClass());
        proxy.save(new Employee1());
    }

    @Test
    public void testUpdate(){
        // 获取到代理对象
        EmployeeService1 proxy = advice.getProxyObject();
        proxy.update(new Employee1());
    }

    @Test
    public void testDelete(){
        EmployeeService1 proxy = advice.getProxyObject();
        proxy.delete(1L);
    }

    @Test
    public void testQueryAll1(){
        EmployeeService1 proxy = advice.getProxyObject();
        proxy.queryAll(); // 没有增强该方法,作了判断过滤
    }
}

2.1、JDK动态代理原理

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
注意: 在接口方法Invoke中打印代理对象会造成死循环,实际上打印代理对象就是打印其对象的toString方法,通过上面反编译的代码,toString方法中仍会调用Invoke方法,造成了死循环.


动态代理代码解释:

public class TestJDKProxy {
    /**
     1. 借⽤类加载器  TestJDKProxy 或 UserServiceImpl 都可以
     2. JDK8.x 前必须加 final
     final UserService userService = new UserServiceImpl();
     */
    public static void main(String[] args) {
        // 1. 创建原始对象
        UserService userService = new UserServiceImpl();

        // 2. JDK 动态代理
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("---- proxy log ----");
                // 原始方法运行
                Object ret = method.invoke(userService, args);
                return ret;
            }
        };
        UserService userServiceProxy = (UserService) Proxy.
                newProxyInstance(TestJDKProxy.class.getClassLoader(),
                                userService.getClass().getInterfaces(),
                                handler);

		/* 
			1、首先userServiceProxy是一个代理对象, 只是该对象是通过动态字节码技术, 
			在运行时根据字节码+反射机制, 动态生成一个实现UserService接口的代理类;
			2、该代理对象调用增强的login方法, 首先就会调用到InvocationHandler接口实现类
			的invoke方法, 上面代码是采用匿名内部类的方式来创建该接口实现类对象handler,
			invoke方法就是对目标对象的原始方法做增强的。
			3、method.invoke(), 其中method就是原始方法对象, args就是原始方法的参数,
			当传入method.invoke(目标对象, 原始方法参数), 此时根据多态的动态绑定机制,
			调用的额就是UserServiceImpl中的login方法
			//... register方法同理

		*/
        userServiceProxy.login("zhenyu", "123456");

        userServiceProxy.register(new User());
    }
}

3、CGLIB动态代理

使用JDK的动态代理,只能针对于目标对象存在接口的情况,如果目标对象没有接口, 此时可以考虑使用CGLIB的动态代理方式;

代码:

public class TransactionManagerAdvice2 implements org.springframework.cglib.proxy.InvocationHandler {

    // 代理对象中包含的真实对象(对谁做增强)
    private Object target; // 这里是对EmployeeService作增强
    private TransactionManager2 tx;

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setTx(TransactionManager2 tx) {
        this.tx = tx;
    }

    // 创建一个代理对象
    public <T> T getProxyObject() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 将继承于哪一个类,来做增强
        enhancer.setCallback(this); // 设置增强的对象
        return (T) enhancer.create(); // 创建代理对象
    }

    // 如何为真实对象的方法做增强的具体操作
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = null;
        tx.begin(); // 开启事务_ 增强操作
        try {
            //------------------------
            ret = method.invoke(target, args); //调用真实对象的方法
            //------------------------
            tx.commit(); // 增强操作
        } catch (Exception e){
            e.printStackTrace();
            tx.rollback(); // 增强操作
        }
        return ret;
    }
}
// CGLIB代理对象
com.sunny._03cglib_dynamic_proxy.service.impl.EmployeeServiceImpl2$$EnhancerByCGLIB$$676c99e7

CGLIB原理:
通过生成代理类,然后继承目标类,再对目标类中可以继承的方法做覆盖,并在该方法中做功能增强,实则调用的是子类中的方法;
在这里插入图片描述
两种动态代理底层:
在这里插入图片描述

4、拦截器思想

  • Java里的拦截器是动态拦截Action调用的对象。它提供了一种机制可以使开发者可以定义在一个action执行的前后执行的代码,也可以在一个action执行前阻止其执行,同时也提供了一种可以提取action中可重用部分的方式。在AOP(Aspect-Oriented Programming)中拦截器用于在某个方法或字段被访问之前,进行拦截然后在之前或之后加入某些操作。

在这里插入图片描述
模拟日志记录操作

// 在service方法调用之前,做日志记录
public class LogUtil {

    public void writeLog(String methodClass,String methodName){
        System.out.println(new Date().toLocaleString() + "调用了"+methodClass+"类中的" +methodName + "方法");
    }
}
// 日志增强
public class LogAdvice implements org.springframework.cglib.proxy.MethodInterceptor{

    private Object target; // 真实对象
    private LogUtil logUtil; // 日志工具类

    public void setTarget(Object target) {
        this.target = target;
    }

    public void setLogUtil(LogUtil logUtil) {
        this.logUtil = logUtil;
    }

    // 创建代理对象
    public <T> T getProxyObject() {
        /*Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass()); // 将继承于哪一个类,来做增强
        enhancer.setCallback(this); // 设置增强的对象
        return (T) enhancer.create(); // 创建代理对象*/

        return (T) Enhancer.create(target.getClass(), this);
    }


    /**
     * 如何对方法作增强
     * @param proxy     代理对象
     * @param method    要作增强的方法
     * @param objects   要作增强方法的参数
     * @param methodProxy   代理方法
     * @return
     * @throws Throwable
     */
    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        logUtil.writeLog(method.getDeclaringClass().getName(),method.getName());
        Object ret = method.invoke(target, objects); // 调用真实对象的方法
        return ret;
    }
}

cglibMethodInterceptor.xml

    <bean id="employeeDao3" class="com.sunny._04cglib_methodinterceptor.dao.impl.EmployeeDaoImpl3"/>

    <bean id="logUtil" class="com.sunny._04cglib_methodinterceptor.log.LogUtil"/>

    <bean id="employeeServiceImpl3" class="com.sunny._04cglib_methodinterceptor.service.impl.EmployeeServiceImpl3">
        <property name="dao" ref="employeeDao3"/>
    </bean>

    <!--配置一个日志增强的类-->
    <bean id="logAdvice" class="com.sunny._04cglib_methodinterceptor.log.LogAdvice">
        <property name="target" ref="employeeServiceImpl3"/>
        <property name="logUtil" ref="logUtil"/>
    </bean>

测试类

    // com.sunny._04cglib_methodinterceptor.service.impl.EmployeeServiceImpl3$$EnhancerByCGLIB$$b1d8ba01
    @Autowired
    private LogAdvice advice;

    @Test
    public void testSave(){
        EmployeeServiceImpl3 proxy = advice.getProxyObject();
        // System.out.println(proxy.getClass());
        proxy.save(new Employee3());
    }

    @Test
    public void testUpdate(){
        EmployeeServiceImpl3 proxy = advice.getProxyObject();
        proxy.update(new Employee3());
    }

在这里插入图片描述

四、代理总结

在这里插入图片描述
JDK动态代理总结:

  • JAVA动态代理是使用java.lang.reflect包中的Proxy类InvocationHandler接口这两个来完成的。

  • 要使用JDK动态代理,代理类必须要实现接口。

  • JDK动态代理将会拦截所有pubic的方法(因为只能调用接口中定义的方法) ,这样即使在接口中增加了新的方法,不用修改代码也会被拦截。

  • 动态代理的最小单位是类(所有类中的方法都会被处理) ,如果只想拦截一部分方法 ,可以在invoke方法中对要执行的方法名进行判断

CGLIB代理总结:

  • CGLIB可以生成委托类的子类,并重写父类非final修饰符的方法。

  • 要求类不能是final 的,要拦截的方法要是非final、非static、非private的。

  • 动态代理的最小单位是类(所有类中的方法都会被处理);

性能和选择:

  • JDK动态代理是基于实现接口的, CGUB和Javassit是基于继承委托类的。

  • 从性能上考虑: Javassit > CGLIB > JDK

  • Struts2的拦截器和Hibernate延迟加载对象,采用的是Javassit的方式

  • 对接口创建代理优于对类创建代理,因为会产生更加松耦合的系统,也更符合面向接口编程规范。若委托对象实现了干接口,优先选用JDK动态代理。

  • 若委托对象没有实现任何接口, 使用Javassit和CGLIB动态代理。

<think>嗯,用户对动态代理提出了三个明确的问题:概念、使用方法原理实现。不过从用户补充的"参考站内引用"来看,用户已经查阅了相关资料,但希望获得更系统、更清晰的理解。这种场景像是开发者在学习设计模式或框架源码时遇到的困惑。用户提供的5个引用很有价值。引用1定义动态代理的核心是运行时创建代理对象的能力;引用2对比了静态代理的局限性;引用3给出了JDK动态代理的标准三步走;引用4强调了运行时动态改变的特性;引用5则区分了静态动态代理的关键差异(.class文件生成时机)。这些材料基本覆盖了所有知识点,但比较零散,需要整合。用户可能正在准备面试,或者正在阅读Spring等框架源码(因为动态代理是AOP的基础)。考虑到Java开发者通常更关注实践,回复应该突出代码示例,但引用23的代码片段不够完整——需要补充完整的可运行示例会更直观。另外要注意区分JDK动态代理CGLIB的适用场景,虽然用户没明确问,但实际开发中常会遇到接口缺失的情况。特别要注意引用5提到的"程序运行前生成.class文件"这个细节,这是静态代理动态代理的本质区别,应该用JVM类加载机制来解释原理。最后联想到Spring的AOP实践,可以提问应用场景来延伸学习路径——毕竟单纯讲代理模式不如结合框架更有意义。</think> 好的,我们来系统性地梳理一下Java动态代理的概念、用法、原理及实现。 **一、 动态代理是什么?(概念)** 动态代理是一种在**程序运行时期**(Runtime),动态地创建代理类及其对象,并为指定的目标对象提供代理功能的技术。它与静态代理的关键区别在于**无需在编译前预先编写代理类的Java源代码**。 * **核心目标:** 为其他对象(目标对象)提供一种替代或增强其功能的控制机制。 * **核心价值:** **解耦**代理逻辑与目标类逻辑,并**提高灵活性**。当需要为大量不同类添加统一功能(如日志、事务、权限检查、方法耗时统计等)时,动态代理避免了为每个类编写静态代理代码的繁琐工作。 * **核心机制:** 利用Java的反射(`java.lang.reflect`)API在运行时动态生成字节码并实例化代理对象。 * **类比理解:** 想象你有一个“万能中介”(代理类)。这个中介在程序运行起来后,才根据你提供的“中介能力说明书”(`InvocationHandler`实现)需要代理的“卖家列表”(目标接口列表),临时组建一个专门针对特定卖家群体的中介团队(代理对象)。这个“万能中介”可以服务于任何符合“说明书”要求的卖家,无需为每个卖家提前组建一个专门的中介[^1][^4]。 **二、 如何使用动态代理?(JDK动态代理用法)** Java标准库提供了基于接口的动态代理(`java.lang.reflect.Proxy`)。以下是标准使用步骤: 1. **定义服务接口(目标接口):** 规定目标对象需要被代理的方法。 ```java // 1. 定义接口 (抽象角色) public interface UserService { void addUser(String username); String getUser(int id); } ``` 2. **实现目标类(真实对象):** 实际执行业务逻辑的类。 ```java // 2. 目标类实现接口 (被代理角色) public class UserServiceImpl implements UserService { @Override public void addUser(String username) { System.out.println("添加用户: " + username); // 实际数据库操作等... } @Override public String getUser(int id) { System.out.println("获取用户ID: " + id); return "用户" + id; // 模拟返回 } } ``` 3. **实现`InvocationHandler`接口(代理逻辑处理器):** 这个类是**代理逻辑的核心**。它负责实现代理的“拦截”逻辑。 ```java import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; // 3. 实现InvocationHandler (定义代理增强逻辑) public class MyInvocationHandler implements InvocationHandler { // 持有目标对象 (真实对象的引用) private final Object target; public MyInvocationHandler(Object target) { this.target = target; // 在运行时传入具体的被代理对象实例 } // invoke方法会在代理对象调用任何方法时被自动触发 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // **1. 前置增强** (代理逻辑在这里添加) System.out.println("==> 准备执行方法: " + method.getName()); System.out.println(" 参数: " + (args != null ? java.util.Arrays.toString(args) : "无")); // **2. 调用目标对象的原始方法** (核心业务逻辑) Object result = method.invoke(target, args); // 通过反射调用真实对象的方法 // **3. 后置增强** System.out.println("<== 方法执行完毕: " + method.getName()); System.out.println(" 结果: " + result); // 返回目标方法执行结果 return result; } } ``` 4. **使用`Proxy.newProxyInstance`创建代理对象:** 在运行时动态生成代理对象。 ```java public class DynamicProxyDemo { public static void main(String[] args) { // 4a. 创建目标对象实例 (被代理对象) UserService realService = new UserServiceImpl(); // 4b. 创建InvocationHandler实例, 并将目标对象传递给它 InvocationHandler handler = new MyInvocationHandler(realService); // 4c. 使用Proxy动态创建代理对象 // 参数1: 目标类的类加载器 (用于加载代理类) // 参数2: 目标对象实现的接口列表 (代理类也要实现这些接口) // 参数3: InvocationHandler实例 (实现代理逻辑的处理器) UserService proxyService = (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), // 获取类加载器 realService.getClass().getInterfaces(), // 获取目标对象实现的所有接口 handler); // 5. 通过代理对象调用方法: 此时调用的不再是目标对象的addUser/getUser, 而是代理对象的invoke方法 proxyService.addUser("张三"); // 此调用会被代理拦截并增强 System.out.println(); String user = proxyService.getUser(123); // 此调用会被代理拦截并增强 System.out.println("主程序接收结果: " + user); } } ``` 5. **执行结果:** ``` ==> 准备执行方法: addUser 参数: [张三] 添加用户: 张三 <== 方法执行完毕: addUser 结果: null ==> 准备执行方法: getUser 参数: [123] 获取用户ID: 123 <== 方法执行完毕: getUser 结果: 用户123 主程序接收结果: 用户123 ``` **三、 动态代理原理及实现(JDK动态代理的核心)** 1. **底层机制 - 字节码生成:** * 当调用`Proxy.newProxyInstance()`时,Java运行时内部会根据你提供的类加载器、接口列表(`Class[] interfaces`),**动态生成一个全新的、实现了所有指定接口的代理类的字节码(`.class`文件)**。这个代理类的名称通常是类似`$Proxy0`, `$Proxy1`这样的格式。**代理类的生成发生在运行时,而不是编译时**[^5]。 2. **代理类的结构:** * 这个动态生成的代理类`$Proxy0`会直接继承自`java.lang.reflect.Proxy`。它包含了所实现接口的所有方法。 * 这个类内部会持有一个`InvocationHandler`接口的实例(就是你传入的那个`MyInvocationHandler`实例)。 3. **方法调用的拦截(核心流程):** * 代理类(如`$Proxy0`)为你所代理的接口(如`UserService`)实现的每个方法(如`addUser`, `getUser`),其方法体内部结构基本固定如下: ```java // 伪代码表示$Proxy0内部结构 public final class $Proxy0 extends Proxy implements UserService { private static Method m3; // 对应UserService接口的addUser方法 private static Method m4; // 对应UserService接口的getUser方法 // ... 静态代码块初始化m3, m4等... // 构造函数传入InvocationHandler public $Proxy0(InvocationHandler var1) { super(var1); // 调用父类Proxy的构造函数 } // 实现UserService接口方法 addUser @Override public void addUser(String var1) { try { // 核心调用逻辑:调用父类Proxy持有的handler的invoke方法! super.h.invoke( this, // 代理对象自身 m3, // 被调用的Method对象 (addUser) new Object[]{var1}); // 实际参数 } catch (...) { ... } } // 实现UserService接口方法 getUser @Override public String getUser(int var1) { try { // 核心调用逻辑:调用父类Proxy持有的handler的invoke方法! return (String) super.h.invoke( this, // 代理对象自身 m4, // 被调用的Method对象 (getUser) new Object[]{var1}); // 实际参数 } catch (...) { ... } } } ``` * **关键点:** 代理对象的方法调用,最终都会被委托(`delegate`)给它内部持有的那个`InvocationHandler`实例的`invoke`方法。这就是**“拦截”**发生的地方。 4. **`InvocationHandler.invoke()`方法的作用:** * 当你通过代理对象(如`proxyService`)调用方法时,实际执行的是`MyInvocationHandler.invoke(Object proxy, Method method, Object[] args)`方法。 * `proxy`: 代理对象本身(`$Proxy0`实例),通常在这个方法内部使用较少(注意避免在内部递归调用造成栈溢出)。 * `method`: 代表当前在代理对象上调用的方法的反射对象(`Method`)。通过它可以知道调用的是什么方法。 * `args`: 调用方法时传入的实际参数列表。 * 这个方法内部就是你编写代理逻辑的地方: * **前增强:** 在调用目标对象真实方法前执行的代码(如日志记录、权限校验)。 * **调用真实方法:** 使用反射 `method.invoke(target, args)` 调用目标对象(你通过构造函数传入的`realService`)上的原始方法。 * **后增强:** 在调用目标对象真实方法后执行的代码(如提交事务、清理资源、记录耗时)。 * 该方法最终需要返回真实方法调用的结果(如果原始方法有返回值)[^1][^2][^3][^4]。 5. **总结原理流程图:** ``` client (调用者) --> proxyService (代理对象:$Proxy0) ---[委托]---> MyInvocationHandler.invoke() | [目标对象实际执行] <-- method.invoke(target, args) <---| | (前/后增强逻辑) ``` **关键特性与注意点:** * **基于接口:** JDK动态代理**要求目标对象必须至少实现一个接口**。代理类本身会实现同样的接口。如果需要代理没有接口的类,需要使用CGLIB之类的字节码操作库。 * **灵活性:** 通过传递不同的目标对象`InvocationHandler`实现,同一个代理类(或`Proxy`生成逻辑)可以为多种不同的目标类提供服务[^4]。 * **性能:** 由于涉及反射调用`method.invoke()`,相对于直接调用目标方法会有轻微性能开销。但在现代JVM上,这种开销通常可以接受。关键的性能瓶颈通常在代理逻辑本身(如远程调用、IO等)。 * **`final`方法:** 动态代理无法代理`final`方法。因为生成的代理类是通过继承`Proxy`并实现接口来工作的,它无法覆盖目标类中的`final`方法(如果目标类本身是非`final`的,但其方法是`final`的,虽然可以生成代理对象,但对`final`方法的调用会直接转到目标对象上,绕过代理逻辑)。 * **查看代理类:** 可以通过设置系统属性`System.setProperty("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");`将运行时生成的代理类字节码文件(`$Proxy0.class`)保存到磁盘,方便分析。 理解动态代理对于掌握Java高级特性(如Spring AOP、RPC框架)至关重要。它在解耦、增强功能、实现统一控制等方面提供了强大的能力[^1][^3][^5]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

white camel

感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值