Java基础----动态代理

来源:互联网 发布:兄弟连 php 课程表 编辑:程序博客网 时间:2024/06/05 02:04
Spring AOP使用动态代理技术在运行期织入增强的代码,Spring AOP的底层工作原理就是动态代理机制。

先来看动态代理要解决什么问题,这样就能更好的理解它,学习一个新技术或者知识点,先知道在不用它的时候我们是怎么解决的,有什么缺点,这样就知道这样做的好处是什么了。

我们在写项目的时候,往往写好了一些功能,这时候需要在原来的功能上或者逻辑上加入一些比如记录日志或者时间的业务,会怎么做呢?
比如下面最简单的一个User的add和delete操作。

package kay.dao;/** * Created by kay on 2016/12/6. *///DAO接口public interface UserDAO {    public void add();    public void delete();}//DAO实现类package kay.dao.impl;import kay.dao.UserDAO;public class UserDaoImpl implements UserDAO{    @Override    public void add() {        System.out.println("添加一个用户");//模拟add    }    @Override    public void delete() {        System.out.println("删除一个用户");//模拟delete    }}

如果现在要加一个记录add和delete方法的业务,可以这样做

@Override    public void add() {        System.out.println("记录日志add方法");//模拟记录日志        System.out.println("添加一个用户");//模拟add    }//delete方法也一样要加这样的逻辑

如果我们这个类中所有的方法(假如有很多),都要加这个逻辑怎么办?
当然,我们可以将日志类抽象出来,并让它实现UserDAO接口,比如这样:

package kay.aop.service;import kay.dao.UserDAO;/** * Created by kay on 2016/12/6. */public class Log implements UserDAO{    private UserDAO userDAO;//封装一个接口对象,用来处理实际业务    public Log(UserDAO userDAO) {        this.userDAO = userDAO;    }    @Override    public void add() {        System.out.println("日志记录add");        userDAO.add();//实际业务    }    @Override    public void delete() {        //省略    }}

我们使用业务就会是这样调用:

public class Test {    public static void main(String[] args) {        UserDaoImpl userDao=new UserDaoImpl();        Log log=new Log(userDao);        log.add();    }}
测试结果:日志记录add添加一个用户

好像没什么问题,但是如果这个时间我们又要添加一个记录方法时间的业务,或者再添加一个验证方法权限的业务·······假如有很多这样的公共的业务,但是与实际业务关系不大,许多地方都要用到,那怎么办?
写很多的类似Log一样的类实现UserDAO接口吗?如果我们要记录的类不仅仅是UserDAO的实现类,而是很多不相干的类,那我们用上面的方法不就要写很多重复的代码了吗?
那怎么解决呢?

想一下我们的问题,在需要给一个目标类(target)添加一些其他业务而不改变原有代码(或者说不能修改源代码)的时候,我们写了一个继承这个目标类接口的代理类(Proxy,这个类可能是记录日志,时间,等等),然后让它来重写了我们实际的方法,添加了功能+调用实际业务的方法
能不能让程序在我们需要这样逻辑的时候自动生成这样的一个类,完成要添加的功能呢?
这个时候就可以用到动态代理了。

JDK动态代理

先来看JDK的动态代理怎么实现的:

package kay.test;import kay.aop.service.Log;import kay.dao.UserDAO;import kay.dao.impl.UserDaoImpl;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;/** * Created by kay on 2016/12/6. */public class Test {    public static void main(String[] args) {       /* UserDaoImpl userDao=new UserDaoImpl();        Log log=new Log(userDao);        log.add();*/        UserDAO dao = new UserDaoImpl(); //创建一个实际业务的对象        //生成一个业务代理,在其中添加需要的业务,比如日志记录,时间记录等        UserDAO proxyInstance = (UserDAO) Proxy.newProxyInstance(dao.getClass().getClassLoader(),                dao.getClass().getInterfaces(),                new InvocationHandler() {                    @Override                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                        System.out.println("日志记录---------");//模拟日志                        return method.invoke(dao,args);  //用反射执行实际的业务UserImpl的add比如。方法也抽象了                    }                });        proxyInstance.add();      //代理其实就是实现了UserDAO接口的一个对象    }}

JDK的动态代理主要由java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。

在Proxy类中,静态方法newProxyInstance()将动态地把需要代理的类进行代理,其原理就是利用反射,继承目标类的接口,生成一个新的类,在InvocationHandler中定义了我们需要添加逻辑的方法,最后这个新生成的类就重写了该方法(里面有添加的逻辑),通过代理就可以直接调用这个方法,然而却并没有去改变我们实际的业务类UserDaoImpl,这就是JDK的动态代理。

这种动态代理可以在业务逻辑的执行前后添加需要的功能而不改变原来的代码,就是一种面向切面编程的思想,或者称为AOP

CGLib动态代理

以下是引用 CGLib代理我没有自己做实验

使用JDK创建代理有一个限制,即它只能为接口创建代理实例,这一点我们可从Proxy的接口newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)的方法签名中就看得很清楚:第二个入参interfaces就是需要代理实例实现的接口列表。虽然面向接口编程的思想被很多大师级人物(包括Rod Johnson)推崇,但在实际开发中,许多开发者也对此深感困惑:难道对一个简单业务表的操作也需要老老实实地创建5个类(领域对象类、Dao接口,Dao实现类,Service接口和Service实现类)吗?难道不能直接通过实现类构建程序吗?对于这个问题,我们很难给出一个孰好孰劣的准确判断,但我们确实发现有很多不使用接口的项目也取得了非常好的效果对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?JDK的代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这个空缺。 CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,并顺势织入横切逻辑。代理知识小结 

Spring AOP的底层就是通过使用JDK动态代理或CGLib动态代理技术为目标Bean织入横切逻辑。在这里,我们对前面两节动态创建代理对象作一个小结。

我们虽然通过PerformanceHandler或CglibProxy实现了性能监视横切逻辑的动态织入,但这种实现方式存在三个明显需要改进的地方:

1)目标类的所有方法都添加了性能监视横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定方法添加横切逻辑;
2)我们通过硬编码的方式指定了织入横切逻辑的织入点,即在目标类业务方法的开始和结束前织入代码;
3)我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用。

以上三个问题,在AOP中占用重要的地位,因为Spring AOP的主要工作就是围绕以上三点展开:Spring AOP通过Pointcut(切点)指定在哪些类的哪些方法上织入横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体织入点(方法前、方法后、方法的两端等)。此外,Spring通过Advisor(切面)将Pointcut和Advice两者组装起来。有了Advisor的信息,Spring就可以利用JDK或CGLib的动态代理技术采用统一的方式为目标Bean创建织入切面的代理对象了。

1 0
原创粉丝点击