前置简介
上一节:https://juejin.im/post/6865852407239147528/
我们说了使用装饰器模式给方法实现额外的功能,这样做的好处是我们无需改动原有的代码,
但是这样有一个缺点,每实现一个额外的功能,我们都需要新增加一个接口实现类,并且改动一些调用时候的代码,
那么可不可以我固定写好了一个类方法,程序可以自动的给我这个方法前后添加一些功能呢,如下:
public class DataService {
public void 撸铁健身(String name) {
System.out.println(name + "正在健身中");
}
}
我这个 DataService
就是一个类,也不想要继承任何接口,但就是希望在 某某某正在健身中 的前后分别打印出
某某某正在吃饭中
某某某健身完毕
这时候就需要用到动态代理
了,上一篇我们说到的装饰器模式实现的代理,称为静态代理
,
因为我们是手动写好代码并进行调整编译的,所以称为静态代理,
下面我们来看看如何实现动态代理。
使用JDK自带的动态代理
首先我们创建一个接口基类, 因为JDK自带的动态代理是必须要基于接口的。
// 接口
public interface DataService {
String 撸铁健身(String name);
}
创建一个接口实现类
// 接口实现类
public class DataServiceImpl implements DataService {
public String 撸铁健身(String name) {
System.out.println(name + "正在健身中");
return "健身完毕";
}
}
下面创建一个调用处理类,也就是切面,在调用方法的前后做一些处理,实现java.lang.reflect.InvocationHandler
这个接口
// 切面类
public class EatProxy implements InvocationHandler {
// 代理对象
DataService delegate;
// 这里要注入需要被代理的对象,也就是健身类
public EatProxy(DataService delegate) {
this.delegate = delegate;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 在调用健身方法前,进行吃饭操作
System.out.println(args[0] + "正在吃饭中");
// 调用真正的方法
return method.invoke(delegate,args);
}
}
创建代理类并进行方法调用
public class ApplicationMain {
public static void main(String[] args) {
// 委托类
DataService service = new DataServiceImpl();
// 代理类
// 三个参数
// 第一个是定义生成的动态代理类的类加载器
// 第二个是需要被代理的接口
// 第三个是切面处理类
DataService dataService = (DataService)Proxy.newProxyInstance(service.getClass().getClassLoader(),new Class[]{DataService.class}, new EatProxy(service));
// 执行方法并输出结果
System.out.println(dataService.撸铁健身("张三"));
}
}
这么写,好像还有点类似装饰器模式,但是其实是有差别的,这一个代理类可以代理相关接口的所有方法,
我们只需要在实现的InvocationHandler
类中的invoke
方法通过method
方法名做相应的处理就好了,
使用装饰器模式则需要实现每个接口方法并做处理。
JDK动态代理的优点是:方便,不需要依赖任何第三方的库。
缺点是:功能受限,只适用于接口,class则不行。
CGLIB/ByteBuddy
JDK自带的动态代理仅支持接口,但是我们使用CGLIB/ByteBuddy这些第三方库后就可以直接对class类进行代理了。
CGLIB/ByteBuddy的本质是对class类文件生成一个子类,如下:
这个是我们自己编写的类
public class DataService {
public void sayHello() {
System.out.println("hello");
}
}
CGLIB/ByteBuddy会偷偷的帮我们生成一个子类,这就是他的核心原理。
public class DataService$EnhancedByCglib extend DataService {
public void sayHello() {
...执行一些操作
super.hello();
...执行一些操作
}
}
下面通过CGLIB实现一个demo:
首先要在项目中引入jar包:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
实现代码:
public class ApplicationMain {
static DataService service = new DataService();
// 设置一个切面处理
public static class LogInterceptor implements MethodInterceptor {
private DataService delegate;
public LogInterceptor(DataService delegate) {
this.delegate = delegate;
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println(method.getName() + "执行了, 参数是:" + Arrays.asList(objects));
// 执行真正的方法
Object returnValue = method.invoke(delegate, objects);
System.out.println(method.getName() + "返回结果了,结果是:" + returnValue);
return returnValue;
}
}
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
// 设置父类
enhancer.setSuperclass(DataService.class);
// 设置切面处理
enhancer.setCallback(new LogInterceptor(service));
// 创建子类
DataService enhancerService = (DataService)enhancer.create();
// 调用方法
enhancerService.撸铁健身("张三");
}
}
通过调试器查看,可以看到,这个类真正运行的时候,是一个名字叫做DataServiceEnhancerByCGLIBEnhancerByCGLIBEnhancerByCGLIB…的类,所以证明这个是通过父类继承的方式使用的。
查看输出结果,可以看到我们设置的切面执行了。
但是即便如此,我们还是觉得有一些不方便,因为我们设置了一个切面后,还需要手动将切面设置到动态代理类中,
下一节我们说说Aop在Spring中的使用,看看Spring是如何给我们带来方便的。
本篇代码示例:https://github.com/qiaomengnan16/DecoratorPatternDemo
下篇链接:https://juejin.im/post/6866411981163724814/