《设计模式》设计模式的基本原则
《设计模式》单例模式
《设计模式》工厂模式
《设计模式》原型模式
《设计模式》建造者模式
《设计模式》适配器模式
《设计模式》桥接模式
《设计模式》装饰者模式
《设计模式》组合模式
《设计模式》外观模式
《设计模式》享元模式
《设计模式》代理模式
《设计模式》模板方法模式
《设计模式》命令模式
定义:
代理模式的角色组成:
代理模式类图如下所示:
案例背景:
每到节假日来临前后,火车站一定是人流量最大的场所之一。由于现在的网络购票途径非常成熟,因此大家可以在手机上简单操作几下就可以将票买到手了,非常便利。即使是在网络购票的方式出来之前,我记得在我小的时候就经常看见一些门店会贴着火车票的代售点的字样,在那个网络还不发达的年代,这也算是比较方便的购票方式了,总比跑到火车站现场购票方便很多。如果使用代理模式的思想分析这个生活中的场景,那么火车站就可以看作是目标对象,而代售点就是代理对象,我们通过代售点进行买票,火车站和代售点都有售票的功能,“我们”就是访问对象。
设计类图如下所示:
BookTicketsService
接口:
public interface BookTicketsService {
void book();
}
BookTicketsServiceImpl
类:
/**
* 目标对象
*/
public class BookTicketsServiceImpl implements BookTicketsService{
@Override
public void book() {
System.out.println("订票");
}
}
BookTicketsProxy
类:
public class BookTicketsProxy implements BookTicketsService{
private final BookTicketsService bookTicketsService;
public BookTicketsProxy(BookTicketsService bookTicketsService) {
this.bookTicketsService = bookTicketsService;
}
@Override
public void book() {
System.out.println("订票前do something...");
bookTicketsService.book();
System.out.println("订票后do something...");
}
}
Client
类:
/**
* 客户端类
*/
public class Client {
public static void main(String[] args) {
BookTicketsService target = new BookTicketsServiceImpl();
BookTicketsProxy bookTicketsProxy = new BookTicketsProxy(target);
bookTicketsProxy.book();
}
}
访问对象 Client
通过代理对象 BookTicketsProxy
进行订票,代理对象可以在订票前后扩展一额外的功能。
静态代理的总结:
JDK 动态代理是 Java 自带的动态代理技术,它可以在运行时动态地创建代理对象,并且只能代理接口。使用 JDK 动态代理时,需要定义一个接口,并使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口来动态地创建代理对象。
JDK 动态代理的实现步骤:
InvocationHandler
接口并重写 invoke()
方法,在 invoke()
方法中调用本地方法,并可以在此处加一些扩展操作。Proxy.newProxyInstance()
创建代理对象。BookTicketsService
接口:
public interface BookTicketsService {
void book();
}
BookTicketsServiceImpl
类:
public class BookTicketsServiceImpl implements BookTicketsService {
@Override
public void book() {
System.out.println("订票");
}
}
自定义 MyInvocationHandler
类:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("订票前do something...");
// 执行目标对象
Object result = method.invoke(target, args);
System.out.println("订票后do something...");
return result;
}
}
JdkProxyFactory
类:代理工厂,创建代理对象:
/**
* JdkProxy工厂,创建代理对象
*/
public class JdkProxyFactory {
public static Object getObjectProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 获取目标对象的加载器,加载代理类
target.getClass().getInterfaces(), // 目标对象实现的接口
new MyInvocationHandler(target) // 对应于代理对象自定义的InvocationHandler
);
}
}
Client
类:
/**
* 客户端类
*/
public class Client {
public static void main(String[] args) {
BookTicketsService bookTicketsService = (BookTicketsService) JdkProxyFactory.getObjectProxy(new BookTicketsServiceImpl());
bookTicketsService.book();
}
}
为了更好地观察动态代理类在内存中的创建过程,需要使用 Java 诊断工具 arthas 来打印出程序在运行过程中代理类的结构,步骤如下:
public class Client {
public static void main(String[] args) {
BookTicketsService bookTicketsService = (BookTicketsService) JdkProxyFactory.getObjectProxy(new BookTicketsServiceImpl());
bookTicketsService.book();
System.out.println(bookTicketsService .getClass());
while (true) {
}
}
}
java -jar arthas-boot.jar
jad com.sun.proxy.$Proxy0
,等待打印出来的程序运行过程中代理类的结构,如下所示:package com.sun.proxy;
import com.hzz.proxy.dynamicproxy.jdk.BookTicketsService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0
extends Proxy
implements BookTicketsService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler invocationHandler) {
super(invocationHandler);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.hzz.proxy.dynamicproxy.jdk.BookTicketsService").getMethod("book", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException noSuchMethodException) {
throw new NoSuchMethodError(noSuchMethodException.getMessage());
}
catch (ClassNotFoundException classNotFoundException) {
throw new NoClassDefFoundError(classNotFoundException.getMessage());
}
}
public final boolean equals(Object object) {
try {
return (Boolean)this.h.invoke(this, m1, new Object[]{object});
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)this.h.invoke(this, m2, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return (Integer)this.h.invoke(this, m0, null);
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void book() {
try {
this.h.invoke(this, m3, null);
return;
}
catch (Error | RuntimeException throwable) {
throw throwable;
}
catch (Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
}
$Proxy0
其实是实现了 BookTicketsService
接口的,只不过是通过 Proxy.newProxyInstance()
方法帮我们实现的,不用像在静态代理中那样显式实现,因此代理类和目标类都要实现相同的接口还是成立的。Proxy.newProxyInstance()
方法中创建的类中传递了 target
目标对象,被传递给了 $Proxy0
的父类 Proxy
.根据类的结构可知,动态代理的执行流程大概如下:
Client
中通过代理对象调用 book
方法。$Proxy0
中的 book()
方法。$Proxy0
中的 book()
方法又去调用 InvocationHandler
接口的子实现类对象的 invoke()
方法。invoke()
方法通过反射执行了目标类 BookTicketsServiceImpl
中的 book()
方法。JDK 动态代理总结:
private
和 static
方法,代理类和目标类需要实现相同的接口,因为 private
和 static
不能修饰接口。$Proxy0
类的父类为 Proxy
类,Java 中不支持多继承。从上面两节可以知道,无论是静态代理还是 JDK 动态代理,都需要目标类去实现一个接口,但是有时目标对象就只是一个单独的对象,并没有去实现任何的接口,这时如果还想使用代理模式的话,就可以使用 CGLIB 动态代理。
CGLIB(Code Generation Library) 动态代理:
final
修饰的方法,并在子类中采用方法拦截的技术拦截父类所有的方法调用。CGLIB 的 jar 坐标如下所示:
<dependency>
<groupId>cglibgroupId>
<artifactId>cglibartifactId>
<version>2.2.2version>
dependency>
JDK动态代理的实现步骤:
MethodInterceptor
接口,并重写intercept
方法,与JDK动态代理中的invoke()
方法类似,intercept()
方法用来拦截增强代理类的方法。Enhancer
类的create()
方法创建代理对象。BookTicketsService
类:目标对象
public class BookTicketsService {
public void book() {
System.out.println("订票");
}
}
自定义的 MyMethodInterceptor
类:
public class MyMethodInterceptor implements MethodInterceptor {
/**
* 重写 intercept 方法,在该方法中调用目标对象的方法
* @param o 代理对象
* @param method 目标对象的方法的 method 实例
* @param objects 方法entry
* @param methodProxy 代理对象中的方法 method 实例
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("订票前do something");
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("订票后do something");
return result;
}
}
CglibProxyFactory
类:
/**
* CglibProxy工厂,创建代理对象
*/
public class CglibProxyFactory {
public static Object getObjectProxy(Class<?> clazz) {
// 创建Enhancer对象,类似于JDK动态代理的Proxy类
Enhancer enhancer = new Enhancer();
// 设置父类的字节码对象
enhancer.setClassLoader(clazz.getClassLoader());
// 设置代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
return enhancer.create();
}
}
Client
类:
/**
* 客户端类
*/
public class Client {
public static void main(String[] args) {
BookTicketsService bookTicketsService = (BookTicketsService) CglibProxyFactory.getObjectProxy(BookTicketsService.class);
bookTicketsService.book();
}
}
CGLIB 代理的注意事项:
final
,因为被继承的父类如果为常量类那么无法被继承,会报错 java.lang.IllegalArgumentException
.private
,因为子类无法访问父类的私有方法;目标对象的方法不能为 final
,因为子类无法重写父类的不可变方法;目标对象的方法不能为 static
,因为静态方法属于类,是不属于对象的。为了了解 CGLIB 和 JDK 这两种动态代理方式性能,设计如下实验:分别使用 CGLIB 和 JDK 两种动态代理方式生成代理对象,运行次数从1000w次到1亿次,每次对比运行次数分别递增1000w次。其中实验的Java环境分别为 JDK1.6,JDK1.8 以及JDK11,cglib 版本为 2.2.2.
抽象主题接口 BookTicketsService
:
public interface BookTicketsService {
void book();
}
目标对象 BookTicketsServiceImpl
:
public class BookTicketsServiceImpl implements BookTicketsService {
@Override
public void book() {
int arr[] = {8, 5, 3, 2, 4};
// 冒泡排序
for (int i = 0; i < arr.length; i++) {
// 外层循环,遍历次数
for (int j = 0; j < arr.length - i - 1; j++) {
// 内层循环,升序(如果前一个值比后一个值大,则交换)
// 内层循环一次,获取一个最大值
if (arr[j] > arr[j + 1]) {
int temp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
// 选择排序
for (int i = 0; i < arr.length; i++) {
// 默认第一个是最小的。
int min = arr[i];
// 记录最小的下标
int index = i;
// 通过与后面的数据进行比较得出,最小值和下标
for (int j = i + 1; j < arr.length; j++) {
if (min > arr[j]) {
min = arr[j];
index = j;
}
}
// 然后将最小值与本次循环的,开始值交换
int temp = arr[i];
arr[i] = min;
arr[index] = temp;
}
// 插入排序
for (int i = 1; i < arr.length; i++) {
// 外层循环,从第二个开始比较
for (int j = i; j > 0; j--) {
// 内存循环,与前面排好序的数据比较,如果后面的数据小于前面的则交换
if (arr[j] < arr[j - 1]) {
int temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
} else {
// 如果不小于,说明插入完毕,退出内层循环
break;
}
}
}
}
}
实现CGLIB动态代理自定义的 MyMethodInterceptor
类:
public class MyMethodInterceptor implements MethodInterceptor {
/**
* @param o 代理对象
* @param method 被拦截的方法
* @param objects 方法entry
* @param methodProxy 调用目标对象方法的代理
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
Object result = methodProxy.invokeSuper(o, objects);
return result;
}
}
CGLIB 动态代理工厂 CglibProxyFactory 类:
/**
* CglibProxy工厂,创建代理对象
*/
public class CglibProxyFactory {
public static Object getObjectProxy(Class<?> clazz) {
// 创建动态代理增强对象
Enhancer enhancer = new Enhancer();
// 设置类加载器
enhancer.setClassLoader(clazz.getClassLoader());
// 设置代理类
enhancer.setSuperclass(clazz);
// 设置方法拦截器
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理类
return enhancer.create();
}
}
实现JDK动态代理自定义的 MyInvocationHandler
类:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 执行目标对象
Object result = method.invoke(target, args);
return result;
}
}
JDK 动态代理工厂 JdkProxyFactory 类:
/**
* JdkProxy工厂,创建代理对象
*/
public class JdkProxyFactory {
public static Object getObjectProxy(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), // 获取目标类的加载器,加载目标类
target.getClass().getInterfaces(), // 代理需要实现的接口
new MyInvocationHandler(target) // 对应于代理对象自定义的InvocationHandler
);
}
}
测试对比 ComparsionTest
类:
public class ComparisonTest {
public static void main(String[] args) {
BookTicketsService bookTicketsService = new BookTicketsServiceImpl();
BookTicketsService jdkProxy = (BookTicketsService) JdkProxyFactory.getObjectProxy(bookTicketsService);
BookTicketsService cglibProxy = (BookTicketsService) CglibProxyFactory.getObjectProxy(BookTicketsServiceImpl.class);
long[] runCounts = new long[]{10000000, 20000000, 30000000, 40000000, 50000000, 60000000, 70000000, 80000000, 90000000, 100000000};
Map<String, BookTicketsService> proxyMap = new HashMap<>(3);
proxyMap.put("jdkProxy", jdkProxy);
proxyMap.put("cglibProxy", cglibProxy);
runWithoutMonitor(proxyMap);
runWithMonitor(proxyMap, runCounts);
}
// 预热
private static void runWithoutMonitor(Map<String, BookTicketsService> proxyMap) {
for (int i = 0; i < 1; i++) {
for (String key : proxyMap.keySet()) {
for (int j = 0; j < 100000; j++) {
proxyMap.get(key).book();
}
}
}
}
private static void runWithMonitor(Map<String, BookTicketsService> proxyMap, long[] runCounts) {
for (int i = 0; i < runCounts.length; i++) {
System.out.println("------------------[jdk version = 1.8.0_301] [运行"+runCounts[i]+"次]---------------------");
for (String key : proxyMap.keySet()) {
long start = System.currentTimeMillis();
for (int j = 0; j < runCounts[i]; j++) {
proxyMap.get(key).book();
}
long end = System.currentTimeMillis();
System.out.println("["+ key + "] Elapsed Time:" + (end-start) + "ms");
}
}
}
}
IDE 中运行结果截图:
cglib 和 jdk 动态代理性能对比实验数据:
运行次数 | cglib 时间消耗(ms) | jdk 时间消耗 |
---|---|---|
10000000 | 359 | 365 |
20000000 | 610 | 624 |
30000000 | 907 | 929 |
40000000 | 1191 | 1226 |
50000000 | 1492 | 1589 |
60000000 | 1807 | 1853 |
70000000 | 2095 | 2159 |
80000000 | 2450 | 2515 |
90000000 | 2756 | 2841 |
100000000 | 3052 | 3155 |
cglib和jdk动态代理性能对比柱状图:
IDE 中运行结果截图:
cglib 和 jdk 动态代理性能对比实验数据:
运行次数 | cglib 时间消耗(ms) | jdk 时间消耗 |
---|---|---|
10000000 | 284 | 292 |
20000000 | 502 | 431 |
30000000 | 744 | 668 |
40000000 | 910 | 866 |
50000000 | 1158 | 1115 |
60000000 | 1379 | 1292 |
70000000 | 1543 | 1512 |
80000000 | 1811 | 1713 |
90000000 | 2028 | 1951 |
100000000 | 2292 | 2143 |
cglib 和 jdk 动态代理性能对比柱状图:
IDE 中运行结果截图:
cglib 和 jdk 动态代理性能对比实验数据:
运行次数 | cglib 时间消耗(ms) | jdk 时间消耗 |
---|---|---|
10000000 | 360 | 325 |
20000000 | 494 | 439 |
30000000 | 642 | 631 |
40000000 | 836 | 836 |
50000000 | 1054 | 1060 |
60000000 | 1267 | 1254 |
70000000 | 1487 | 1454 |
80000000 | 1685 | 1650 |
90000000 | 1931 | 1893 |
100000000 | 2087 | 2045 |
cglib和jdk动态代理性能对比柱状图:
静态代理和动态代理的区别:
InvocationHandler.invoke
中处理。 在接口方法数量比较多的时候,可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。JDK 代理和 CGLIB 代理的区别:
我们知道,Spring 中有一个重要的概念就是面向切面编程(AOP),它很好地与 OOP 相配合,可以帮助我们做一下日志处理、权限控制以及事务管理的操作。因此,Spring 中对 AOP 的实现机制也有相关说明:
大概意思就是:
但是,在 SpringBoot 中我们发现,对于 SpringBoot 2.x 以前的版本,默认使用的是 JDK 动态代理实现 AOP,具体可以见类 AopAutoConfiguration
:
SpringBoot 1.5 版本中 AOP 的代理机制:
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false",
matchIfMissing = true)
public static class JdkDynamicAutoProxyConfiguration {
}
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
matchIfMissing = false)
public static class CglibAutoProxyConfiguration {
}
}
在 SpringBoot 1.5 版本中:
SpringBoot 2.7 版本中 AOP 的代理机制:
@Configuration
@ConditionalOnClass({ EnableAspectJAutoProxy.class, Aspect.class, Advice.class })
@ConditionalOnProperty(prefix = "spring.aop", name = "auto", havingValue = "true", matchIfMissing = true)
public class AopAutoConfiguration {
@Configuration(
proxyBeanMethods = false
)
@EnableAspectJAutoProxy(
proxyTargetClass = false
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"proxy-target-class"},
havingValue = "false",
matchIfMissing = false
)
static class JdkDynamicAutoProxyConfiguration {
JdkDynamicAutoProxyConfiguration() {
}
}
@Configuration(
proxyBeanMethods = false
)
@EnableAspectJAutoProxy(
proxyTargetClass = true
)
@ConditionalOnProperty(
prefix = "spring.aop",
name = {"proxy-target-class"},
havingValue = "true",
matchIfMissing = true
)
static class CglibAutoProxyConfiguration {
CglibAutoProxyConfiguration() {
}
}
}
在 SpringBoot 2.7 版本中:
至于为什么要更改 springboot 中 AOP 默认代理方式,首先看一下之前 Spring 的开发者们在 github 上的讨论:
正如上图所示:Phil Webb 提到使用 @EnableTransactionManagement(proxyTargetClass = true)
,也就是说建议使用 CGLIB 动态代理方式,以防有人不使用接口时,可以避免带来一些令人讨厌的问题。这里的令人讨厌的问题就是 JDK 动态代理不能代理类,只能代理接口这一局限性导致的。因此,从 springboot 2.x 开始,AOP 代理的方式默认都为 CGLIB 动态代理而不是 JDK 动态代理。
当然,如果说你非要使用 JDK 动态代理的话,也可以通过配置文件的方式指定:spring.aop.proxy-target-class = false