Spring学习 一 代理模式(动态代理、CGLIB)

概述

代理(Proxy),是一种设计模式,为目标对象提供了一种非"直接"的访问方式.即通过"代理"人的方式访问目标对象.类似"电影明星" 和 "经纪人" 模式. 这种模式的好处在于: 可以在目标对象实现的基础上. 对目标对象进行功能扩展或防止直接访问目标对象所带来的不必要的复杂性.

在Spring的AOP框架中:

如果被加入容器的目标对象有实现接口, 就会使用JDK代理
如果没有实现任何接口,则使用Cglib子类代理.

下面我们就代理模式,做一些基础的讲解.

静态代理

代理对象需要实现与目标对象一样的接口.
举例说明:

接口类:

public interface IUserDao {
    /**
     * 保存数据到数据库;
     */
    void save();
}

接口实现类:

public class UserDaoImpl implements IUserDao {
    @Override
    public void save() {
        System.out.println("------数据存储完毕------");
    }
}

代理类:

public class UserDaoProxy implements IUserDao{

    private IUserDao target;
    
    public UserDaoProxy(IUserDao target) {
        this.target = target;
    }
    
    @Override
    public void save() {
        
        System.out.println("保存前...");
        
        target.save();
        
        System.out.println("保存后...");
    }
}

测试 :

public class App {
    public static void main(String[] args) {
        // 目标对象
        UserDaoImpl target = new UserDaoImpl();
        // 代理对象
        IUserDao proxyDao = new UserDaoProxy(target);
        proxyDao.save();
    }
}

测试结果如图所示


Spring学习 一 代理模式(动态代理、CGLIB)_第1张图片
测试结果

优点: 可以做到在不修改目标功能的前提下,对目标功能进行扩展.

缺点: 因为要和目标对象一样实现同样的接口,如果代理对象较多.响应的就需要些很多的代理类. 一旦接口发生改变(增加方法). 则实现此接口的所有类都需要改变.不便于维护

动态代理

相比静态代理,动态代理,在代理目标对象时,不需要实现和目标相同的接口.

动态代理对象的生成是依靠JDK中提供的API.动态的在内存冲构建代理对象.在动态创建代理对象时,需要我们指定代理对象实现的接口类型. 因为使用了JDK中的Api,所以动态代理又名: "JDK代理" or "接口代理"

JDK中生成代理对象的API :

public static Object newProxyInstance(ClassLoader loader,  
                                      Class[] interfaces,
                                      InvocationHandler h)

@param loader : 指定当前目标对象使用的类加载器.
@param interfaces : 目标对象实现的接口类型.
@param h : 事件处理器. 当执行目标对象实现的接口类型中定义的方法时, 会将方法名等信息,以参数的形式回调.

举例说明 :

代理工厂类(动态代理):

public class ProxyFactory {
    
    private Object target;
    
    public ProxyFactory(Object target){
        this.target = target;
    }
    
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        
                        System.out.println("方法执行前");
                        
                        Object result = method.invoke(target, args);
                        
                        System.out.println("方法执行后");
                        
                        return result;
                    }
                });
    }
}

测试:

public void testDynamic() throws Exception {
    // 目标对象
    UserDaoImpl target = new UserDaoImpl();
    // 代理对象
    IUserDao proxyDao = (IUserDao) new ProxyFactory(target).getProxyInstance();
    proxyDao.save();
}

测试结果:


Spring学习 一 代理模式(动态代理、CGLIB)_第2张图片
测试结果

相比静态代理,动态代理的优势很明显, 代理对象由系统生成,并且不需要和代理对象实现相同的接口类.

但是使用动态代理有一个前提 :
目标对象一定要实现接口! ! !
目标对象一定要实现接口! ! !
目标对象一定要实现接口! ! !
否则不能使用动态代理.

Cglib代理

如果有一个目标对象, 我们想要在不修改它的前提下扩展它的功能,但是这个对象没有实现接口, 这是我们怎样实现扩展?

我们可以以子类的方式实现代理 一一 Cglib代理

Cglib代理,也叫作子类代理. 即在内存中构建一个子类对象,从而实现对目标对象功能的扩展.

JDK的动态代理有一个限制, 就是使用动态代理的对象必须实现一个或多个接口.如果想代理没有实现接口的类,就可以使用Cglib实现

Cglib是一个强大的、高性能的代码生成包,他可以再程序运行期间扩展Java类与实现Java接口. 它广泛的被许多AOP框架使用. 例如 Spring AOP 和 Dynaop, 为它们提供方法的interception(拦截).

Cglib包的底层使用一个小而快的字节处理框架ASM来转换字节码并生成新的类, 这里我们不鼓励直接使用ASM. 因为它要求你必须对JVM内部结构 一一 包括class文件的格式和指令集都很熟悉. 这里我们就不对ASM展开了.

使用Cglib
  1. 使用Cglib子类代理,需要引入cglib.jar包. 在Spring的核心包中已经为包含了Cglib功能,所以直接引入Spring-core.jar即可.
Spring学习 一 代理模式(动态代理、CGLIB)_第3张图片
cglib.jar

下面我们就写一个CglibProxyFactory类:

public class CglibProxyFactory implements MethodInterceptor {

    private Object target;

    public CglibProxyFactory(Object target) {
        this.target = target;
    }

    public Object getProxyInstance() {
        
        // 工具类
        Enhancer en = new Enhancer();
        // 设置父类
        en.setSuperclass(target.getClass());
        // 设置回调函数
        // 这里我是让工厂列实现方法拦截回调,当方法被调用时,就会执行intercept方法
        en.setCallback(this);
        // 创建子类(代理类)
        return en.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy arg3) throws Throwable {
        
        System.out.println("Cglib 代理, 方法执行前...");
        
        Object returnValue = method.invoke(target, args);
        
        System.out.println("Cglib 代理, 方法执行后...");
        
        return returnValue;
    }
}

测试方法 :

public void testCglib() throws Exception {
    // 目标对象
    UserDaoImpl target = new UserDaoImpl();
    
    // 代理对象
    UserDaoImpl proxy = (UserDaoImpl) new CglibProxyFactory(target).getProxyInstance();
    proxy.save();
}

测试结果 :

Spring学习 一 代理模式(动态代理、CGLIB)_第4张图片
测试结果

使用Cglib子类代理时,需要注意:

  1. 被代理的类(目标类)不能被final关键字修饰.否则报错
  2. 目标对象中的方法如果被 final / static 关键字修饰, 那么被修饰的方法.在被调用时就不会被MethodIntercept
    拦截器拦截,即不会执行目标对象额外的业务方法.

你可能感兴趣的:(Spring学习 一 代理模式(动态代理、CGLIB))