在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
一、静态代理
静态代理的实现方式是实现公共接口或继承抽象类
角色:
- 抽象⻆⾊:声明真实对象和代理对象的共同接⼝;
- 代理⻆⾊:代理对象⻆⾊内部含有对真实对象的引⽤,从⽽可以操作真实对象,同时代理对象提供与真实 对象相同的接⼝以便在任何时刻都能代替真实对象。同时,代理对象可以在执⾏真实对象操作时,附加其 他的操作,相当于对真实对象进⾏封装。
- 真实⻆⾊:代理⻆⾊所代表的真实对象,是我们最终要引⽤的对象。
/**
* @author GodLiang
* @version 1.0
* @date 2021/2/14 15:38
* @description: TODO 静态代理
*/
public class StaticProxy {
public static void main(String[] args) {
Subject proxySubject = new ProxySubject(new RealSubject());
proxySubject.request();
}
}
/**
* 抽象接口角色 规范请求的方法
*/
interface Subject {
/**
* 请求方法
*/
void request();
}
/**
* 真实对象。被代理的对象
*/
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("发送一个请求....");
}
}
/**
* 代理角色
*/
class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
if (realSubject == null)
throw new NullPointerException("object not null");
this.realSubject = realSubject;
}
@Override
public void request() {
try {
//执行前置增强方法
beforeRequest();
//执行原生方法
realSubject.request();
//执行后置增强方法
AfterRequest();
}catch (Exception e) {
//异常处理
solveException();
}
}
/**
* 前置增强
*/
public void beforeRequest() {
System.out.println("前置增强");
}
/**
* 后置增强
*/
public void AfterRequest() {
System.out.println("后置增强");
}
/**
* 异常处理
*/
public void solveException() {
System.out.println("异常处理");
}
}
代理类与被代理类实现同一个接口。加一些加强方法。在方法内调用增强方法进行增强
优缺点:
- 优点:可以做到在不修改⽬标对象的功能前提下,对⽬标功能扩展.
- 缺点:每⼀个代理类都必须实现⼀遍委托类(也就是realsubject)的接⼝,如果接⼝增加⽅法,则代理类 也必须跟着修改。其次,代理类每⼀个接⼝对象对应⼀个委托对象,如果委托对象⾮常多,则静态代理类 就⾮常臃肿,难以胜任。
二、JDK动态代理
动态代理解决静态代理中代理类接⼝过多的问题,通过反射来实现的,借助Java⾃带的 java.lang.reflect.Proxy,通过固定的规则⽣成。
/**
* @author GodLiang
* @version 1.0
* @date 2021/2/14 16:16
* @description: TODO 动态代理
*/
public class JdkProxy {
public static void main(String[] args) {
Subject subject = new RealSubject();
InvocationHandler dynamicProxy = new DynamicProxy(subject);
ClassLoader classLoader = RealSubject.class.getClassLoader();
Subject newProxySubject = (Subject) Proxy.newProxyInstance(classLoader, new Class[]{Subject.class}, dynamicProxy);
newProxySubject.request();
}
}
/**
* 抽象接口角色 规范请求的方法
*/
interface Subject {
/**
* 请求方法
*/
void request();
}
/**
* 真实对象。被代理的对象
*/
class RealSubject implements Subject {
@Override
public void request() {
System.out.println("发送一个请求....");
}
}
/**
* 动态代理类
*/
class DynamicProxy implements InvocationHandler {
Object object;
public DynamicProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object invoke = null;
try {
//前置增强
beforeRequest();
//执行方法
invoke = method.invoke(object, args);
//后置增强
AfterRequest();
}catch (Exception e) {
//异常处理
solveException();
}
return invoke;
}
/**
* 前置增强
*/
public void beforeRequest() {
System.out.println("前置增强");
}
/**
* 后置增强
*/
public void AfterRequest() {
System.out.println("后置增强");
}
/**
* 异常处理
*/
public void solveException() {
System.out.println("异常处理");
}
}
上述代码的关键是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)⽅法,该⽅法会根据指定的参数动态创建代理对象。三个参数的意义如下:
- loader,指定被代理对象实现类(Subject)的类加载器;
- interfaces,代理对象需要实现的接⼝,可以同时指定多个接⼝;
- handler,⽅法调⽤的实际处理者,代理对象的⽅法调⽤都会转发到这⾥(*注意1)。
三、cglib动态代理
注:引入cglib.jar包和相关依赖asm
/**
* @author GodLiang
* @version 1.0
* @date 2021/2/14 17:07
* @description: TODO cglib动态代理
*/
public class CglibProxy {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloController.class);
enhancer.setCallback(new MyMethodInterceptor());
HelloController helloController = (HelloController) enhancer.create();
helloController.hello();
}
}
/**
* 被代理的类 无接口
*/
class HelloController {
public void hello() {
System.out.println("hello world");
}
}
/**
* cglib动态代理实现类
* ⾸先实现⼀个MethodInterceptor,⽅法调⽤会被转发到该类的intercept() ⽅法。
*/
class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//todo 前置加强...
//方法代理 注意这里是methodProxy 不是用method进行反射
Object invoke = methodProxy.invokeSuper(o, objects);
//todo 后置加强...
return invoke;
}
}
通过CGLIB的Enhancer来指定要代理的⽬标对象、实际处理代理逻辑的对象,最终通过调⽤create()⽅法 得到代理对象,对这个对象所有⾮final⽅法(非final是因为final的方法无法被继承)的调⽤都会转发给MethodInterceptor.intercept()⽅法,在 intercept()⽅法⾥我们可以加⼊任何逻辑,⽐如修改⽅法参数,加⼊⽇志功能、安全检查功能等;通过调 ⽤MethodProxy.invokeSuper()⽅法,我们将调⽤转发给原始对象,具体到本例,就是HelloController的 具体⽅法。 对于从Object中继承的⽅法,CGLIB代理也会进⾏代理,如hashCode()、equals()、toString()等,但是 getClass()、wait()等⽅法不会,因为它是final⽅法,CGLIB⽆法代理。
原理:
CGLIB是⼀个强⼤的⾼性能的代码⽣成包,底层是通过使⽤⼀个⼩⽽快的字节码处理框架ASM,它可以在 运⾏期扩展Java类与实现Java接⼝ Enhancer是CGLIB的字节码增强器,可以很⽅便的对类进⾏拓展 创建代理对象的⼏个步骤:
- ⽣成代理类的⼆进制字节码⽂件
- 加载⼆进制字节码,⽣成Class对象( 例如使⽤Class.forName()⽅法 )
- 通过反射机制获得实例构造,并创建代理类对象
四、关系总结
-
jdk动态代理:利⽤拦截器(拦截器必须实现InvocationHanlder)加上反射机制⽣成⼀个实现代理接⼝的 匿名类,在调⽤具体⽅法前调⽤InvokeHandler来处理。只能对实现了接⼝的类⽣成代理只能对实现了接⼝的类⽣成代理
-
cglib:利⽤ASM开源包,对代理对象类的class⽂件加载进来,通过修改其字节码⽣成⼦类来处理。主 要是对指定的类⽣成⼀个⼦类,覆盖其中的⽅法,并覆盖其中⽅法实现增强,但是因为采⽤的是继承, 对于final类或⽅法,是⽆法继承的。
-
选择
a、如果⽬标对象实现了接⼝,默认情况下会采⽤JDK的动态代理实现AOP。
b、如果⽬标对象实现了接⼝,可以强制使⽤CGLIB实现AOP。
c、如果⽬标对象没有实现了接⼝,必须采⽤CGLIB库,Spring会⾃动在JDK动态代理和CGLIB之间转 换。