代理模式
使用一个代理对象代替真实对象,以此在不修改原对象的前提下扩展该对象的功能。
例如我们需要在每个方法执行的前后都加上输出语句表明该方法执行与结束,这时就能使用代理模式扩展原方法的功能
具体实现:
静态代理
创建代理对象实现与目标对象实现的相同接口,创建目标对象,调用目标对象方法,在此基础上扩展
之所以实现相同接口,是为了尽可能保证代理对象的内部结构和目标对象一致,这样我们对代理对象的操作最终都可以转移到目标对象身上,代理对象只需专注于增强代码的编写。
//接口
public interface Subject {
void getResult();
}
//目标对象
public class RealSubject implements Subject{
public void getResult(){
System.out.println("方法执行了");
}
}
//代理对象
public class StaticProxy implements Subject{
public RealSubject realSubject;
public StaticProxy(RealSubject realSubject) {
this.realSubject = realSubject;
}//实现接口方法
public void getResult(){
System.out.println("方法执行前————————");
realSubject.getResult();
System.out.println("方法执行后————————");
}
}
//测试类
public class Test {
public static void main(String[] args) throws Exception {
Subject subject = new StaticProxy(new RealSubject());
subject.getResult();
}
}
/*
输出:
方法执行后————————
方法执行了
方法执行前————————
*/
这里每次创建代理对象都需要手动创建一个代理类,而且对目标对象的每个方法都需要进行重写,较为麻烦,这时我们就能考虑使用动态代理
动态代理
使用JDK提供的方法,动态生成代理对象
具体实现:
我们需要用到java.lang.reflect.Proxy
下的方法getConstructor(ClassLoader loader,Class<?>... interfaces)
在该方法中传入与目标对象相同的类加载器和目标对象实现的接口类,该方法就会为我们返回一个Class代理对象
接下来使用反射机制获取代理对象的有参构造,在构造器中传入InvocationHandler
接口类,此时我们使用该有参构造获取实例并实现InvocationHandler
接口,InvocationHandler
接口有一个invoke()
方法,实现invoke()
方法后一旦调用目标对象的某个方法,invoke()就会执行。invoke()
中有我们需要的目标对象的方法、参数等信息,我们要扩展方法就可以在这里完成
proxy:是调用该方法的代理实例。
method:是在代理实例上调用的接口方法对应的Method实例。
args:一个Object数组,是在代理实例上的方法调用中传递的参数值。如果接口方法为无参,则该值为null。
public class Test {
public static void main(String[] args) throws Exception {
RealSubject realSubject = new RealSubject();
Subject subject = (Subject) getProxy(realSubject);
subject.getResult();
}
public static Object getProxy(final Object target) throws Exception {
Class proxyClass = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
Constructor constructor = proxyClass.getConstructor(InvocationHandler.class);
Object proxy = constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行后——————————");
Object result = method.invoke(target, args);
//注:此invoke()是Method对象的方法,执行的是代理实例的对应方法
System.out.println("方法执行前——————————");
return result;
}
});
return proxy;
}
}
来看看执行流程
我们注意到JDK动态代理仍要求我们目标对象必须有实现的接口才能使用,因此我们还可以考虑使用CGLIB动态代理
CGLIB动态代理
cglib是个强大的开源项目,spring的AOP中就用了JDK的动态代理和cglib动态代理,它的底层使用了 ASM 操作字节码的框架来实现生成代理类
我们要使用cglib首先要导入它的依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
接下来创建代理对象,这里需要实现接口,对目标对象方法的扩展也在这里进行
public class CglibProxy implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("方法执行前——————————");
System.out.println(o.getClass().toString());
System.out.println(method.getName());
System.out.println(objects.length);
System.out.println(methodProxy.getSuperName());
//关键是下面这句,可以看作JDK动态代理中的method.invoke()
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("方法执行后——————————");
return result;
}
}
这里给出四个参数的含义,具体细节大家可通过测试来得知,例如用method.getName()
等方法验证
Object o
:生成的代理对象
Method method:
目标对象调用的方法
Object[] objects
:目标对象调用方法的参数
MethodProxy methodProxy
:目标对象方法的代理
接下来看看如何使用这个代理类来生成我们需要的代理对象
public class Test {
public static void main(String[] args) throws Exception {
//构建Enhancer对象
Enhancer enhancer = new Enhancer();
//设置Enhancer的父类对象为目标对象
enhancer.setSuperclass(RealSubject.class);
//设置Enhancer回调对象为代理对象
enhancer.setCallback(new CglibProxy());
//create()函数创建代理对象
RealSubject realSubject = (RealSubject)enhancer.create();
realSubject.getResult();
}
}
/*
输出:
方法执行前——————————
class com.college.test.Proxy.RealSubject$$EnhancerByCGLIB$$79180c56
getResult
0
CGLIB$getResult$1
方法执行了
方法执行后——————————
*/
可以看到,这里我们没有用到接口就实现了RealSubject代理对象的创建
最后,来看一下JDK动态代理和CGLIB动态代理的区别,这也是面试的常考点
-
JDK动态代理使用反射机制来实现;CGLIB动态代理使用ASM框架操作字节码来实现
-
JDK动态代理要求实现目标对象的接口;CGLIB动态代理可以直接生成普通类的代理对象(生成子类)
-
效率上的区别
创建对象上,JDK动态代理效率高于CGLIB动态代理
执行效率上,CGLIB动态代理效率高于JDK动态代理因此,对于单例的代理对象则适合用CGLIB动态代理,对于需要频繁创建的对象则用JDK动态代理