一、前言
代理模式是一种很常见的设计模式,在多线程、aop、远程代理等场景中均有体现,代理在我们的生活中也是很随处可见的。代理模式即为一个真实对象提供了一个代理对象,这个代理除了拥有真实对象的功能外,还能在不改变真实对象的情况下进行功能的增强且也能达到安全控制的效果。
1、代理分类
- 静态代理
- 动态代理
- 基于接口:JDK代理
- 基于类:Cglib代理
2、代理角色
- AbstractSubject(抽象角色):真实角色和代理角色都实现的接口或抽象类
- RealSubject(真实角色):真正提供功能的对象
- Proxy(代理角色):代理真实对象的代理对象
- Client(客户):使用代理完成完成请求的角色
二、静态代理
1、静态代理理解
静态代理需要定义接口(或类),代理对象和被代理对象都需要实现这个接口(或类)。为什么是静态的呢?是因为我们为每个真实对象提供代理对象的时候,代理对象都是先创建好的,即在程序中要使用代理必先有代理类。
2、静态代理
在我们的web项目开发中,一般的业务逻辑(CRUD)实现写在Service层,但是当我们需要扩展Service层功能的时候由于原先的项目已稳定运行不能随意修改。假如有一个新的需求:在新增方法之前添加一个校验功能检查数据的正确性。下面提供几个做法:
- 我们是可以在原先的Service层对应的方法进行代码开发。但是在原来功能稳定运行的情况下去修改原有的逻辑,无疑是开发中的大忌!
- 其次我们也可以通过继承方式来实现,每当我们有新需求就得再继承一次,这样做造成程序耦合度高,所以慎用继承。
- 最后一个方法就是使用了代理模式,来看看它的好处。
3、demo
- 创建接口(抽象角色),暴露接口的功能
package com.spring.proxy.service;
public interface UserService {
void add();
void del();
void update();
void search(int id);
}
- 创建实现类(真实角色),实现功能
package com.spring.proxy.service.impl;
import com.spring.proxy.service.UserService;
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("新增加一条数据!");
}
@Override
public void del() {
System.out.println("删除了一条数据!");
}
@Override
public void update() {
System.out.println("更新了一条数据");
}
@Override
public void search(int id) {
System.out.println("返回查找一条数据!");
}
}
- 创建代理类(代理角色),并进行功能增强
package com.spring.proxy.demo;
import com.spring.proxy.service.UserService;
public class UserServiceProxy implements UserService {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
@Override
public void add() {
valid();
userService.add();
}
@Override
public void del() {
}
@Override
public void update() {
}
@Override
public void search(int id) {
}
//进行功能的增强检验功能
private void valid() {
System.out.println("检查数据合法性...");
}
}
- 通过代理调用目标对象方法
@Test
public void testStaticProxy(){
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
//设置代理对象
proxy.setUserService(userService);
//通过代理调用新增功能
proxy.add();
}
/**
结果:
检查数据合法性...
新增加一条数据!
*/
可以看到在不改变原有代码前提下进行了功能增强是不是很妙,使用代理模式虽然并不能减少我们的代码量,但是能降低我们修改代码造成的风险,且使用代理模式各个角色更好的实现了业务的分工。但是静态代理也是有缺点的:
- 因为真实对象和代理对象都实现同一个接口,当接口变动对象的实现类和代理类也要维护
- 静态代理需提前写好代理类
- 当代理的接口增多,代理类也会增多,导致开发效率低且容易造成代码冗余
既然静态代理有问题,肯定有解决,那就是动态代理
三、JDK的动态代理(基于接口)
1、动态代理理解
动态代理和静态代理的角色都是一样的,区别在于静态代理的代理类需要我们提前写好,而动态代理的代理类是动态生成的,使用的是jdk的api动态在内存中构建代理对象。
2、JDK的动态代理
实现jdk动态代理核心的两个类InvocationHandler、Proxy
- InvocationHandler:在代理对象调用目标对象的方法时,调用被分派到InvocationHandler的invoke方法处理。来看看invoke方法的三个参数。
- proxy: 代理对象
- method:代理对象要调用目标对象的某个方法的Method对象
- args:目标对象对应方法的参数列表
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
- Proxy :提供了创建动态代理类和实例的静态方法
- loader:目标对象使用的类加载器
- interfaces:目标对象实现的接口列表
- h:代理对象调用目标对象方法时关联的InvocationHandler对象
- 返回值:实现的接口
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h)
3、demo
需求:测试上面逻辑代码每个功能方法的运行效率,进行性能优化。
- 动态代理类,当使用代理对象调用目标对象的方法时分配到newProxyInstance方法第三参数InvocationHandler(调用程序处理器)对象的invoke方法,下面已使用lambda表达式实现该接口且重写了invoke方法。当然也可以使用匿名内部类实现InvocationHandler接口。又或者自行写一个InvocationHandler实现类进行参数传递实现动态代理。当然lambda表达式比较装逼!
package com.spring.proxy.dynamicProxy;
import java.lang.reflect.Proxy;
public class UserProxy {
//可通过构造器或setter初始化目标对象
private Object target;
public UserProxy(Object target) {
this.target = target;
}
/**
* 获取代理对象
*/
public Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), (proxy, method, args) -> {
long startTime = System.nanoTime();
Object value = method.invoke(target, args);
long endTime = System.nanoTime();
System.out.println(method.getName() + "方法耗时:" + (endTime - startTime));
return value;
});
}
}
- 测试
@Test
public void testDynamicProxy(){
//创建目标对象
UserService userService = new UserServiceImpl();
//创建代理对象
UserService proxy = (UserService) new UserProxy(userService).getProxy();
//通过代理对象调用目标对象方法
proxy.add();
proxy.del();
proxy.update();
proxy.search();
}
/**结果
新增加一条数据!
add方法耗时:96201
删除了一条数据!
del方法耗时:25099
更新了一条数据
update方法耗时:51600
返回查找一条数据!
search方法耗时:21700
*/
- 调试分析
通过调试可以看到proxy对象为动态生成的,根据传参生成UserServiceImpl代理类,再根据代理对象调用目标对象的方法,通过反射获取到Method对象调用实现类中的方法,args为实现方法的参数列表,进一步验证了我们上面的结论。
四、cglib代理(基于类)
1、cglib代理理解
上面我们看到静态代理和JDK动态代理都是基于接口,目标对象都需要实现一个接口。如果目标对象并没有实现任何接口,这个时候就可以通过cglib代理来实现动态代理。
2、cglib动态代理
-
cglib基于AMS框架实现,需要导入四个jar包
-
被代理的类不能是final修饰,因为final不能被继承,会抛出java.lang.IllegalArgumentException
-
目标对象的方法如果为final/static代理对象不会执行目标对象的不会执行目标对象额外的增强方法
3、demo
需求:在每个方法执行前新增日志打印功能。
- 动态代理类
package com.spring.proxy.cglibProxy;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class UserSerProxy implements MethodInterceptor {
//目标对象
private Object target;
public void setTarget(Object target) {
this.target = target;
}
/**
* 获取代理对象
*/
public Object getProxy() {
//创建Enhancer对象
Enhancer enhancer = new Enhancer();
//设置目标对象为父类
enhancer.setSuperclass(target.getClass());
//设置回调函数,参数实现MethodInterceptor的对象即当前对象
enhancer.setCallback(this);
//创建子类对象即代理对象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
log("准备执行" + method.getName());
return method.invoke(target, objects);
}
private void log(String msg) {
System.out.println("[自定义日志:]" + msg);
}
}
- 测试
@Test
public void testCglibProxy(){
//创建目标对象
UserServiceImpl userService = new UserServiceImpl();
//获取代理对象
UserSerProxy userSerProxy = new UserSerProxy();
userSerProxy.setTarget(userService);
UserServiceImpl proxy = (UserServiceImpl) userSerProxy.getProxy();
//通过代理对象调用目标对象的方法
proxy.add();
proxy.del();
proxy.update();
proxy.search(1);
}
/**结果:
[自定义日志:]准备执行add
新增加一条数据!
[自定义日志:]准备执行del
删除了一条数据!
[自定义日志:]准备执行update
更新了一条数据
[自定义日志:]准备执行search
返回查找一条数据!
*/
五、代理模式深入理解
我们都指定aop的底层就是代理,那么在spring中使用aop有什么效果呢?如下图,我们开发中都是纵向开发一个功能,当功能稳定运行但需要增强功能的时候,这就需要横向开发达到不影响原来逻辑的情况下完成补强。至于选择何种代理模式取决于你的项目需求。