前言:本篇博客依次介绍了静态代理、JDK动态代理和CGLIB动态代理,从静态代理开始一步一步理解这三种代理的实现方式。文中有一些代码的例子,希望可以耐心读完,都是些逻辑很简单的代码,但是如果不看代码,可能就不太好理解。
一、代理模式
简单理解代理模式:简单来说就是我们使用①代理对象来代替对真实对象(real object)的访问,这样就②可以在不修改原目标对象的前提下③提供额外的功能操作,扩展目标对象的功能。
上面用序号标出来的3点就是代理模式的关键:
①通过代理对象去访问真正要访问的对象。
举个例子:小明要跟总裁小红进行沟通,但是小明不能直接跟小红面对面讲话,而是需要通过小红的秘书来跟小红沟通;这个例子中,小红的秘书就是一个代理对象。
②后面两点是我们使用代理模式的目的,就是为了增强对象的方法(即提供额外操作),同时不改变原先写好的代码。
举个例子:现在写好了一个类myeg,这个类里面有一个sayHi()方法,我们要使sayHi()的功能更丰富,同时不对类myeg进行修改,那么我们就可以考虑使用动态代理。
那么问题来了,代理要怎么实现呢?在Java中,代理有两种实现方式,分别是静态代理和动态代理。接下来我们就从静态代理开始理解,一步一步来认识Java中的代理。
参考资料:https://javaguide.cn/java/basis/proxy.html
二、静态代理
静态代理的实现方法十分简单粗暴,我们直接从代码中来认识它:
①首先,我们先实现一个类及其接口,这个类就是我们接下来要增强的类,暂且把它叫做「目标类」。
接口StaticService:
public interface StaticService {
void sayHi();
}
接口的实现类(待会要增强它!)
public class StaticServiceImpl implements StaticService {
@Override
public void sayHi() {
System.out.println("Hi~");
}
}
②我们实现一个代理类,这个代理类也实现了上文中定义的接口,同时实例化了一个「目标类」对象(这是要干什么捏,别急,且看代码)。
public class StaticProxy implements StaticService {
private StaticService myProxy;
public StaticProxy(StaticService staticService){
myProxy = staticService;
}
@Override
public void sayHi() {
System.out.println("手动增强中biu - biu - biu - ");
myProxy.sayHi();
System.out.println("成功!");
}
}
看了代码,我们应该就清楚代理类是怎么增强「目标类」的了,其实它就是手动调用了「目标类」的方法,这样就保证代理类百分之百保留了「目标类」的东西;同时,我们可以在调用前后编写代码来实现增强的效果(如这里的两句输出语句)。
③实际使用。
public class TestProxy {
@Test
public void testStaticProxy(){
StaticService myServie = new StaticServiceImpl();
StaticProxy myProxy = new StaticProxy(myServie);
myProxy.sayHi();
}
}
总结一下:
三、动态代理之JDK动态代理
首先,我们先了解下为什么需要动态代理。【静态代理:你礼貌吗?】
因为静态代理有以下缺点:①静态代理我们要针对每一个目标类都单独创建一个代理类。②一旦目标类实现的接口多加另一个方法,那我们不仅要修改目标类,还要修改代理类。
总结:
静态代理类不够灵活,所以我们需要动态代理。动态代理主要有两种,分别是JDK动态代理和CGLIB动态代理,这一节我们先介绍JDK动态代理。
—分割线—
JDK动态代理中,我们需要先认识一个接口InvocationHandler,这个接口中有一个方法invoke(),待会我们需要去实现实现这个接口并实现这个invoke()方法。此外,还有一个类需要我们认识,这个类叫Proxy,它有一个方法叫newProxyInstance(),这个方法是用来帮我们生成代理对象的。
我们依旧从代码出发:
①首先,依旧是先实现「目标类」及其接口:
还是刚才那两个。【虽然它们名字带了Static,但现在是动态代理的例子了!】
接口:
public interface StaticService {
void sayHi();
}
接口的实现类(待会要增强它!)
public class StaticServiceImpl implements StaticService {
@Override
public void sayHi() {
System.out.println("Hi~");
}
}
②写一个类实现InvocationHandler接口及其invoke()方法。
之所以要实现这个接口和这个invoke()方法,是因为我们待会是通过这个invoke()方法来调用「目标类」中的方法的;而在调用的前后,我们同样可以写代码来达到增强方法的目的。
这个invoke()方法有3个参数:
- proxy:动态生成的代理类(动态生成代理类的代码在后面,这里先知道参数的意义即可)。
- method:与代理类对象调用的方法相对应。
-
args:调用method对应的方法中所需要的参数。
也就是说:我们在通过代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。
接下来我们看看实现InvocationHandler接口及其invoke()方法的代码:
public class jdkProxyHandler implements InvocationHandler {
private Object target;
public jdkProxyHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("JDK动态代理增强中biu - biu - biu - ");
method.invoke(target, args);
System.out.println("成功!");
return null;
}
}
注意在method.invoke()中,第一个参数是类中的成员变量target,如果错写成proxy,就会陷入到无限循环中。
③动态生成代理类对象。
这里需要用到前面提到的Proxy类及其newProxyInstance()方法。前面已经提到,这个方法是用来生成代理对象的,它有3个参数:
- loader:「目标类」的类加载器,用于加载代理对象。
- interfaces:「目标类」实现的接口。
- h:实现了InvoctionHandler接口的类的对象,也就是我们在上一步中实现的类的对象。
由于newProxyInstance()方法是一个静态方法,因此可以直接通过类来调用。
代码中的getProxy()需要我们传入一个「目标类」对象。
public class jdkProxyFactory {
public static Object getProxy(Object target){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new jdkProxyHandler(target)
);
}
}
④具体使用。
通过第③步中实现的jdkProxyFactory动态生成代理对象(由于是Object类型,所以要强转成实际要用的StaticService类型),然后调用代理对象的方法即可。在调用代理对象方法的时候,会自动去调用第②步实现的jdkProxyHandler类中的invoke()方法,这个jdkProxyHandler类实现了InvocationHandler接口。
public class TestProxy {
@Test
public void testJDKProxy(){
StaticService myProxy = (StaticService) jdkProxyFactory.getProxy(new StaticServiceImpl());
myProxy.sayHi();
}
}
总结一下:
四、CGLIB动态代理
JDK动态代理有一个缺点,那就是:只有一个类是有实现接口的类,这个类才能被增强。为解决这个问题,我们可以使用CGLIB动态代理。
同样的,在实现CGLIB动态代理之前,我们先认识一下需要用到的接口和类。首先是一个叫做MethodInterceptor的接口,我们需要实现这个接口及其intercept()方法,实现这个接口的目的跟实现JDK动态代理中的InvocationHandler接口类似。此外,还需要认识一个类Enhancer,这个类用来动态生成代理对象。
接下来,我们从代码中来理解CGLIB动态代理:
①首先,依旧是实现一个「目标类」,这个目标类还是之前那个,只不过我把它改成没有实现接口的了。
public class StaticServiceImpl{
public void sayHi() {
System.out.println("Hi~");
}
}
②写一个类实现MethodInterceptor接口及其intercept()方法。
这个接口及方法的作用类似于JDK动态代理中的InvocationHandler接口和invoke()方法。
这个intercept()方法有4个参数:
- obj:「目标类」对象。
- method:被拦截的方法(「目标类」中需要增强的方法)。
- args:method方法需要的参数。
- proxy:用于调用原始方法。
在调用方法的前后,我们依然可以通过编写代码来达到增强的目的,如这里的两句输出语句。
public class CglibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("CGLIB动态代理增强中biu - biu - biu - ");
methodProxy.invokeSuper(o, args);
System.out.println("成功!");
return null;
}
}
③动态生成代理类对象。
这里需要用的Enhancer类,我们先实例一个Enhancer类的对象,然后进行一些信息的设置,最后调用这个对象的create()方法来生成代理类。
public class CglibProxyFactory {
public static Object getProxy(Class<?> clazz){
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置目标类
enhancer.setSuperclass(clazz);
// 设置方法的拦截器
enhancer.setCallback(new CglibProxy());
// 创建代理类
return enhancer.create();
}
}
④实际使用。
依然是获取代理对象,然后调用代理对象的方法即可。
public class TestProxy {
@Test
public void testCglibProxy(){
StaticServiceImpl myProxy = (StaticServiceImpl) CglibProxyFactory.getProxy(StaticServiceImpl.class);
myProxy.sayHi();
}
}
总结一下:
五、对比:JDK动态代理和CGLIB动态代理
—-完结撒花—-