Java代理模式(3)一CGLib动态代理

目录

Java代理模式(1)一静态代理
Java代理模式(2)一动态代理
Java代理模式(3)一CGLib代理


前言

Java代理模式(2)一动态代理中提到Java的动态代理只局限于实现接口的实现类(RealSubject/RealSubject2都实现 ProblemInterface),尽管比起静态代理优点有很多,但是实际业务中不是所有的类都会实现一个接口,在SpringHibernate这些框架更是很明显,所以它们都会用到了CGLib对实现接口的类动态生成代理类


一、CGLib原理

1、什么是CGLib?

CGLib是一个强大的,高性能,高质量的代码生成类库(Code Generation Library),提供比反射更为强大方便的特性来实现比Java动态代理更为灵活的代理。它为没有实现接口的类提供代理,是JDK动态代理的一种补充与扩充。

2、CGLib原理

(1)动态生成一个代理类的子类,子类重写要代理的类的所有方法(不包括final修饰的,实际上是继承了重写了被代理类的所有方法,自然final不能被重写代理)。在子类中采用方法拦截的技术拦截intercept所有父类方法的调用,同时织入横切逻辑。
理解起来可能会有点晦涩,先进行CGLib相关的方法介绍或许能理解:

  • net.sf.cglib.proxy.Enhancer 字节码增强器,可以很方便的对类进行拓展,使用Enhancer.create()方法创建动态代理类返回。
  • net.sf.cglib.proxy.MethodInterceptor 主要的方法拦截类,被代理对象RealSubject的每个method()的调用都会转向实现此接口的intercept()方法。具体请看下图。
  • net.sf.cglib.proxy.MethodProxy JDKjava.lang.reflect.Method类的代理类,可以方便的实现对目标对象方法的调用。

Java代理模式(3)一CGLib动态代理_第1张图片

  • intercept()中我们可以对被代理对象方法进行调用,在调用的前后还可以加入其它业务,也就是织入横切逻辑
@Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) 
    throws Throwable { 
        System.out.println("调用前相关操作...");
        Object result = proxy.invokeSuper(obj, args); //调用业务类的方法
        System.out.println("调用后相关操作...");
        return result ; 
    }
  • 当对Proxy代理中所有方法的调用时,都会转向MethodInterceptor类型的拦截intercept()方法,在拦截方法中再调用真实业务RealSubject对象相应的方法。

(2)它是通过底层ASM字节码相关处理,转为字节码生成新的代理类的子类,所以会比通过Java的反射机制创建动态代理更加有效率。关于ASM可以查看此博客Java ASM介绍、ASM快速入门

二、完整例子代码实现

业务类RealSubject3

public class RealSubject3 {

    public void resolve() {
        System.out.println("真实业务类RealSubject3主要实现CRUD操作...");
    }

}

实现MethodInterceptor接口的MethodInterceptorImpl类:

public class MethodInterceptorImpl implements MethodInterceptor{

    private Object target;//业务类真实对象

    //类似于JDK动态代理中的绑定
    public Object getInstance(Object target) {  
        this.target = target; 
        Enhancer enhancer = new Enhancer();
        //设置代理类的父类,即RealSubject3为proxy的父类
        enhancer.setSuperclass(this.target.getClass());  
        //设置回调:表示对代理对象proxy的方法的调用都会回调intercept()函数进行处理
        enhancer.setCallback(this); 
       // 创建动态代理类对象并返回  
       return enhancer.create(); 
    }

    // 实现回调方法 
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 
        System.out.println("调用前相关操作...");
        Object result = proxy.invokeSuper(obj, args); //调用真实业务类的方法
        System.out.println("调用后相关操作...");
        return result; 
    } 
}

CGLibClient测试类:

public class CGLibClient {

    public static void main(String[] args) {
        RealSubject3 realSubject3 = new RealSubject3();
        MethodInterceptorImpl methodInterceptor = new MethodInterceptorImpl();
        RealSubject3 proxy = (RealSubject3)methodInterceptor.getInstance(realSubject3);//生成RealSubject3的子类代理类proxy
        proxy.resolve();
    }

}

结果如下,成功实现了代理

调用前相关操作…
真实业务类RealSubject3主要实现CRUD操作…
调用后相关操作…

注意:在编写过程中可能会遇到以下的错误:

Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
    at net.sf.cglib.core.TypeUtils.parseType(TypeUtils.java:184)
    at net.sf.cglib.core.KeyFactory.(KeyFactory.java:72)
    at net.sf.cglib.proxy.Enhancer.(Enhancer.java:72)
    ...

这要么是因为jar包冲突,要么就是没引对jar包,我们通常使用带ASMcglib-nodep-3.2.6.jar包,如果同时存在cglib-3.2.6.jar会冲突。

<dependency>
   <groupId>cglibgroupId>
    <artifactId>cglib-nodepartifactId>
    <version>3.2.6version>
dependency>

现在我们再增加一个方法:add()方法:

public class RealSubject3 {

    public void resolve() {
        System.out.println("真实业务类RealSubject3主要实现CRUD操作...");
    }
    public void add() {
        System.out.println("add操作...");
    }
}

然后我们可以进行在回调处理函数intercept()中进行对add()方法的权限控制:

public class MethodInterceptorImpl implements MethodInterceptor{

    private Object target;//业务类真实对象
    private String name;
    //传入username
    public MethodInterceptorImpl(String name) {
        this.name = name;
    }
    //类似于JDK动态代理中的绑定
    public Object getInstance(Object target) {  
        this.target = target; 
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());  //设置代理类的父类,即RealSubject3为proxy的父类
        //设置回调:表示对代理对象proxy的方法的调用都会回调intercept()函数进行处理
        enhancer.setCallback(this); 
       // 创建动态代理类对象并返回  
       return enhancer.create(); 
    }

    // 实现回调方法 
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { 
        //System.out.println("调用前相关操作...");
        if (!"小李".equals(name)) { //对add()方法进行权限控制
            System.out.println(name+"您不是管理员,只有管理员才有权限");
            return null;  
        }
       System.out.println("管理员"+name+",欢迎您...");
        Object result = proxy.invokeSuper(obj, args); //调用真实业务类的方法
        //System.out.println("调用后相关操作...");
        return result; 
    } 
}

客户端CGLibClient

public class CGLibClient {

    public static void main(String[] args) {
        RealSubject3 realSubject3 = new RealSubject3();
        String username = "小张";
        MethodInterceptorImpl methodInterceptor = new MethodInterceptorImpl(username);
        RealSubject3 proxy = (RealSubject3)methodInterceptor.getInstance(realSubject3);//生成RealSubject3的子类代理类proxy
        proxy.add();
        String username2 = "小李";
        MethodInterceptorImpl methodInterceptor2 = new MethodInterceptorImpl(username2);
        RealSubject3 proxy2 = (RealSubject3)methodInterceptor2.getInstance(realSubject3);//生成RealSubject3的子类代理类proxy
        proxy2.add();
    }
}

结果:

小张您不是管理员,只有管理员才有权限
管理员小李,欢迎您…
add操作…

总结与补充

1、在代理实现了接口的类时候,默认的情况下Spring等框架都是使用JDK动态代理,当然也可以强制使用CGLib动态代理实现接口的被代理类,但是要代理没有实现接口的被代理类时,Spring都会使用CGLib实现动态代理。

2、其实我们是可以查看动态生成代理类.class文件,之后反编译查看结果,首先我们CGLibClient程序中中加入如下代码,表示CGLib动态生成的.class输出到指定的路径下。

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\xxx\\xxx");

之后会生成.class文件,利用反编译工具进行查看:

(1)生成的代理类extends RealSubject3
Java代理模式(3)一CGLib动态代理_第2张图片
(2)还有重写了父类RealSubject3所有的方法(不包括final
Java代理模式(3)一CGLib动态代理_第3张图片
(3)还有必备的toStringequals等方法
Java代理模式(3)一CGLib动态代理_第4张图片

你可能感兴趣的:(设计模式,CGLib代理,Java动态代理)