Java动态代理实现与分析

静态代理

  1. 静态代理的实现:
    假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交给老师。这里,班长就是代理学生上交班费。班长就是学生的代理。

    首先,我们创建一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有上交班费的行为。这样,学生上交班费就可以让班长来代理执行。

    public interface Person {
        //上交班费
        void giveMoney();
    }
    

    Student类实现Person接口。Student可以具体实施上交班费的动作。

    public class Student implements Person {
        private String name;
        public Student(String name) {
            this.name = name;
        }
        
        @Override
        public void giveMoney() {
           System.out.println(name + "上交班费50元");
        }
    }
    

    StudentsProxy类,这个类也实现了Person接口,但是还另外持有一个学生类对象,由于实现了Peson接口,同时持有一个学生对象,那么他可以代理学生类对象执行上交班费(执行giveMoney()方法)行为。

        public class StudentsProxy implements Person{
        //被代理的学生
        Student stu;
        
        public StudentsProxy(Person stu) {
            // 只代理学生对象
            if(stu.getClass() == Student.class) {
                this.stu = (Student)stu;
            }
        }
        
        //代理上交班费,调用被代理学生的上交班费行为
        public void giveMoney() {
            stu.giveMoney();
        }
    }
    

    测试代码

    public class StaticProxyTest {
        public static void main(String[] args) {
            //被代理的学生张三,他的班费上交有代理对象monitor(班长)完成
            Person zhangsan = new Student("张三");
            
            //生成代理对象,并将张三传给代理对象
            Person monitor = new StudentsProxy(zhangsan);
            
            //班长代理上交班费
            monitor.giveMoney();
        }
    }
    

    结果
    在这里插入图片描述
    这里并没有直接通过张三(被代理对象)来执行上交班费的行为,而是通过班长(代理对象)来代理执行了。这就是代理模式。

    代理模式最主要的就是有一个公共接口(Person),一个具体的类(Student),一个代理类(StudentsProxy),代理类持有具体类的实例,代为执行具体类实例方法。代理模式就是在访问实际对象时引入一定程度的间接性,因为这种间接性,可以附加多种用途。这里的间接性就是指不直接调用实际对象的方法,那么我们在代理过程中就可以加上一些其他用途。就这个例子来说,加入班长在帮张三上交班费之前想要先反映一下张三最近学习有很大进步,通过代理模式很轻松就能办到:

    public class StudentsProxy implements Person{
        //被代理的学生
        Student stu;
        
        public StudentsProxy(Person stu) {
            // 只代理学生对象
            if(stu.getClass() == Student.class) {
                this.stu = (Student)stu;
            }
        }
        
        //代理上交班费,调用被代理学生的上交班费行为
        public void giveMoney() {
            System.out.println("张三最近学习有进步!");
            stu.giveMoney();
        }
    }
    

    在这里插入图片描述
    只需要在代理类中帮张三上交班费之前,执行其他操作就可以了。这种操作,也是使用代理模式的一个很大的优点。最直白的就是 在Spring中的面向切面编程(AOP),我们能在一个切点之前执行一些操作,在一个切点之后执行一些操作,这个切点就是一个个方法。 这些方法所在类肯定就是被代理了,在代理过程中切入了一些其他操作。

动态代理

代理类在程序运行时创建的代理方式被成为动态代理。 我们上面静态代理的例子中,代理类(studentProxy)是自己定义好的,在程序运行之前就已经编译完成。然而动态代理,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。 比如说,想要在每个代理的方法前都加上一个处理方法。

在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

  • 创建一个InvocationHandler对象

    //创建一个与代理对象相关联的InvocationHandler
     InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
    
  • 使用Proxy类的getProxyClass静态方法生成一个动态代理类stuProxyClass

     Class<?> stuProxyClass = Proxy.getProxyClass(Person.class.getClassLoader(), new Class<?>[] {Person.class});
    
  • 获得stuProxyClass 中一个带InvocationHandler参数的构造器constructor

    Constructor<?> constructor = PersonProxy.getConstructor(InvocationHandler.class);
    
  • 通过构造器constructor来创建一个动态实例stuProxy

    Person stuProxy = (Person) cons.newInstance(stuHandler);
    

就此,一个动态代理对象就创建完毕,当然,上面四个步骤可以通过Proxy类的newProxyInstances方法来简化:

 //创建一个与代理对象相关联的InvocationHandler
  InvocationHandler stuHandler = new MyInvocationHandler<Person>(stu);
//创建一个代理对象stuProxy,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
  Person stuProxy= (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

到这里肯定都会很疑惑,这动态代理到底是如何执行的,是如何通过代理对象来执行被代理对象的方法的,先不急,我们先看看一个简单的完整的动态代理的例子。还是上面静态代理的例子,班长需要帮学生代交班费。
首先是定义一个Person接口:

public interface Person {
    //上交班费
    void giveMoney();
}

创建需要被代理的实际类:

public class Student implements Person {
    private String name;
    public Student(String name) {
        this.name = name;
    }
    
    @Override
    public void giveMoney() {
        try {
          //假设数钱花了一秒时间
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       System.out.println(name + "上交班费50元");
    }
}

再定义一个检测方法执行时间的工具类,在任何方法执行前先调用start方法,执行后调用finsh方法,就可以计算出该方法的运行时间,这也是一个最简单的方法执行时间检测工具。

public class MonitorUtil {
    
    private static ThreadLocal<Long> tl = new ThreadLocal<>();
    
    public static void start() {
        tl.set(System.currentTimeMillis());
    }
    
    //结束时打印耗时
    public static void finish(String methodName) {
        long finishTime = System.currentTimeMillis();
        System.out.println(methodName + "方法耗时" + (finishTime - tl.get()) + "ms");
    }
}

创建StuInvocationHandler类,实现InvocationHandler接口,这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法,所有执行代理对象的方法都会被替换成执行invoke方法。

再再invoke方法中执行被代理对象target的相应方法。当然,在代理过程中,我们在真正执行被代理对象的方法前加入自己其他处理。这也是Spring中的AOP实现的主要原理,这里还涉及到一个很重要的关于java反射方面的基础知识。

public class StuInvocationHandler<T> implements InvocationHandler {
   //invocationHandler持有的被代理对象
    T target;
    
    public StuInvocationHandler(T target) {
       this.target = target;
    }
    
    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理执行" +method.getName() + "方法");
     */   
        //代理过程中插入监测方法,计算该方法耗时
        MonitorUtil.start();
        Object result = method.invoke(target, args);
        MonitorUtil.finish(method.getName());
        return result;
    }
}

做完上面的工作后,我们就可以具体来创建动态代理对象了,上面简单介绍了如何创建动态代理对象,我们使用简化的方式创建动态代理对象:

    public class ProxyTest {
    public static void main(String[] args) {
        
        //创建一个实例对象,这个对象是被代理的对象
        Person zhangsan = new Student("张三");
        
        //创建一个与代理对象相关联的InvocationHandler
        InvocationHandler stuHandler = new StuInvocationHandler<Person>(zhangsan);
        
        //创建一个代理对象stuProxy来代理zhangsan,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);

       //代理执行上交班费的方法
        stuProxy.giveMoney();
    }
}

我们执行这个ProxyTest类,先想一下,我们创建了一个需要被代理的学生张三,将zhangsan对象传给了stuHandler中,我们在创建代理对象stuProxy时,将stuHandler作为参数了的,上面也有说到所有执行代理对象的方法都会被替换成执行invoke方法,也就是说,最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。
在这里插入图片描述
转载此博主的,写的真心好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值