您现在的位置是:首页 >技术教程 >1. 设计模式之代理模式网站首页技术教程
1. 设计模式之代理模式
简介1. 设计模式之代理模式
前言
我们都知道java是面向对象的,他追求万物皆对象,将核心业务逻辑封装到一个个对象中。这样在设计中确实符合高内聚,低耦合的理念。但是有个问题是真实业务场景中,我们有时候需要在一些方法中加入通用的处理逻辑,增强原来的业务功能。
比如:我想看项目中某一类方法的处理时间,一种方式是我在每个方法前后加打印日志计算时间的逻辑,这样可以实现,但是有几个缺点:
- 花很多人力做重复劳动,而且容易修改错改漏。
- 如果计算时间逻辑(想加入的增强通用逻辑)有调整,又得重新改一遍
- 对原来的业务代码有侵入,极不推荐这样实现
代理,就是解决上面问题的很好的方案。我们将针对对象的访问,交给代理对象来控制,可以在代理对象的前后做一些计算时间、打印日志、事务控制等通用逻辑。
具体如何做,本节我们将会详细介绍。
代理中三种核心角色
- 抽象角色:
定义代理角色和真实角色的公共对外方法 - 真实角色
实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。关注真正的业务逻辑! - 代理角色
实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制放到代理角色中处理(比如日志切面,事务管理)!
1. 静态代理
1.1 简介
静态代理,就是每次我们需要代理某一类抽象角色的时候,创建相关静态代理类,完成相关的增强功能。可以实现功能的增强,但是需要维护很多的静态代理类。
1.2 定义抽象角色
package com.wanlong.design_pattern.structure.proxy;
public interface IRentHouse {
void rentHouse();
}
1.3 定义真实角色
package com.wanlong.design_pattern.structure.proxy;
public class RentHouse implements IRentHouse {
@Override
public void rentHouse() {
System.out.println("租房子");
}
}
1.4 定义代理角色
package com.wanlong.design_pattern.structure.proxy;
public class RentHouseProxy implements IRentHouse {
private IRentHouse rentHouse;
public RentHouseProxy(IRentHouse rentHouse) {
this.rentHouse = rentHouse;
}
@Override
public void rentHouse() {
System.out.println("交中介费");
rentHouse.rentHouse();
System.out.println("租房后维修清洁");
}
}
1.5 测试
public class Mytest {
@Test
public void testStaticProxy() {
RentHouse rentHouse = new RentHouse();
RentHouseProxy rentHouseProxy = new RentHouseProxy(rentHouse);
rentHouseProxy.rentHouse();
}
}
1.6 运行结果分析
运行结果:
交中介费
租房子
租房后维修清洁
通过上面例子,可以看到:
- 通过对代理对象的访问,实现对真实对象的调用
- 代理对象增强了真实对象的功能,可以在调用真实对象前后做一些处理
- 真实对象和代理对象要实现相同的接口
- 缺点是当真实类很多的时候,需要创建很多的静态代理类,这样构建的代理类的代码量是非常大的
2.动态代理
2.1 jdk动态代理
2.1.1 简介
java自己实现的代理方式,好处是使用简单,方便,缺点是不能代理对象,要求被代理对象必须实现接口。
2.1.2 测试类
@Test
public void testJDKDynamicProxy() {
//1.创建真实对象
RentHouse rentHouse = new RentHouse();
//2. 获取被代理实现类对象的Class对象
Class<? extends IRentHouse> clazz = rentHouse.getClass();
//第一个参数,被代理对象的类加载器
ClassLoader classLoader = clazz.getClassLoader();
//第二个参数,被代理对象实现的所有接口数组
Class<?>[] interfaces = clazz.getInterfaces();
//通过调用Proxy.newProxyInstance的方法来创建一个代理类对象来代理真实对象
//第三个参数InvocationHandler的实现类,这里用了匿名内部类的方式
IRentHouse proxyBean = (IRentHouse) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
@Override
//重写InvocationHandler的invoke方法,他有三个参数可以供我们使用
//第一个参数proxy 可以通过proxy.getClass().getClassName() 来查看对象,是一个匿名的Proxy的代理类
//第二个参数method 也就是被代理对象myClac对象的方法,可以通过method调用myClac的方法
//第三个参数args 也就是真实对象方法执行时传入的实际参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置处理,比如交中介费------");
//method.invoke的返回值就是真实对象的方法的返回值
Object invoke = method.invoke(rentHouse, args);
System.out.println("后置增强,比如打扫房间------");
return invoke;
}
});
//代理对象调用
proxyBean.rentHouse();
}
2.1.3 运行结果分析
运行结果:
前置处理,比如交中介费------
租房子
后置增强,比如打扫房间------
注意事项:
- 通过动态代理实现了业务功能的增强
- 没有新建静态代理类,避免了后续维护静态代理类的代码烦恼
- jdk代理实现方式相对简单固定,调用方法Proxy.newProxyInstance,传入真实类的类加载器,真实对象的接口,实现InvocationHandler的匿名内部类
- jdk动态代理要求真实对象必须实现接口,所以如果目标对象没有实现接口,则只能选择其他动态代理方式
2.2 cglib动态代理
2.2.1 简介
上面提到了jdk动态代理使用简单,但是有缺陷是不能代理没有实现接口的类。但是如果真的的有需求要代理这种类,我们可以通过本节介绍的CGLib实现。在一些框架中,考虑到性能,一般可能两种代理方式都会存在。如果能用jdk动态代理,就用jdk动态代理。如果jdk动态代理没法实现,用cglib动态代理。比如spring框架。
2.2.2 定义抽象角色
public interface IRentHouse {
void rentHouse();
void say();
void say2();
}
2.2.3 定义真实角色
public class RentHouse implements IRentHouse {
@Override
public void rentHouse() {
System.out.println("租房子");
}
@Override
public void say() {
System.out.println("hello");
say2();
}
@Override
public void say2() {
System.out.println("hello2");
}
public final void finalMethod() {
System.out.println("final method");
}
public static void staticMethod() {
System.out.println("static method");
}
}
2.2.4 CGlib动态代理第一种方式
package com.wanlong.design_pattern.structure.proxy.dynamic;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyIntercepter implements MethodInterceptor {
private Object target;
public MyIntercepter(Object target) {
this.target = target;
}
/**
* @param o 代理对象
* @param method 被代理对象的方法
* @param objects 方法入参
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib before");
//1. 调用代理类FastClass对象
//Object result = methodProxy.invokeSuper(o, objects);
//2. 使用传对象的方式创建代理对象
Object result = methodProxy.invoke(target, objects);
System.out.println("cglib after");
return result;
}
}
2.2.5 CGlib动态代理第二种方式
public class MyIntercepter implements MethodInterceptor {
private Object target;
public MyIntercepter(Object target) {
this.target = target;
}
/**
* @param o 代理对象
* @param method 被代理对象的方法
* @param objects 方法入参
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib before");
//1. 调用代理类FastClass对象
Object result = methodProxy.invokeSuper(o, objects);
//2. 使用传对象的方式创建代理对象
//Object result = methodProxy.invoke(target, objects);
System.out.println("cglib after");
return result;
}
}
2.2.6 测试类
@Test
public void testCglibDynamicProxy() {
IRentHouse target=new RentHouse();
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(RentHouse.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyIntercepter(target));
// 创建代理对象
RentHouse rentHouse = (RentHouse) enhancer.create();
// 通过代理对象调用目标方法
rentHouse.rentHouse();
System.out.println("-----------");
rentHouse.say();
System.out.println("-----------");
rentHouse.finalMethod();
RentHouse.staticMethod();
//final,static等方法不会代码增强,say()方法内部调用say2(),say2()方法也会进行增强。具体原因后面代理类字节码反编译解释。
}
2.2.7 运行结果分析
运行结果1:
cglib before
租房子
cglib after
-----------
cglib before
hello
hello2
cglib after
-----------
final method
static method
运行结果2:
cglib before
租房子
cglib after
-----------
cglib before
hello
cglib before
hello2
cglib after
cglib after
-----------
final method
static method
注意事项:
- 通过结果可以看到,通过代理对象,可以正常访问目标对象,并且在调用前后做一些事情
- 可以看到在上面的例子中,say方法调用了say2方法
- 第一种结果我们只拦截了say,say里面调用的方法没有拦截
- 第二种方式,say和say里面调用的say2都做了拦截,在前后打印了日志,另外可以留意一下拦截的顺序
- final,static等方法不会代码增强
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可。
- 关于CGlib更多回调函数的使用,比如懒加载,可以看我之前的博客
3. 代理实际应用场景
安全代理:屏蔽对真实角色的直接访问。
远程代理:通过代理类处理远程方法调用(RMI)
延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。
3.1 在框架中的应用
- Hibernate中延时加载的实现
- mybatis中实现拦截器插件
- AspectJ的实现
- spring中AOP的实现(日志拦截、声明式事务处理)
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。