在对象的一个业务方法完成之后, 有时候我们可能需要去添加一些新的功能(前置校验等). 但我们又不想更改原来的代码 , 代理模式就为我们提供了一种解决方案 .
1 . 代理模式的定义
代理模式就是在不改变原有代码的基础上 , 实现对目标对象的功能扩展 .
以现实的情况为例 , 目前有一个艺人, 她会表演和唱歌 . 现在想扩展一个行为来为这个艺人来接通告 , 通常情况下我们是不想把这个业务交给原来的艺人去做的, 毕竟也不是她的职责 . 这时候我们就可以引入一个代理对象 , 即经纪人 . 经纪人去来完成收发通告的一些扩展业务 , 但需要表演时 , 仍然要经纪人(代理对象)通知艺人(目标对象)来完成功能 .
代理模式分为静态代理 , 动态代理(根据实现方式又分为JDK动态代理和CGLIB动态代理) .
2 . 代理模式的特点
不影响原有业务代码实现新功能扩展
3 . 代理模式的主要角色
- 抽象接口 : 即目标对象实现的接口 , 在静态代理和JDK代理中是必备角色
- 目标对象 : 就是实现了具体业务的类 , 在代理模式下仍然需要目标对象来完成原本业务
- 代理对象 : 目标对象的代理类 , 对目标对象的原有业务增加扩展操作
4 . 代理模式的应用场景
最经典的就是Spring中的AOP , 它的原理就是通过动态代理来完成
5 . 代码实现
5.1 静态代理
静态代理就是通过实现与目标相同的接口 , 并注入原本的代理目标对象 . 在重写的方法中完成扩展 , 并调用目标对象来实现原有操作
package com.xbz.xstudy.shejimoshi.proxy.targetObject;
/**
* @title 需要代理的目标接口(艺人)
* @author Xingbz
* @createDate 2019-4-23
*/
public interface ActorInterface {
void show();
}
package com.xbz.xstudy.shejimoshi.proxy.targetObject;
/**
* @title 需要代理的目标实现对象(艺人)
* @author Xingbz
* @createDate 2019-4-23
*/
public class ActorInterfaceImpl implements ActorInterface {
@Override
public void show() {
System.out.println("艺人完成了一场表演 ~ ");
}
}
package com.xbz.xstudy.shejimoshi.proxy.staticState;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterface;
/**
* @title 静态代理对象(经纪人)
* @author Xingbz
* @createDate 2019-4-23
*/
public class ActorProxy implements ActorInterface {
private ActorInterface actor;
public ActorProxy(ActorInterface actor) {
this.actor = actor;
}
@Override
public void show() {
System.out.println("[静态代理]经纪人为艺人接了通告");
actor.show();//通知艺人自身来完成行为
System.out.println("[静态代理]经纪人为艺人完成通告");
}
}
package com.xbz.xstudy.shejimoshi.proxy.staticState;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterface;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterfaceImpl;
/**
* @title 静态代理测试类
* @author Xingbz
* @createDate 2019-4-22
*/
public class Demo {
public static void main(String[] args){
ActorInterface actor = new ActorInterfaceImpl();
actor.show();//正常执行方法
ActorInterface actorProxy = new ActorProxy(actor);
actorProxy.show();//实际还是actor自身的操作, 但在方法执行前后会有一些经纪人的代理操作
}
}
静态代理的优点 : 可以做到不对目标对象进行任何修改的前提下 , 将目标对象实现功能扩展
静态代理的缺点 : 因为代理对象需要实现与目标对象一样的接口 , 会导致代理类非常繁多 , 不易维护 . 并且如果接口增加方法 , 目标对象和代理对象都需要变动
5.2 JDK动态代理
JDK动态代理就是指动态的在内存中创建代理对象 . 不用实现任何借口 , 通过传入的目标对象和指定接口 , 利用反射调用JDK API动态的创建代理类
package com.xbz.xstudy.shejimoshi.proxy.dynamic.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @title 动态代理Handler
* @author Xingbz
* @createDate 2019-4-23
*/
public class ProxyHandler implements InvocationHandler {
private Object targetObject;//用于接收代理目标实际对象
public ProxyHandler(Object targetObject) {
this.targetObject = targetObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[JDK动态代理]经纪人为艺人接了通告");
method.invoke(targetObject, args);//通知艺人自身来完成行为
System.out.println("[JDK动态代理]经纪人为艺人完成通告");
return proxy;
}
}
package com.xbz.xstudy.shejimoshi.proxy.dynamic.jdk;
import com.xbz.xstudy.shejimoshi.proxy.ProxyClassOutUtil;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterface;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.ActorInterfaceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @title JDK动态代理测试类
* @author Xingbz
* @createDate 2019-4-23
*/
public class Demo {
public static void main(String[] args){
ActorInterface actor = new ActorInterfaceImpl();
InvocationHandler actorHandler= new ProxyHandler(actor);
ActorInterface actorProxy = (ActorInterface) Proxy.newProxyInstance(ActorInterface.class.getClassLoader(), new Class[]{ActorInterface.class}, actorHandler);
actorProxy.show();
}
}
JDK动态代理的优点 : 无需实现接口 , 免去编写很多代理类的繁琐
JDK动态代理的缺点 : 代理对象不需实现接口 , 但目标对象必须实现接口 , 如果目标对象没有接口的话就不能使用JDK动态代理了
5.3 CGLIB动态代理
cglib是通过扫描该类及其父类中的所有public方法 , 通过asm动态生成该类的子类字节码 . 在子类中重写了该类的所有方法 , 加入扩展逻辑 , 然后返回该子类的实例作为代理类 . 即是说 , cglib是通过该类的子类作为代理类来实现代理操作的 , 所以代理目标中的非public方法及static/final修饰的方法 , 不能实现代理 .
asm是一个小而快的字节码处理框架 , 它负责生成从代理目标类中扫描出的方法字节码 , 并将这些字节码暂存在内存中 . 然后我们通过某种方式将这些字节码转换为class,最勇利用反射创建代理类的实例返回 .
使用该方法需要引入cglib和asm依赖 , spring已经内嵌了这两个jar包 . 如果项目中有用到spring则无需再引入 .
package com.xbz.xstudy.shejimoshi.proxy.targetObject;
/**
* @title 未实现接口的代理目标对象(主播)
* @author Xingbz
* @createDate 2019-4-23
*/
public class Anchor {
public void sing(String song) {
System.out.println("主播唱了一首歌 : " + song);
}
}
package com.xbz.xstudy.shejimoshi.proxy.dynamic.cglib;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @title CGLIB动态代理Inerceptor
* @description
* @author Xingbz
* @createDate 2019-4-23
*/
public class MyMethodInerceptor implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("[CGLIB动态代理]助理为主播选了一首歌");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("[CGLIB动态代理]助理带头为主播刷礼物");
return result;
}
}
package com.xbz.xstudy.shejimoshi.proxy.dynamic.cglib;
import com.xbz.xstudy.shejimoshi.proxy.ProxyClassOutUtil;
import com.xbz.xstudy.shejimoshi.proxy.targetObject.Anchor;
import org.springframework.cglib.proxy.Enhancer;
/**
* @title CGLIB动态代理测试类
* @author Xingbz
* @createDate 2019-4-23
*/
public class Demo {
public static void main(String[] args){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Anchor.class);
enhancer.setCallback(new MyMethodInerceptor());
Anchor anchor = (Anchor) enhancer.create();
anchor.sing("今天是个好日子");
}
}
CGLIB动态代理的优点 : 无需目标对象实现接口
CGLIB动态代理的缺点 : 非public , 或statsi/final修饰的方法无法被代理
6 . 总结
三种代理各有优缺点和适用范围 , 主要看目标是否实现了接口 . 在Spring框架的AOP中 , 如果纳入容器的bean有实现接口 , 则用JDK代理 . 如果没有实现接口 , 则用CGLIB代理