结构型模式关注如何将现有类或对象组织一起形成更加强大的结构。
代理模式(Proxy Pattern):给某个对象提供一个代理活占位符,并由代理对象来控制对原对象的访问。
我们以房屋租借问题中的房东、代理商为例:
抽象主题角色: IRent 租借接口:定义租借相关行为规范
/* IRent 租借接口:定义租借相关行为规范 */ public interface IRent { void rent(); //租借 }
真实主题角色: HouseKeeping:房东类
/* HouseKeeping:房东类 */ public class HouseKeeping implements IRent { /** * 核心业务方法:租借房屋 */ @Override public void rent() { System.out.println("我有一套房,租金1500每月"); } }
代理主题角色: 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(); }
测试结果:
从 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(); }
测试结果:
CGLib (Code Generation Library) ,一个强大的、高性能、高质量的 Code 生成类库。
它可以在运行期扩展 Java 类与实现 Java 接口。Hibernate 用它来实现 PO 字节码的动态生成。CGLib 比 Java 的 java.lang.reflect.Proxy 类更强的在于它不仅可以接管接口类的方法,还可以接管普通类的方法。CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程。
JDK动态代理、CGLIB代理 区别:
两者速度对比:
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(Aspect-OrientedProgramming,面向切面编程),AOP包括切面(aspect)、通知(advice)、连接点(joinpoint),实现方式就是通过对目标对象的代理在连接点前后加入通知,完成统一的切面操作。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP的应用技术:
Spring 动态代理机制:
Spring 默认提供了两种方式来生成代理对象:JDK Proxy + CGLib,具体使用哪种根据情况而定,默认策略如下:
Spring 提供了配置参数来强制选择使用 CGLIB 技术,如下:
<aop:config proxy-target-class="true" />
CGLIB使用生成代理子类实现代理,proxy-target-class
表示属性值决定基于接口 / 类的代理被创建,proxy-target-class="true"
表示 强制使用 CGLIB 技术来实现AOP,若填入 <aop:config />
配置缺省,则依据 Spring 默认策略 选择代理。
相关术语:
在 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!!!"); } }
访问路径:
访问成功:
访问后结果: