什么是代理模式
定义:代理模式给个目标对象(target)提供代理对象(proxy),并由代理对象控制对目标对象的引用。
举个生活中的例子:小孩子去医院看病时,由于小朋友无法很好的描述病情,会由父母来代替向医生描述病情。这里的父母就相当于一个代理对象,小朋友相当于目标对象。医生直接访问的父母,来获取小朋友的病情。
静态代理
个人理解静态代理模式类似于装饰器模式,都是在不修改target的前提下,对target类进行扩展。
举例如下:
创建一个表示能力的接口move
,两个target类Bird
和Plane
实现能力,一个代理类StaticProxy
表示对target类能力进行扩展。
// 表示能力的接口
public interface Move {
void fly(Long ms);
void run(long ms);
}
// target类实现能力
public class Bird implements Move {
@Override
public void fly(Long ms) {
System.out.println("bird is flying!");
try {
Thread.sleep(ms);
}
catch (Exception e) {
System.err.println("err");
}
}
@Override
public void run(long ms) {
System.out.println("bird is running!");
try {
Thread.sleep(ms);
}
catch (Exception e) {
System.err.println("err");
}
}
}
// 静态代理类,对原target类能力进行拓展
public class StaticProxy implements Move {
private Move move;
public StaticProxy(Move move) {
this.move = move;
}
@Override
public void fly(Long ms) {
try {
System.out.println("现在开始,我要飞了!");
long begin = System.currentTimeMillis();
move.fly(ms);
long end = System.currentTimeMillis();
System.out.println("好累哦!飞了" + (end - begin) + "ms");
} catch (Exception e) {
e.printStackTrace();
System.out.println("invoke failed!");
throw e;
}
}
@Override
public void run(long ms) {
try {
System.out.println("现在开始,我要跑了!");
long begin = System.currentTimeMillis();
move.fly(ms);
long end = System.currentTimeMillis();
System.out.println("好累哦!跑了" + (end - begin) + "ms");
} catch (Exception e) {
e.printStackTrace();
System.out.println("invoke failed!");
throw e;
}
}
}
调用代理类结果:
可以看到,静态代理类通过实现target类相同的接口,来进行能力扩展。也就是说,如果我们的接口新增其他的方法,target类和proxy类都需要新增实现,代码量会增加。
动态代理
动态代理通过了反射的方式,将接口中声明的所有方法都在代理类中统一处理,规避了静态代理新增方法增加代码量的问题。
JDK动态代理
jdk提供了接口InvocationHandler
来实现对接口的动态代理:
使用方式
代理类:
public class JdkDynamicProxy implements InvocationHandler {
// 被代理类的目标接口
private Object targetObject;
public Object newProxyInstance(Object targetObject) {
this.targetObject = targetObject;
// loader: 代理类的类加载器,获取加载器的方法是固定的
// interfaces: 代理类要实现的接口列表
// h:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
}
/**
* 对目标方法统一进行功能扩展
*
* @param proxy 调用该方法的代理实例
* @param method 需要调用的方法
* @param args 方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result;
try {
System.out.println("现在开始,我要进行一个" + method.getName());
long begin = System.currentTimeMillis();
// 调用被代理方法
result = method.invoke(targetObject, args);
long end = System.currentTimeMillis();
System.out.println("好累哦!" + method.getName() + "了" + (end - begin) + "ms");
}
catch (Exception e) {
e.printStackTrace();
System.out.println("invoke failed!");
throw e;
}
return result;
}
}
调用代理类结果:
可以看到,动态代理可以做到一个代理类,代理多个接口,每个接口的多个方法
从原理解释只能代理接口
查看运行后,生成的代理类:
$Proxy
:生成的代理对象
Proxy
:提供创建动态代理类的方法
Move
:被代理的目标接口
可以看出,生成的代理类是继承了Proxy
类,而Java是单继承,所以只能代理接口,而不能代理另一个类。
cglib动态代理
那么,如何实现对一个类的代理呢?Spring框架提供了cglib来实现。
使用方式
继续开头的小孩子无法说明自己病情的例子。有目标类Kid,仅有字段symptom用于表示症状,无描述病情的方法:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Kid {
private String symptom;
}
通过实现MethodInterceptor
接口来实现动态代理:
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
public Object getProxyInstance() {
Enhancer en = new Enhancer();
en.setSuperclass(this.target.getClass());
en.setCallback(this);
return en.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 在调用方法前做什么
System.out.println("回忆孩子有什么症状...");
// 调用原有方法
Object returnValue = method.invoke(target, objects);
// 调用方法后做什么
System.out.println("获取到症状为:" + returnValue);
// 修改返回值
return "这孩子" + returnValue + "得睡不着啊";
}
}
实现结果:
拦截了get方法,能实现在调用方法前后操作,并且能修改返回值。
AOP
在某个方法前后额外做些事情,这是不是有点什么既视感?在Spring中AOP的实现就是基于动态代理。加入容器的目标对象有实现接口,就使用JDK代理。如果没有实现接口,就使用Cglib代理。
在了解AOP前,需要知道以下术语:
- 切点(point cut):说明在什么位置执行增强逻辑。表示被拦截的一个或多个方法,可以通过正则式和指示器来指定。
- 通知(advice):说明在什么时候执行增强逻辑。可以用注释来指定:前置通知(@Before)、后置通知(@After)、环绕通知。
(@Around)、事后返回通知(@After-returning)和异常通知(@After-throwing) - 切面(aspect):用来定义切点、各类通知和引入的内容。
- 引入(introduction):引入新的类和其方法,增强现有Bean的功能 。
- 织入(weaving):为原有服务对象生成代理对象 , 然后将与切点定义匹配的连接点拦截 ,并按约定将各类通知织入约定流程的过程 。
定义切面:
@Aspect
@Component
public class MoveAspect {
@Before("execution(* com.test.springboot.proxy.service.*.*(..))")
public void beforeMove() {
System.err.println("before");
}
}
定义切点,执行结果与上面相同:
@Aspect
@Component
public class MoveAspect {
// 定义切点,方便复用
@Pointcut("execution(* com.test.springboot.proxy.service.*.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void beforeMove() {
System.err.println("before");
}
}