Java代理模式(1)一静态代理
Java代理模式(2)一动态代理
Java代理模式(3)一CGLib代理
Java代理模式(2)一动态代理中提到Java的动态代理只局限于实现接口的实现类(
RealSubject/RealSubject2
都实现ProblemInterface
),尽管比起静态代理优点有很多,但是实际业务中不是所有的类都会实现一个接口,在Spring
、Hibernate
这些框架更是很明显,所以它们都会用到了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
JDK
的java.lang.reflect.Method
类的代理类,可以方便的实现对目标对象方法的调用。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包,我们通常使用带ASM
的cglib-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
:
(2)还有重写了父类RealSubject3
所有的方法(不包括final
)
(3)还有必备的toString
、equals
等方法