代理模式
1、概念
(1)什么是代理模式?
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。举个例子:要买某个东西时,我们不用自己费劲挑选,可以直接问朋友推荐买哪个东西,然后告诉我们直接买就行了。这时朋友就充当了代理的角色。
(2)为什么用代理模式?
**中介隔离作用:**在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
**开闭原则,增加功能:**代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。
(3)代理模式实现方式
按照代理创建的时期来进行分类的话, 可以分为两种:静态代理、动态代理。静态代理是由程序员创建或特定工具自动生成源代码,在对其编译。在程序员运行之前,代理类.class文件就已经被创建了。动态代理是在程序运行时通过反射机制动态创建的。
Spring的AOP底层就是用动态代理来实现的。可以对原有功能进行增强,包括前置通知通知、后置通知、异常通知、最终通知和环回通知。
使用时机:原有的对象需要额外的功能,可以使用动态代理。
2、基于接口的代理
(1)静态代理
第一步:创建服务类接口:
package com.example.proxy;
public interface ProxyTestInterface {
void buyBook();
}
第二步:被代理类实现服务接口
package com.example.proxy;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ProxyTestInterfaceIpml implements ProxyTestInterface{
@Override
public void buyBook() {
log.info("付100元买了一本代理模式学习书");
}
}
第三步:创建静态代理类
package com.example.proxy;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ProxyTestBuyBookProxy implements ProxyTestBuyBook {
private ProxyTestBuyBook proxyTestBuyBook;
public ProxyTestBuyBookProxy(ProxyTestBuyBookImpl proxyTestBuyBookImpl) {
this.proxyTestBuyBook = proxyTestBuyBookImpl;
}
@Override
public void buyBook() {
log.info("推荐买****书");
proxyTestBuyBook.buyBook();
log.info("代理收到10元推荐费");
}
}
第四步:执行测试
public static void main(String[] args) {
ProxyTestBuyBookImpl proxyTestBuyBook = new ProxyTestBuyBookImpl();
ProxyTestBuyBookProxy proxyTestBuyBookProxy = new ProxyTestBuyBookProxy(proxyTestBuyBook);
proxyTestBuyBookProxy.buyBook();
}
静态类总结:
优点:可以在符合开闭原则的情况下对目标对象进行功能扩展。
缺点:每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
(2)动态代理
在动态代理中我们不再需要再手动的创建代理类,我们只需要编写一个动态处理器就可以了。代理对象的生成,是利用JDKAPI,动态地在内存中构建代理对象(需要指定创建代理对象以及代理对象实现的接口),并且会默认实现接口中的全部方法。
第一步: 编写动态处理器
package com.example.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DynamicProxyHandler implements InvocationHandler {
private ProxyTestBuyBook proxyTestBuyBook;
public DynamicProxyHandler(ProxyTestBuyBook proxyTestBuyBook) {
this.proxyTestBuyBook = proxyTestBuyBook;
}
// 买书需要付款及确认付款到账
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("buyBook")) {
// 推荐书籍
recommend();
// 调用被代理对象的方法买书
method.invoke(proxyTestBuyBook, args);
// 代理方确认收款
confirm();
}
return null;
}
private void confirm() {
log.info("代理收到10元推荐费");
}
private void recommend() {
log.info("推荐买****书");
}
}
第二步:执行测试
package com.example.proxy;
import java.lang.reflect.Proxy;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class DynamicProxyTest {
public static void main(String[] args) {
ProxyTestBuyBook proxyTestBuyBook = new ProxyTestBuyBookImpl();
ProxyTestBuyBook dynamicProxyTest = (ProxyTestBuyBook) Proxy.newProxyInstance(
ProxyTestBuyBookImpl.class.getClassLoader(),
ProxyTestBuyBookImpl.class.getInterfaces(), new DynamicProxyHandler(proxyTestBuyBook));
dynamicProxyTest.buyBook();
}
}
Proxy.newProxyInstance接受三个参数:
ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的。如ProxyTestBuyBookImpl.class.getClassLoader()
Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型。如ProxyTestBuyBookImpl.class.getInterfaces()或new Class[{ProxyTestBuyBookImpl.class}
InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法。会默认实现接口中的全部方法。
动态类总结:
优点:相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。
缺点:仅支持interface代理。
无法支持class的动态代理:原因:动态生成的代理类有一个共同的父类叫Proxy。Java的继承机制注定了这些动态代理类们无法实现对class的动态代理,原因是多继承在Java中本质上就行不通。
3、CGLIB代理(基于class的代理)
使用场景:
动态代理没有接口的类时使用CGLIB,被代理的类必须不是final类。
CGLIB原理:CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。JDK动态代理与CGLib动态代理均是实现Spring AOP的基础。
第一步:创建CGLIB代理类
package com.example.proxy.cglib;
import java.lang.reflect.Method;
import com.example.proxy.ProxyTestBuyBookImpl;
import lombok.extern.slf4j.Slf4j;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
@Slf4j
public class CglibProxyTest implements MethodInterceptor {
private Object target;//被代理对象
//动态生成一个新的类,使用父类的无参构造方法创建一个指定了特定回调的代理实例
public Object getInstance(Object target) {
this.target = target;
Enhancer enhancer = new Enhancer();//增强器,动态代码生成器
enhancer.setSuperclass(this.target.getClass());//设置生成类的父类类型
enhancer.setCallback(this);//回调方法
return enhancer.create();//动态生成字节码并返回代理对象
}
// 拦截方法
@Override
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if (method.getName().equals("buyBook")) {
// 推荐书籍
recommend();
// 调用被代理对象的方法买书
Object invoke = methodProxy.invoke(target, args);//methodProxy为第四个参数,对象应为被代理类对象
// 代理方确认收款
confirm();
return invoke;
}
return null;
}
private void confirm() {
log.info("代理收到10元推荐费");
}
private void recommend() {
log.info("推荐买****书");
}
}
第二步:执行测试
public static void main(String[] args) {
ProxyTestBuyBookImpl proxyTestBuyBook = new ProxyTestBuyBookImpl();
CglibProxyTest cglibProxyTest = new CglibProxyTest();
ProxyTestBuyBookImpl instance = (ProxyTestBuyBookImpl) cglibProxyTest.getInstance(proxyTestBuyBook);
instance.buyBook();
}
Spring 中Cglib的使用
package com.example.proxy.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.example.proxy.ProxyTestBuyBookImpl;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SpringCgLibProxyTest {
public static Object cglibProxyObject(Object object) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects,
MethodProxy methodProxy) throws Throwable {
// 拦截器 - 前置处理
confirm();
Object result = method.invoke(object, objects);
// 拦截器 - 后置处理
recommend();
return result;
}
private void confirm() {
log.info("代理收到10元推荐费");
}
private void recommend() {
log.info("推荐买****书");
}
});
return enhancer.create();
}
public static void main(String[] args) {
ProxyTestBuyBookImpl proxyTestBuyBook = new ProxyTestBuyBookImpl();
CglibProxyTest cglibProxyTest = new CglibProxyTest();
ProxyTestBuyBookImpl instance = (ProxyTestBuyBookImpl) cglibProxyTest
.getInstance(proxyTestBuyBook);
instance.buyBook();
}
}
注:动态代理总体性能:JDK > CGLIB,Spring默认的动态代理方式为JDK。
参考博客:
https://www.cnblogs.com/daniels/p/8242592.html
https://www.cnblogs.com/wyq1995/p/10945034.html