面试的时候经常会被问到 Spring 里面 AOP 的代理模式?jdk 的代理模式和 cglib 的代理模式又啥区别?
这次要跟大家分享的是设计模式中三大类创建型中的代理模式,代理模式在业务场景上我们可能不会经常用到,但是面试官却会经常问一个问题。
接下来开始一步步分析一下代理模式。
定义
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介
举个例子来说明:假如说我现在想租房,虽然我可以自己去找房源,但是你获取房源的资源太少了,还要自己去找房源,这太浪费时间和精力了。我只是想租个房子而已,为什么我还要额外做这么多事呢?于是我就通过中介来租房,他们来给我找房源,帮我办理房子入住的手续,然后付钱就可以了,如下图所示:
比如我们代码中的日志和事务这些操作,不属于业务范围内的事,一个交给专门负责这部分的类来做。
下面我们看一下代理模式有哪些部分构成:
从图中可以看出整个接口还是很简单,就是一个真实对象以及代理对象。
代码实现
这里以一个上学传字条的例子(我经常这么干),假如现在同学小明想问同学小天放学要不要去打篮球,因为现在是上课时间,又不能大声说,同学小明和同学小天之间坐了一个同学小红,所以现在同学小明只能是先找到同学小红把纸条给它,让他转告同学小天,但是去打篮球还是不是不去打,那还是只能真正的同学小天自己才能决定。
所以代理模式可以理解为同学小红是同学小天的代理,同学小明要找同学小天,只能找到同学小红,通过同学小红转达同学小天,同时将同学的小天的执行结果反馈给同学小明。
接下来看具体的代码:
定义一个共同的接口(就是一起做的事,比如打篮球)
public interface Subject {
// 共同的接口
void doSomething();
}
构建一个真实对象,即例子中的同学小天
public class RealSubject implements Subject {
// 真实对象
@Override
public void doSomething() {
System.out.println("放学去打篮球");
}
}
构建代理对象,即同学小红,那么可以看到同学小明并没有真实接触到同学小天,通过同学小红对同学小天的代理就能知道同学小天放学能不能跟他一起去打篮球。
public class ProxySubject implements Subject {
private RealSubject realSubject;
public ProxySubject(RealSubject realSubject) {
this.realSubject = realSubject;
}
public ProxySubject() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
this.realSubject = (RealSubject) this.getClass().getClassLoader().loadClass("com.ao.bing.demo.proxyPattern.RealSubject").newInstance();
}
@Override
public void doSomething() {
realSubject.doSomething();
}
public static void main(String[] args) {
try {
new ProxySubject().doSomething();
// 打印结果: 放学去打篮球
} catch (Exception e) {
// 异常情况
}
}
}
静态代理
在上面的举的列子实现其实就是静态代理,大家可以看到整体也比较简单。但是它的缺点也很明显,就是为每一个对象都创建一个代理类,增加了维护成本以及开发成本。这时候就需要用到动态代理了。
动态代理
所谓动态代理,就是在程序运行时,动态的为被代理对象生成代理类,这就需要借助编程语言当中的"反射"特性 。
写动态代理首先需要理解两个东西:
Proxy :可以理解为就是调度器。
InvocationHandler :增强服务接口可以理解为代理器。所以个人理解动态代理其实就是一种行为的监听。
具体的实现过程如下:
实现InvocationHandler接口,定义调用方法前后所做的事情
public class MyInvocationHandler implements InvocationHandler {
private Object object;
public MyInvocationHandler(Object object){
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "方法调用前");
method.invoke(object, args);
System.out.println(method.getName() + "方法调用后");
return null;
}
}
通过Proxy类的newProxyInstance方法,动态生成代理对象
public static void main(String[] args) {
// TODO Auto-generated method stub
IStudentService studentService = new StudentService();
InvocationHandler studentInvocationHandler = new MyInvocationHandler(studentService);
IStudentService studentServiceProxy = (IStudentService) Proxy.newProxyInstance(studentInvocationHandler.getClass().getClassLoader(), studentService.getClass().getInterfaces(), studentInvocationHandler);
studentServiceProxy.insertStudent();
studentServiceProxy.deleteStudent();
}
运行结果
insertStudent方法调用前
insertStudent方法调用后
deleteStudent方法调用前
deleteStudent方法调用后
动态代理实现的方式看着会有点古怪,有时间的小伙伴看一下Proxy源码,就能搞懂了。
这里除了 jdk 自带的动态代理之外,我们也可以使用 CGLib的方式生成动态代理,跟jdk自带的动态代理实现上有很大的区别
Cglib动态代理
具体代码如下:
创建共同接口,以及真实对象
public interface BaseService {
void mainService();
}
public class Cicada implements BaseService {
@Override
public void mainService() {
System.out.println("主要的,你有调用,我监听到");
}
}
测试最后的结果
```c
public class PrayingMantis implements MethodInterceptor {
private Cicada cicada;// 代理对象
public Cicada getInstance(Cicada cicada) {
this.cicada = cicada;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.cicada.getClass());
enhancer.setCallback(this);
return (Cicada) enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object object = methodProxy.invokeSuper(o, objects);
secondaryMain();
return object;
}
private void secondaryMain() {
System.out.println("不是主要的");
}
public static void main(String[] args) {
PrayingMantis prayingMantis = new PrayingMantis();
Cicada instance = prayingMantis.getInstance(new Cicada());
instance.mainService();
}
从上面代码可以发现的一点是 Cglib 不再通过接口来实现,它是通过实现子类的方式来完成调用的。
总结
CGLIB 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 要多。所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用 JDK 方式要更为合适一些。同时由于 CGLib 由于是采用动态创建子类的方法,对于 final 修饰的方法无法进行代理。