【Java 设计模式 · 结构型 & AOP】代理模式(Proxy Pattern)& Spring AOP 应用
2021/9/29 20:12:31
本文主要是介绍【Java 设计模式 · 结构型 & AOP】代理模式(Proxy Pattern)& Spring AOP 应用,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
代理模式 Proxy Pattern & Spring AOP 应用
- 一、概述
- 二、结构
- 三、实现
- 1. 静态代理
- 2. JDK 动态代理
- 3. CGLib代理
- 四、特点
- ☯ 优点
- ☯ 缺点
- 五、动态代理应用:AOP
- 六、Spring AOP
- Spring Boot 使用 AOP
结构型模式关注如何将现有类或对象组织一起形成更加强大的结构。
一、概述
代理模式(Proxy Pattern):给某个对象提供一个代理活占位符,并由代理对象来控制对原对象的访问。
二、结构
- Subject(抽象主题角色):
声明了真实主题和代理的主题的共同接口,这样一来,在任何使用真实主题的地方都可以使用代理主题,客户端通常需要针对抽象主题角色进行编程。 - Proxy(代理主题角色):
包含了对真实主题对象的引用,从而可以操作真实对象,在代理主题中提供了与真实主题相同的接口,对真实对象的使用加以控制并约束,可以在执行主题操作前后执行其他操作。 - RealSubject(真实主题角色):
定义了代理角色所代表的真实对象,在真实主题角色中实现了真正的业务操作,客户端可以通过代理主题角色调用真实角色中定义的操作
三、实现
我们以房屋租借问题中的房东、代理商为例:
抽象主题角色: IRent 租借接口:定义租借相关行为规范
/* IRent 租借接口:定义租借相关行为规范 */ public interface IRent { void rent(); //租借 }
真实主题角色: HouseKeeping:房东类
/* HouseKeeping:房东类 */ public class HouseKeeping implements IRent { /** * 核心业务方法:租借房屋 */ @Override public void rent() { System.out.println("我有一套房,租金1500每月"); } }
1. 静态代理
代理主题角色: HouseAgency 租借代理商类
/* HouseAgency 租借代理商类 */ public class HouseAgency implements IRent { //真实主题对象 private IRent rent; /* 唯一构造器:初始化 */ public HouseAgency(IRent rent) { this.rent = rent; } /** * 核心业务方法:出租房屋 */ @Override public void rent() { before(); Object result = method.invoke(this.real, args); after(); } /** * 调用业务方法前进行处理的方法 */ public void before() { System.out.println("提前收押金"); } /** * 调用业务方法后进行处理的方法 */ public void after() { System.out.println("入住收物业费、水电费..."); } }
测试代码:
使用 JUnit 单元测试:
@Test public void test() { /* 创建代理对象 */ IRent rent = new HouseAgency(new HouseKeeping()); /* 通过代理对象 */ rent.rent(); }
测试结果:
2. JDK 动态代理
从 JDK 1.3 开始,Java语言提供了对动态代理的支持,需要用到 java.lang.reflect
包下的一些类:
Proxy 类:
Proxy
类提供了用于创建动态代理类和实例对象的方法,它是创建动态代理类的父类,它最常用的方法如下:
/* 用于返回一个Class类型的代理类,并在参数中提供类加载器,并指定代理的接口数组 */ public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) /* 用于返回一个动态创建的代理类实例: 第一个参数 loader 表示代理类的类加载器 第二个参数 interfaces 表示代理类所实现的接口列表 第三个参数 h 表示所指派的调用处理程序类 */ public static Object newProxyInstance(ClassLoader loader, Class<?>... interfaces, InvocationHandler h)
InvocationHandler 接口:
代理处理程序的实现接口,该接口作为代理实例的调用处理者的公共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(InvocationHandler
接口的子类)
其核心方法如下:
/* 该方法用于代理对代理类实例的方法调用,代理实现业务方法被调用时,该方法自动被调用 第一个参数 proxy 表示代理类的实例 第二个参数 method 表示需要代理的方法 第三个参数 args 表示代理方法的参数 */ public Object invork(Object proxy, Method method, Object[] args)
实现代码(代理主题角色):
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /* 代理主题角色:实现 InvocationHandler 接口 */ public class HouseProxy implements InvocationHandler { //真实主题角色 private Object real; /* 唯一构造器:初始化 */ public HouseProxy(Object real) { this.real = real; } /** * 产生代理对象 */ public Object createProxy() { return Proxy.newProxyInstance(this.real.getClass().getClassLoader(), this.real.getClass().getInterfaces(), this); } /** * 代理对代理类实例的方法调用,代理实现业务方法被调用时,该方法自动被调用 * @param proxy 表示代理类的实例 * @param method 表示需要代理的方法 * @param args 表示代理方法的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { before(); Object result = method.invoke(this.real, args); after(); return result; } /** * 调用业务方法前进行处理的方法 */ public void before() { System.out.println("提前收押金"); } /** * 调用业务方法后进行处理的方法 */ public void after() { System.out.println("入住收物业费、水电费..."); } }
测试代码:
使用 JUnit 单元测试:
@Test public void test(){ /* 产生代理对象 */ IRent rent = (IRent) new HouseProxy(new HouseKeeping()).createProxy(); /* 通过代理对象,调用业务方法 */ rent.rent(); }
测试结果:
3. CGLib代理
CGLib (Code Generation Library) ,一个强大的、高性能、高质量的 Code 生成类库。
它可以在运行期扩展 Java 类与实现 Java 接口。Hibernate 用它来实现 PO 字节码的动态生成。CGLib 比 Java 的 java.lang.reflect.Proxy 类更强的在于它不仅可以接管接口类的方法,还可以接管普通类的方法。CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程。
JDK动态代理、CGLIB代理 区别:
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。
- CGLIB针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。
两者速度对比:
- JDK动态代理是面向接口,在创建代理实现类时速度比CGLib快
- CGLib动态代理是通过字节码底层继承要代理类来实现(代理类不能被final关键字所修饰),运行速度比JDK动态代理更快
Maven 引入依赖:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency>
CGLib 的核心接口是位于 net.sf.cglib.proxy
包下的 MethodInterceptor
接口,它继承自 Callback
接口:
核心方法:
/** 代理对代理类实例的方法调用,代理实现业务方法被调用时,该方法自动被调用 * @param obj 表示本类 * @param method 表示需要代理的方法 * @param args 表示代理方法的参数 * @param proxy 代表对父类进行代理 */ public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
实现代码:
import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /* 代理主题角色:实现 MethodInterceptor 接口 */ public class CGLibProxy implements MethodInterceptor { //真实主题角色 private Object real; /* 唯一构造器:初始化 */ public CGLibProxy(Object real) { this.real = real; } /** * 创建代理对象 * @return */ public Object createProxy() { Enhancer e = new Enhancer(); e.setSuperclass(this.real.getClass()); e.setCallback(this); return e.create(); } /** 代理对代理类实例的方法调用,代理实现业务方法被调用时,该方法自动被调用 * @param obj 表示本类 * @param method 表示需要代理的方法 * @param args 表示代理方法的参数 * @param proxy 代表对父类进行代理 */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { before(); Object result = proxy.invokeSuper(obj,args); after(); return result; } /** * 调用业务方法前进行处理的方法 */ public void before() { System.out.println("提前收押金"); } /** * 调用业务方法后进行处理的方法 */ public void after() { System.out.println("入住收物业费、水电费..."); } }
测试代码:
@Test public void test(){ /* 产生代理对象 */ HouseKeeping h = (HouseKeeping) new CGLibProxy(new HouseKeeping()).createProxy(); /* 通过代理对象,调用业务方法 */ h.rent(); }
测试结果:
四、特点
☯ 优点
- 能够协调调用者与被调用者,一定程度上降低了系统的耦合度
- 可针对抽象主题角色进行编程,更换代理类灵活、便于扩展,符合开闭原则
☯ 缺点
- 由于代理对象的出现,可能使得处理请求变慢
五、动态代理应用:AOP
AOP(Aspect-OrientedProgramming,面向切面编程),AOP包括切面(aspect)、通知(advice)、连接点(joinpoint),实现方式就是通过对目标对象的代理在连接点前后加入通知,完成统一的切面操作。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP的应用技术:
- 采用动态代理技术,截获消息进行装饰,取代原有对象(真实主题角色)行为的执行
- 采用静态织入方式,在编译期间织入相关代码
六、Spring AOP
Spring 动态代理机制:
Spring 默认提供了两种方式来生成代理对象:JDK Proxy + CGLib,具体使用哪种根据情况而定,默认策略如下:
- 目标类是接口,使用 JDK动态代理
- 目标对象没有实现接口,采用 CGLIB代理
Spring 提供了配置参数来强制选择使用 CGLIB 技术,如下:
<aop:config proxy-target-class="true" />
CGLIB使用生成代理子类实现代理,proxy-target-class
表示属性值决定基于接口 / 类的代理被创建,proxy-target-class="true"
表示 强制使用 CGLIB 技术来实现AOP,若填入 <aop:config />
配置缺省,则依据 Spring 默认策略 选择代理。
相关术语:
- 通知(Advice):包含了需要用于多个应用对象的横切行为
- 连接点(Join Point):程序执行过程中能够应用通知的所有点
- 切点(PointCut):定义何时进行切入,哪些连接点会得到通知
- 切面(Aspect):通知、切点相结合
- 引入(Introduction):允许向现有类中添加新的属性、方法
- 织入(Weaving):将切面应用到目标对象,并创建新代理对象的过程,分为编译期织入、类加载期织入、运行期织入
Spring Boot 使用 AOP
在 SpringBoot 中使用 AOP 之前,首先要引入相关依赖:
使用 Maven 引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> <version>2.5.4</version> </dependency>
我们写一个小的demo,对AOP进行测试:
核心代码结构一览:
编写 Controller 并对其进行切面:
IDEA 对 AOP 支持度较高,被切面的方法,会在以特殊图标进行标记:
编写 Controller 代码:
package com.ljw.aop.controller; //包路径 //省略imports... @RestController @RequestMapping("/api/aop") public class AopController { @GetMapping("/hello") public String hello(){ System.out.println("hello"); return "hello"; } }
定义切点:
切点是通过@Pointcut注解和切点表达式定义的,@Pointcut注解可以在一个切面内定义可重用的切点。
Spring 切面力度最小可达到方法级别,使用 execution 表达式需致命方法返回类型、类名、方法名、参数名等相关信息,这种使用方式最为广泛:
定义通知:
通知的五种类型(包括 IDEA 支持图标):
-
前置(@Before):目标方法调用前执行通知
-
后置(@After):目标方法调用后执行通知
-
环绕(@Around):目标方法调用前后执行通知
-
返回(@AfterReturning):目标方法成功执行之后执行通知
-
异常(@AfterThrowing):目标方法抛出异常之后执行通知
编写 Advice 代码:
package com.ljw.aop.advice; //包路径 //省略imports... @Aspect @Component public class AopAdvice { /** * 定义切点 */ @Pointcut("execution (* com.ljw.aop.controller.*.*(..))") public void point() { } /** * 前置通知 */ @Before("point()") public void before() { System.out.println("before"); } /** * 后置通知 */ @After("point()") public void after() { System.out.println("after"); } /** * 环绕通知 * @param proceedingJoinPoint 切入点 */ @Around("point()") public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) { System.out.println("around-before"); //执行前 try { proceedingJoinPoint.proceed(); //执行时 } catch (Throwable t) { t.printStackTrace(); } System.out.println("around-after"); //执行后 } /** * 方法执行完毕通知 */ @AfterReturning("point()") public void returnAdvice() { System.out.println("finish"); } /** * 方法抛出异常 */ @AfterThrowing("point()") public void throwAdvice() { System.out.println("Exception!!!"); } }
访问路径:
访问成功:
访问后结果:
这篇关于【Java 设计模式 · 结构型 & AOP】代理模式(Proxy Pattern)& Spring AOP 应用的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-01基于Python+Vue开发的医院门诊预约挂号系统
- 2024-10-01基于Python+Vue开发的旅游景区管理系统
- 2024-10-01RestfulAPI入门指南:打造简单易懂的API接口
- 2024-10-01初学者指南:了解和使用Server Action
- 2024-10-01Server Component入门指南:搭建与配置详解
- 2024-10-01React 中使用 useRequest 实现数据请求
- 2024-10-01使用 golang 将ETH账户的资产平均分散到其他账户
- 2024-10-01JWT用户校验课程:从入门到实践
- 2024-10-01Server Component课程入门指南
- 2024-09-30Dnd-Kit学习:新手快速入门指南