静态代理、jdk动态代理、cglib动态代理

一、静态代理

Subject:抽象主题角色,抽象主题类可以是抽象类,也可以是接口,是一个最普通的业务类型定义,无特殊要求。

RealSubject:具体主题角色,也叫被委托角色、被代理角色。是业务逻辑的具体执行者。

Proxy:代理主题角色,也叫委托类、代理类。它把所有抽象主题类定义的方法给具体主题角色实现,并且在具体主题角色处理完毕前后做预处理和善后工作。(最简单的比如打印日志):

public interface Count {
    // 查看账户方法
    public void queryCount();
 
    // 修改账户方法
    public void updateCount();
 
}


public class CountImpl implements Count {  
  
    @Override  
    public void queryCount() {  
        System.out.println("查看账户方法...");  
  
    }  
  
    @Override  
    public void updateCount() {  
        System.out.println("修改账户方法...");  
  
    }  
  
}  


public class CountProxy implements Count {  
    private CountImpl countImpl;  
  
    /** 
     * 覆盖默认构造器 
     *  
     * @param countImpl 
     */  
    public CountProxy(CountImpl countImpl) {  
        this.countImpl = countImpl;  
    }  
  
    @Override  
    public void queryCount() {  
        System.out.println("事务处理之前");  
        // 调用委托类的方法;  
        countImpl.queryCount();  
        System.out.println("事务处理之后");  
    }  
  
    @Override  
    public void updateCount() {  
        System.out.println("事务处理之前");  
        // 调用委托类的方法;  
        countImpl.updateCount();  
        System.out.println("事务处理之后");  
  
    }  
  
}  

1.接口:代理类需要实现一个接口,这个接口和委托类的接口是一样的,这样proxy才能和委托类行为表现一致

2.方法(Method):由于接口限制,proxy类中也要有interface中的各个方法,这就造成了代码重复

二、动态代理

定义接口

public interface UserService {
    public String getName(int id);

    public Integer getAge(int id);
}

1.jdk动态代理

基于JDK的动态代理关键在于两个类:InvocationHandler和Proxy。
其主要实现逻辑是,由InvocationHandler定义方法执行前后的增强逻辑,由Proxy类去生成一个继承自Proxy并且实现了真实对象接口的新对象–代理对象,对该代理对象的方法调用经由InvocationHandler拦截,执行增强逻辑和调用真实对象执行业务逻辑。

接口实现类

public class UserServiceImpl implements UserService {
    public UserServiceImpl() {
    }

    @Override
    public String getName(int id) {
        System.out.println("---getName---");
        return "John";
    }

    @Override
    public Integer getAge(int id) {
        System.out.println("---getAge---");
        return 10;
    }
}

代理实现

public class MyInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("getName")) {
            System.out.println("+++before get name+++");
            Object res = method.invoke(target, args);
            System.out.println("+++after get name+++");
            return res;
        } else {
            Object res = method.invoke(target, args);
            return res;
        }
    }

    public static void main(String[] args) {
        UserService us = new UserServiceImpl();
        InvocationHandler ih = new MyInvocationHandler(us);
        UserService usProxy = (UserService) Proxy.newProxyInstance(us.getClass().getClassLoader(), us.getClass().getInterfaces(), ih);
        System.out.println(usProxy.getName(1));
        System.out.println(usProxy.getAge(1));
        System.out.println(usProxy.getClass());
    }
}

2.cglib动态代理

JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。 通过cglib生成代理类只需要一个目标类和一个回调函数(增强逻辑)

public class UserServiceB {
    public String getName(int id) {
        System.out.println("---getName---");
        return "John";
    }

    public Integer getAge(int id) {
        System.out.println("---getAge---");
        return 10;
    }

    public static void main(String[] args) throws InterruptedException, IOException {
        UserServiceB us = new UserServiceB();
        // 定义增强器
        Enhancer en = new Enhancer();
        // 定义要代理的对象
        en.setSuperclass(us.getClass());
        // 定义回调函数
        en.setCallback(new MethodInterceptor() {
            // 这里要理解intercept方法的几个参数代表的意思
            // obj指的是代理类对象
            // Method指的是 目标类中被拦截的方法
            // args指的是 调用拦截方法所需的参数
            // MethodProxy指的是用来调用目标类被拦截方法的方法,这个方法比反射更快
            @Override
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("-----before-------");
                methodProxy.invokeSuper(obj, args);
                System.out.println("-----after--------");
                return null;
            }
        });
        // 生成代理对象
        UserServiceB usb = (UserServiceB) en.create();
        // 在代理对象上调用方法
        usb.getName(1);
    }
}

三、jdk动态代理和CGlib动态代理的区别

1、JDK动态代理

利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

2、CGLiB动态代理

利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

3、何时使用JDK还是CGLiB?

1)如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。

2)如果目标对象实现了接口,可以强制使用CGLIB实现AOP。

3)如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

4、如何强制使用CGLIB实现AOP?

1)添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)

2)在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

5、JDK动态代理和CGLIB字节码生成的区别?

1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。

6、CGlib比JDK快?

1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

2)在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

7、Spring如何选择用JDK还是CGLiB?

1)当Bean实现接口时,Spring就会用JDK的动态代理。

2)当Bean没有实现接口时,Spring使用CGlib是实现。

3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)。

类型 机制 回调方式 适用场景 效率
jdk动态代理 委托机制,代理类和目标类都实现了同样的接口,InvocationHandler持有目标类,代理类委托InvocationHandler去调用目标类的原始方法 反射 目标类是接口类 效率瓶颈在反射调用稍慢
cglib动态代理 继承机制,代理类继承了目标类并重写了目标方法,通过回调函数MethodInterceptor调用父类方法执行原始逻辑 通过FastClass方法索引调用 非接口类,非final类,非final方法 第一次调用因为要生成多个Class对象较JDK方式慢,多次调用因为有方法索引较反射方式快,如果方法过多switch case过多其效率还需测试
原文地址:https://www.cnblogs.com/JavaZhangXu/p/10103224.html