代理模式

代理模式

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值