1、定义
代理模式(Proxy Pattern)是指为其他对象提供一种代理,以控制对这个对象的访问。
代理对象在客户端和目标对象之间起到了中介作用。
代理模式中的三个角色:
**抽象主题角色(Subject):**主要职责是声明真实主题与代理的共同接口方法,可以使接口也可以是抽象类;
**真实主题角色(RealSubject):**被代理类,该类定义了代理所表示的真实对象,是负责执行系统真正的逻辑业务对象;
**代理主题角色(Proxy):**代理类,其内部持有RealSubject的引用,因此具有完全的RealSubject的代理权。客户端调用代理对象的方法,同时也调用被代理对象的方法,但是会在代理对象前后增强一些处理代码。
属于结构型设计模式。
2、使用场景
事务处理,非侵入时日志监听等,都是代理模式的实际体现。当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过交给代理对象来间接访问。使用代理模式主要有两个目的:保护目标对象,增强目标对象的功能。
3、静态代理
显式声明被代理对象
静态代理在分布式业务场景中,通常会对数据库分库分表。分库分表之后使用Java操作室可能需要多个数据源我们可以通过设置数据源路由来动态切换数据源。
// 老王帮忙代理老张找房子
public interface IPerson {
void findHouse();
}
public class Zhang implements IPerson {
@Override
public void findHouse() {
System.out.println("张的需求:需要找一个便宜舒适的房子。");
}
}
public class LaoWang implements IPerson {
private Zhang zhang;
public LaoWang(Zhang zhang){
this.zhang = zhang;
}
@Override
public void findHouse() {
System.out.println("老王帮忙打听房源");
zhang.findHouse();
System.out.println("成功找到房子");
}
}
// 测试
public class Test {
public static void main(String[] args) {
LaoWang laoWang = new LaoWang(new Zhang());
laoWang.findHouse();
}
}
// 缺点:只能代理一个人
// 中介帮忙代理找房子
public interface IPerson {
void findHouse();
}
public class Zhang implements IPerson {
@Override
public void findHouse() {
System.out.println("张的需求:需要找一个便宜舒适的房子。");
}
}
public class Liu implements IPerson {
@Override
public void findHouse() {
System.out.println("刘的需求:需要找一个高档小区");
}
}
// 中介
public class Medium implements IPerson {
private IPerson person;
public Medium(IPerson person){
this.person = person;
}
@Override
public void findHouse() {
before();
person.findHouse();
after();
}
private void after() {
System.out.println("成功找到房子");
}
private void before() {
System.out.println("中介开始物色房源");
}
}
// 测试
public class Test {
public static void main(String[] args) {
Medium m = new Medium(new Zhang());
m.findHouse();
System.out.println("================================");
Medium m2 = new Medium(new Liu());
m2.findHouse();
}
}
4、动态代理
动态配置和替换被代理对象
动态代理跟静态代理思路基本上一致,只不过低昂太代理功能更加强大,随着业务扩展适应性更强。
JDK自带的代理类:java.lang.reflect.Proxy;
代理类继承InvocationHandler,实现invoke()方法,Object result = method.invoke(this.target,args);
public interface IPerson {
void findHouse();
}
public class Liu implements IPerson {
@Override
public void findHouse() {
System.out.println("刘的需求:需要找一个高档小区");
}
}
public class Medium implements InvocationHandler {
private IPerson person;
/**
* @Description 通过反射拿到对象,必须实现InvocationHandler接口
**/
public IPerson getInstance(IPerson person){
this.person = person;
Class<? extends IPerson> clazz = person.getClass();
return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
private void after() {
System.out.println("成功找到房子");
}
private void before() {
System.out.println("中介开始物色房源");
}
/**
* @Description 拿到代理类的方法,进行增强
**/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(this.person,args);
after();
return result;
}
}
// 测试
public class Test {
public static void main(String[] args) {
Medium medium = new Medium();
IPerson liu = medium.getInstance(new Liu());
liu.findHouse();
}
}
CGLib代理类:net.sf.cglib.proxy.Enhancer;
代理类继承MethodInterceptor接口,实现intercept()方法,
使用CGLib需要引入jar包:
cglib
cglib-nodep
2.2
public class Liu{
public void findHouse() {
System.out.println("刘的需求:需要找一个高档小区");
}
}
public class Medium implements MethodInterceptor {
public Object getInstance(Class<? > clazz) {
//相当于Proxy,代理的工具类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
private void after() {
System.out.println("成功找到房子");
}
private void before() {
System.out.println("中介开始物色房源");
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object obj = methodProxy.invokeSuper(o,objects);
after();
return obj;
}
}
// 测试
public class Test {
public static void main(String[] args) {
//JDK是采用读取接口的信息
//CGLib覆盖父类方法
//目的:都是生成一个新的类,去实现增强代码逻辑的功能
//JDK Proxy 对于用户而言,必须要有一个接口实现,目标类相对来说复杂
//CGLib 可以代理任意一个普通的类,没有任何要求
//CGLib 生成代理逻辑更复杂,效率,调用效率更高,生成一个包含了所有的逻辑的FastClass,不再需要反射调用
//JDK Proxy生成代理的逻辑简单,执行效率相对要低,每次都要反射动态调用
//CGLib 有个坑,CGLib不能代理final的方法
Liu obj = (Liu) new Medium().getInstance(Liu.class);
obj.findHouse();
}
}
Spring底层用到的就是CGLib动态代理。
区别:
JDK要想实现动态代理,被代理类必须要实现接口,因为代理类中需要传入的参数为接口;
而CGLib实现动态代理,只需要你本身是一个class类,就可以直接代理。
5、深度分析代理模式
JDK动态代理实现原理
采用字节重组,重新生成对象来替代原始对象,已达到动态代理的目的。
JDK实现动态代理步骤如下:
1.获取被代理对象的引用,并且获取它的所有接口,通过反射来获取。
2.JDK动态代理类重新生成一个新的类,同事新的类要实现被代理类所实现的所有接口。
3.动态生成Java代码,新增的业务逻辑方法由一定的逻辑代码调用。
4.动态生成源码.java文件
5.吧.java文件输出到磁盘,保存
6.把.java文件编译成.class文件
7.把.class文件加载到JVM中
8.通过字节码重组返回代理对象
以上过程就是字节码重组的所有流程,JDK中有一个规范,在ClassPath下只要是$开头的.class文件,一般都是自动生成的。只在内存中可见,在程序中不可见。
断点调试Debug
通过反编译
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class);
FileOutputStream os = new FileOutputStream(“路径名”);
os.wirte(bytes);
os.close();
就会输出一个class文件在磁盘上。
使用工具jad(http://www.kpdus.com/jad.html),实现class文件的反编译。
CGLib动态代理实现原理
cglib采用的是继承的方式,覆盖父类的方法。
cglib的执行效率要比JDK高,只要是因为cglib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个类,这个类为代理类或被代理类的方法分配一个index;这个index当做入参,FastClass就可以直接定诶到要调用的方法并直接进行调用,省去了反射调用,所以调用效率比JDK代理通过反射调用高。
CGLib和JDK动态代理对比
JDK | CGLib | |
---|---|---|
相同点 | 都通过生成字节码,重组一个新的类 | 都通过生成字节码,重组一个新的类 |
不同 | jdk采用的是实现的方式,必须要求代理的目标对象必须实现一个接口 | cglib采用的是继承的方式,覆盖父类的方法 |
优点 | Proxy对于用户而言,依赖性更强,生成逻辑相对简单 | cglib对目标类没有任何的要求,效率相比而言更高,性能更高。底层没有用到反射 |
缺点 | 调用更复杂,执行效率低,每次都要用到反射,比较消耗性能 | 目标代理类不能有final修饰的方法,会自动忽略final修饰的方法。 |
在Spring中的应用
Spring的切面编程就是通过动态代理来实现的。
Spring中的代理选择原则
1、当Bean有时限接口时,Spring就会使用JDK的动态代理。
2、当Bean没有实现接口是,Spring会选择CGLib。
3、Sprinf可以通过配置强制使用CGLib,只需要在Spring的配置文件中加入如下代码:
<aop:aspectj-autoproxy proxy-target-class="true"/>
6、代理模式总结
静态代理和动态代理本质区别
1、静态代理只能通过手动完成代理操作,如果被代理类增加了新的方法,代理类就需要同步增加,违背了开闭原则;
2、静态代理采用在运动时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则;
3、弱动态代理要对目标类的增强逻辑进行扩展,结合策略模式,只需要增强策略类便可完成,无需修改代理类的代码。
代理模式优点:
代理模式能将代理对象与真实被调用的目标对象分离。
一定程度上降低了系统的耦合程度,易于扩展。
代理可以起到保护目标对象的作用。
增强目标对象的职责。
代理模式缺点:
代理模式会造成系统设计中类的数目增加
在客户端和目标对象之间增加了一个代理对象,请求处理速度变慢
增加了系统的复杂度