文档章节

浅谈Java【代理设计模式】——看这篇文章就懂了

须臾之余
 须臾之余
发布于 05/18 22:45
字数 2325
阅读 1196
收藏 84

写在前面:设计模式源于生活,而又高于生活!

什么是代理模式

为其他对象提供一种代理以控制对这个对象的访问。

为什么使用代理模式

中介隔离:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口

开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息过滤消息把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

代理模式实现原理

代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色,Proxied)以及代理类角色(Proxy)

抽象主题角色(Subject):可以是接口,也可以是抽象类

委托类角色(proxied):真实主题角色,业务逻辑的具体执行者

代理类角色(Proxy):内部含有对真实对象RealSubject的引用,负责对真实主题角色的调用,并在真实主题角色处理前后做预处理和后处理

代理模式应用场景

SpringAop、日志收集、权限控制、过滤器、RPC远程调用

代理模式创建的方式

静态代理 和 动态代理

静态代理

静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。

所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。

一句话,自己手写代理类就是静态代理。

基于接口实现方式

//主题(Subject)
public interface OrderService {
    void order();
}
//实现接口
public class OrderServiceImpl implements OrderService {
    public void order() {
        System.out.println("用户下单操作..");
    }
}
//代理类
public class OrderServiceProxy implements OrderService {
    /**
     * 代理对象
     */
    private OrderService proxiedOrderService;

    public OrderServiceProxy( OrderService orderService) {
      this.proxiedOrderService=orderService;
    }

    public void order() {
        System.out.println("日志收集开始..");
        proxiedOrderService.order();
        System.out.println("日志收集结束..");
    }
}
//测试
public class ClientTest {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceProxy(new OrderServiceImpl());
        orderService.order();
    }
}

接口继承方式实现

//继承接口实现类
public class OrderServiceProxy extends OrderServiceImpl {

    public void order() {
        System.out.println("日志收集开始..");
        super.order();
        System.out.println("日志收集结束..");
    }
}
//测试
public class ClientTest {
    public static void main(String[] args) {
        OrderService orderService = new OrderServiceProxy();
        orderService.order();
    }
}

动态代理

动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成 。

JDK动态代理

JDK动态代理的一般步骤如下:

1.创建被代理的接口和类;

2.实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;

3.调用Proxy的静态方法,创建代理类并生成相应的代理对象;

//主题()Subject
public interface OrderService {
    void order();
}
//实现接口
public class OrderServiceImpl implements OrderService {
    public void order() {
        System.out.println("修改数据库订单操作..");
    }
}
//代理类
public class JdkInvocationHandler implements InvocationHandler {
    /**
     * 目标代理对象
     */
    public Object target;

    public JdkInvocationHandler(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(">>>日志收集开始>>>>");
        // 执行代理对象方法
        Object reuslt = method.invoke(target, args);
        System.out.println(">>>日志收集结束>>>>");
        return reuslt;
    }

    /**
     * 获取代理对象接口
     *
     * @param <T>
     * @return
     */
    public <T> T getProxy() {
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

}
JdkInvocationHandler jdkInvocationHandler = new JdkInvocationHandler(new OrderServiceImpl());
OrderService proxy = jdkInvocationHandler.getProxy();
proxy.order();

原理分析

1.	获取代理的生成的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

2.使用反编译工具该Proxy0.class

注意:继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。

CGLIB动态代理

Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。

CGLIB原理

运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。

Cglib优缺点

优点:JDK动态代理要求被代理的类必须实现接口,当需要代理的类没有实现接口时Cglib代理是一个很好的选择。另一个优点是Cglib动态代理比使用java反射的JDK动态代理要快

缺点:对于被代理类中的final方法,无法进行代理,因为子类中无法重写final函数

CGLIB代理实现

版权@须臾之余https://my.oschina.net/u/3995125

实现MethodInterceptor接口的intercept方法后,所有生成的代理方法都调用这个方法。

intercept方法的具体参数有

obj 目标类的实例

1.method 目标方法实例(通过反射获取的目标方法实例)
2.args 目标方法的参数
3.proxy 代理类的实例

该方法的返回值就是目标方法的返回值。

public class OrderServiceImpl {
    public void order() {
        System.out.println("用户下单操作..");
    }
}
public class CglibMethodInterceptor implements MethodInterceptor {
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("<<<<<日志收集开始...>>>>>>>");
        Object reuslt = proxy.invokeSuper(obj, args);
        System.out.println("<<<<<日志收集结束...>>>>>>>");
        return reuslt;
    }
}



System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
CglibMethodInterceptor cglibMethodInterceptor = new CglibMethodInterceptor();
Enhancer enhancer = new Enhancer();
// 设置代理类的付类
enhancer.setSuperclass(OrderServiceImpl.class);
// 设置回调对象
enhancer.setCallback(cglibMethodInterceptor);
// 创建代理对象
OrderServiceImpl orderServiceImpl = (OrderServiceImpl) enhancer.create();
orderServiceImpl.order();

Maven依赖

<dependencies>

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.12</version>
    </dependency>
</dependencies>

结果

>>>>cglib日志收集开始....
执行订单业务逻辑代码
>>>>cglib日志收集结束....

静态代理与动态代理区别

静态代理需要自己写代理类,而动态代理不需要写代理类。

JDK动态代理与CGLIB实现区别

JDK动态代理底层实现:

JDK的动态代理使用Java的反射技术生成动态代理类,只能代理实现了接口的类, 没有实现接口的类不能实现动态代理。

CGLIB动态代理底层实现:

运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法,在子类中采用方法拦截的技术拦截所有父类方法的调用,不需要被代理类对象实现接口,从而CGLIB动态代理效率比Jdk动态代理反射技术效率要高。

案例:使用AOP拦截Controller所有请求日志

@Aspect
@Component
@Slf4j
public class AopLogAspect {


    // 申明一个切点 里面是 execution表达式
    @Pointcut("execution(* com.xuyu.controller.*.*(..))")
    private void serviceAspect() {
    }
    // 请求method前打印内容
    @Before(value = "serviceAspect()")
    public void methodBefore(JoinPoint joinPoint) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();

        // 打印请求内容
        log.info("===============请求内容===============");
        log.info("请求地址:" + request.getRequestURL().toString());
        log.info("请求方式:" + request.getMethod());
        log.info("请求类方法:" + joinPoint.getSignature());
        log.info("请求类方法参数:" + Arrays.toString(joinPoint.getArgs()));
        log.info("===============请求内容===============");

    }

    // 在方法执行完结后打印返回内容
    @AfterReturning(returning = "o", pointcut = "serviceAspect()")
    public void methodAfterReturing(Object o) {
        log.info("--------------返回内容----------------");
        log.info("Response内容:" + o.toString());
        log.info("--------------返回内容----------------");

    }
}

Maven依赖信息

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
</parent>
<dependencies>

    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>3.2.12</version>
    </dependency>
    <!-- sprinboot web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.16.10</version>
    </dependency>
    <dependency>
        <groupId>commons-lang</groupId>
        <artifactId>commons-lang</artifactId>
        <version>2.6</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
</dependencies>

结果

 ===============请求内容===============
 : 请求地址:http://127.0.0.1:8080/getUser
 : 请求方式:GET
 : 请求类方法:String com.xuyu.service.controller.IndexController.getUser(String,Integer)
 : 请求类方法参数:[xuyu, 2]
 : ===============请求内容===============
 : >>>userName:{},xuyu
 : --------------返回内容----------------
 : Response内容:success_getUser
 : --------------返回内容----------------
 

版权@须臾之余https://my.oschina.net/u/3995125

本文参考:蚂蚁课堂

© 著作权归作者所有

须臾之余
粉丝 70
博文 18
码字总数 41781
作品 0
吉安
程序员
私信 提问
加载中

评论(4)

恒宝乐园
恒宝乐园
学习了
迪加
很有收获,谢谢
飞趣社区创始人
飞趣社区创始人
666666666666 学习了 果断收藏
R
RoyTao
学习了 谢谢分享
《成神之路-基础篇》JVM——JVM内存结构(已完结)

Java内存模型,Java内存管理,Java堆和栈,垃圾回收 本文是《成神之路系列文章》的第一篇,主要是关于JVM的一些介绍。 持续更新中 参考文章: Java虚拟机的内存组成以及堆内存介绍 Java堆和栈...

2018/05/05
0
0
Java面试:投行的15个多线程和并发面试题

本文由ImportNew -一杯哈希不加盐 翻译自dzone。欢迎加入翻译小组。转载请见文末要求。 多线程和并发问题已成为各种 Java 面试中必不可少的一部分。如果你准备参加投行的 Java 开发岗位面试,...

ImportNew
2018/08/23
0
0
AC自动机+trie树实现高效多模式匹配字典

前言 经常会遇到一类需求,在一段字符串中查找所有能匹配上的模式,比如查找一段文字匹配上字典中哪些短语。这时为了高效处理,就会考虑 AC 自动机,即 Aho-Corasick 自动机算法。它的核心思...

超人汪小建
2018/07/09
0
0
如果你想学习Java,那么就来看这篇文章

一、前言 我是从大二开始学习的Java,当时的目标是Java Web开发,当时并不想考研,所以当时的学习是以就业为主,现在我大三了,学习Java Web开发已经一年了,因为种种原因,决定要考研,所以...

Jivanmoon
2018/08/27
0
0
从Java到JVM到OS线程睡眠

前言 Java 中有时需要将线程进入睡眠状态,这时一般我们就会通过使线程进入睡眠状态,接下去就看看执行该语句在 JVM 中做了什么。 简单例子 以下是一个简单的例子,使主线程睡眠5秒钟。 JVM ...

超人汪小建
2018/08/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

聊聊dubbo的TimeoutFilter

序 本文主要研究一下dubbo的TimeoutFilter ListenableFilter dubbo-2.7.2/dubbo-rpc/dubbo-rpc-api/src/main/java/org/apache/dubbo/rpc/ListenableFilter.java public abstract class Liste......

go4it
22分钟前
5
0
方法与数组

方法 方法就是完成特定功能的代码块;在很多语言里面都有函数的定义,函数在Java中被称为方法 格式: 修饰符 返回值类型 方法名(参数类型 参数名1,参数类型 参数名2…) throw 异常{ 函数体;...

凹凸凸
44分钟前
2
0
死磕 java同步系列之StampedLock源码解析

问题 (1)StampedLock是什么? (2)StampedLock具有什么特性? (3)StampedLock是否支持可重入? (4)StampedLock与ReentrantReadWriteLock的对比? 简介 StampedLock是java8中新增的类,...

彤哥读源码
今天
4
1
性能优化工具(二)-Systrace

一、简介 Systrace是分析Android性能问题的神器,Google IO 2017上更是对其各种强推. 是分析卡顿掉帧问题核心工具,只要能提供卡顿现场,systrace就能很好定位问题,但是有一定上手难度,所以...

天王盖地虎626
今天
9
0
Linux 网络

MtrS
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部