一、代理模式
在 Java 程序中的代理模式的作用:
当一个对象需要受到保护的时候,可以考虑使用代理对象去完成某个行为
需要给某个对象的功能进行功能增强的时候,可以考虑找一个代理进行增强
A 对象无法和 B 对象直接交互时,也可以使用代理模式来解决
代理模式中的三大角色:
目标对象(演员)
代理对象(替身演员)
目标对象和代理对象的公共接口(演员与替身演员相同的行为,可以让观众不知道是替身演员)
如果使用代理模式,对于客户端程序来说,客户端是无法察觉的,客户端在使用代理对象的时候就像在使用目标对象
代理模式在代码实现上,包括两种形式:
静态代理
动态代理
二、静态代理
java
package org.qiu.proxy.service;
/**
* 订单业务接口
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-16-13:09
* @since 1.0
*/
public interface OrderService {
/**
* 生成订单
*/
void generate();
/**
* 修改订单信息
*/
void modify();
/**
* 查看订单详情
*/
void detail();
}
java
package org.qiu.proxy.service;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-16-13:10
* @since 1.0
*/
public class OrderServiceImpl implements OrderService{
@Override
public void generate() {
// 模拟网络延迟
try {
Thread.sleep(1024);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成!");
}
@Override
public void modify() {
// 模拟网络延迟
try {
Thread.sleep(512);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改!");
}
@Override
public void detail() {
// 模拟网络延迟
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单详情......");
}
}
java
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
orderService.generate();
orderService.detail();
orderService.modify();
}
}
运行结果:
项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。如果是你,你该怎么做呢?
第一种方案
直接修改Java源代码,在每个业务方法中添加统计逻辑,如下:
java
package org.qiu.proxy.service;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-16-13:10
* @since 1.0
*/
public class OrderServiceImpl implements OrderService{
@Override
public void generate() {
long begin = System.currentTimeMillis();
// 模拟网络延迟
try {
Thread.sleep(1024);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成!");
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
}
@Override
public void modify() {
long begin = System.currentTimeMillis();
// 模拟网络延迟
try {
Thread.sleep(512);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改!");
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
}
@Override
public void detail() {
long begin = System.currentTimeMillis();
// 模拟网络延迟
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单详情......");
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
}
}
运行效果:
需求可以满足,但显然是违背了OCP开闭原则,这种方案不可取
第二种方案
编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:
java
package org.qiu.proxy.service;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-16-13:24
* @since 1.0
*/
public class OrderServiceImplSub extends OrderServiceImpl{
@Override
public void generate() {
long begin = System.currentTimeMillis();
super.generate();
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
}
@Override
public void modify() {
long begin = System.currentTimeMillis();
super.modify();
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
}
@Override
public void detail() {
long begin = System.currentTimeMillis();
super.detail();
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
}
}
java
public class Test {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImplSub();
orderService.generate();
orderService.detail();
orderService.modify();
}
}
运行效果:
这种方式可以解决,但是存在两个问题:
-
第一个问题:假设系统中有100个这样的业务类,需要提供100个子类,并且之前写好的创建Service对象的代码,都要修改为创建子类对象
-
第二个问题:由于采用了继承的方式,导致代码之间的耦合度较高。
这种方案也不可取
第三种方案
使用代理模式(这里采用静态代理)
可以为 OrderService 接口提供一个代理类
java
package org.qiu.proxy.service;
/**
* 代理对象
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-16-13:32
* @since 1.0
*/
public class OrderServiceProxy implements OrderService {
// 目标对象
// 这里要使用"公共接口"类型,因为公共接口耦合度低
private OrderService orderService;
// 通过构造方法将目标对象传递给代理对象
public OrderServiceProxy(OrderService orderService){
this.orderService = orderService;
}
@Override
public void generate() {
long begin = System.currentTimeMillis();
// 执行目标对象的目标方法
orderService.generate();
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
}
@Override
public void modify() {
long begin = System.currentTimeMillis();
orderService.modify();
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
}
@Override
public void detail() {
long begin = System.currentTimeMillis();
orderService.detail();
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
}
}
这种方式的优点:
符合OCP开闭原则,同时采用的是关联关系,所以程序的耦合度较低。所以这种方案是被推荐的
java
package org.qiu.proxy.client;
import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.OrderServiceImplSub;
import org.qiu.proxy.service.OrderServiceProxy;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.client
* @date 2022-11-16-13:13
* @since 1.0
*/
public class Test {
public static void main(String[] args) {
// 创建目标对象
OrderService target = new OrderServiceImpl();
// 创建代理对象
OrderService proxy = new OrderServiceProxy(target);
// 调用代理对象的代理方法
proxy.generate();
proxy.modify();
proxy.detail();
}
}
运行效果:
以上就是代理模式中的静态代理,其中:
OrderService 接口是代理类和目标类的共同接口
OrderServiceImpl 是目标类
OrderServiceProxy 是代理类
思考一下:如果系统中业务接口很多,一个接口对应一个代理类,显然也是不合理的,会导致类爆炸。怎么解决这个问题?动态代理可以解决。因为在动态代理中可以在内存中动态的为我们生成代理类的字节码。代理类不需要我们写了。类爆炸解决了,而且代码只需要写一次,代码也会得到复用
三、动态代理
在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量,解决代码复用的问题
在内存当中动态生成类的技术常见的包括:
-
JDK 动态代理技术:只能代理接口
-
CGLIB 动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展 Java 类与实现 Java 接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比 JDK 动态代理要好(底层有一个小而快的字节码处理框架ASM)
-
Javassist 动态代理技术:Javassist 是一个开源的分析、编辑和创建 Java 字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码 JBoss 应用服务器项目,通过使用 Javassist 对字节码操作为 JBoss 实现动态"AOP"框架。
JDK 动态代理
java
package org.qiu.proxy.service;
/**
* 订单业务接口
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-20-08:23
* @since 1.0
*/
public interface OrderService {
/**
* 生成订单
*/
void generate();
/**
* 修改订单信息
*/
void modify();
/**
* 查看订单详情
*/
void detail();
}
java
package org.qiu.proxy.service;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-20-08:23
* @since 1.0
*/
public class OrderServiceImpl implements OrderService{
@Override
public void generate() {
// 模拟网络延迟
try {
Thread.sleep(1024);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成!");
}
@Override
public void modify() {
// 模拟网络延迟
try {
Thread.sleep(512);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已修改!");
}
@Override
public void detail() {
// 模拟网络延迟
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单详情......");
}
}
使用静态代理的时候,除了以上一个接口和一个实现类之外,还需要写一个代理类 UserServiceProxy!在动态代理中UserServiceProxy 代理类是可以动态生成的,这个类不需要写。我们直接写客户端程序即可:
java
package org.qiu.proxy.client;
import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.client
* @date 2022-11-20-08:22
* @since 1.0
*/
public class Test {
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
/**
* 参数解析:
* 参数一:Classloader loader 类加载器
* JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
* 参数二:Class<?>[] interfaces 代理类要实现的接口
* 代理类和目标类要实现同一个接口或同一些接口
* 参数三:InvocationHandler h 调用处理器对象(是一个接口)
* 在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)
*/
OrderService orderServiceProxy = (OrderService)
Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TimeInvocatioinHandler(target)
);
orderServiceProxy.detail();
orderServiceProxy.modify();
orderServiceProxy.generate();
}
}
java
package org.qiu.proxy.service;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-20-09:01
* @since 1.0
*/
public class TimeInvocatioinHandler implements InvocationHandler {
private Object target;
public TimeInvocatioinHandler(Object target) {
this.target = target;
}
/**
* invoke 什么时候被调用?
* 当代理对象调用代理方法的时候,注册在 InvocationHandler 调用处理器当中的 invoke 方法被调用
*
* @param proxy 代理对象的引用
* @param method 目标对象上的目标方法
* @param args 目标方法上的实际参数
* @return 若代理对象代用代理方法之后需要返回结果的话,invoke方法必须将目标对象目标方法执行结果继续返回
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long begin = System.currentTimeMillis();
// 调用目标对象上的目标方法
Object resultValue = method.invoke(target, args);
long end = System.currentTimeMillis();
System.out.println("耗时(毫秒):" + (end - begin));
return null;
}
}
上面调用 JDK 自带的方法比较繁琐,这里可以封装一个工具类,方便使用:
java
package org.qiu.proxy.util;
import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import java.lang.reflect.Proxy;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.util
* @date 2022-11-20-09:12
* @since 1.0
*/
public class ProxyUtil {
/**
* 获取代理对象(JDK动态代理)
* @param target
* @return
*/
public static Object newProxyInstance(Object target){
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));
}
}
java
package org.qiu.proxy.client;
import org.qiu.proxy.service.OrderService;
import org.qiu.proxy.service.OrderServiceImpl;
import org.qiu.proxy.service.TimeInvocatioinHandler;
import org.qiu.proxy.util.ProxyUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.client
* @date 2022-11-20-08:22
* @since 1.0
*/
public class Test {
public static void main(String[] args) {
OrderService target = new OrderServiceImpl();
/**
* 参数解析:
* 参数一:Classloader loader 类加载器
* JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个
* 参数二:Class<?>[] interfaces 代理类要实现的接口
* 代理类和目标类要实现同一个接口或同一些接口
* 参数三:InvocationHandler h 调用处理器对象(是一个接口)
* 在这里实现功能的增强(这里采用内部实现类,也可以将其独立出来)
*/
/*
OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TimeInvocatioinHandler(target));*/
// 工具类封装
OrderService orderServiceProxy = (OrderService) ProxyUtil.newProxyInstance(target);
orderServiceProxy.detail();
orderServiceProxy.modify();
orderServiceProxy.generate();
}
}
CGLIB 动态代理
CGLIB 既可以代理接口,又可以代理类。底层采用继承的方式实现。所以被代理的目标类不能使用 final 修饰。
使用CGLIB,需要引入它的依赖:
XML
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
准备一个没有实现接口的类,如下:
java
package org.qiu.proxy.service;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-20-09:22
* @since 1.0
*/
public class UserService {
public void login(){
System.out.println("用户正在登录系统....");
}
public void logout(){
System.out.println("用户正在退出系统....");
}
}
使用 CGLIB 在内存中为 UserService 类生成代理类,并创建对象:
java
package org.qiu.proxy.client;
import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.client
* @date 2022-11-20-09:23
* @since 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建字节码增强器
Enhancer enhancer = new Enhancer();
// 告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置回调接口
enhancer.setCallback(方法拦截器对象);
// 生成源码,编译class,加载到JVM,并创建代理对象
UserService userServiceProxy = (UserService)enhancer.create();
userServiceProxy.login();
userServiceProxy.logout();
}
}
和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,而是:net.sf.cglib.proxy.MethodInterceptor
编写MethodInterceptor接口实现类:
java
package org.qiu.proxy.service;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-20-09:24
* @since 1.0
*/
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return null;
}
}
MethodInterceptor接口中有一个方法intercept(),该方法有4个参数:
第一个参数:目标对象
第二个参数:目标方法
第三个参数:目标方法调用时的实参
第四个参数:代理方法
在MethodInterceptor的intercept()方法中调用目标以及添加增强:
java
package org.qiu.proxy.service;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.service
* @date 2022-11-20-09:24
* @since 1.0
*/
public class TimerMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 前增强
long begin = System.currentTimeMillis();
// 调用目标
Object retValue = methodProxy.invokeSuper(target, objects);
// 后增强
long end = System.currentTimeMillis();
System.out.println("耗时" + (end - begin) + "毫秒");
// 一定要返回
return retValue;
}
}
回调已经写完了,可以修改客户端程序了:
java
package org.qiu.proxy.client;
import net.sf.cglib.proxy.Enhancer;
import org.qiu.proxy.service.TimerMethodInterceptor;
import org.qiu.proxy.service.UserService;
/**
* @author 秋玄
* @version 1.0
* @email qiu_2022@aliyun.com
* @project Spring
* @package org.qiu.proxy.client
* @date 2022-11-20-09:23
* @since 1.0
*/
public class Client {
public static void main(String[] args) {
// 创建字节码增强器
Enhancer enhancer = new Enhancer();
// 告诉cglib要继承哪个类
enhancer.setSuperclass(UserService.class);
// 设置回调接口
enhancer.setCallback(new TimerMethodInterceptor());
// 生成源码,编译class,加载到JVM,并创建代理对象
UserService userServiceProxy = (UserService)enhancer.create();
userServiceProxy.login();
userServiceProxy.logout();
}
}
对于高版本的JDK,如果使用CGLIB,需要在启动项中添加两个启动参数:
-
--add-opens java.base/java.lang=ALL-UNNAMED
-
--add-opens java.base/sun.net.util=ALL-UNNAMED
一 叶 知 秋,奥 妙 玄 心