文章目录
代理模式
1.什么是代理模式?
代理模式的定义:给某一个对象提供一个代理,并由代理对象控制对原对象的引用。
- 代理是一23种设计模式之一,其目的就是: 为其他对象提供一个代理以控制对某个对象的访问 。
- 代理类负责为委托类 预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。
- 代理模式有几个:
虚拟代理
,计数代理
,远程代理
,动态代理
。主要分为2类: 静态代理和动态代理
2.代理模式的组成
-
真实角色 (RealSubject)
实现抽象角色
,定义真实角色所要实现的业务逻辑,提供代理角色调用
-
抽象角色 (Subject)
通过接口或抽象类 声明真实角色实现的业务方法。 -
代理角色 (ProxySubject)
实现抽象角色
,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
代理模式包含如下角色:
- ISubject:抽象角色,是一个接口。该接口是真实对象和它的代理对象共用的接口。
- RealSubject:真实角色,是实现抽象角色接口的类。
- Proxy:代理角色,内部含有对真实对象RealSubject的引用,从而可以操作真实对象。代理对象与真实对象实现了相同的接口,在任何时刻都能代替真实对象。同时,代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
注意:实现动态代理的关键技术是反射
。
静态代理
静态代理比较简单,是由程序员编写的代理类,并在程序运行前就编译好的
,而不是由程序动态产生代理类
,这就是所谓的静态
。
1.聚合式静态代理
抽象角色
public interface Subject {
public String talk(String message);
}
真实角色–汪老师
public class RealSubject implements Subject {
@Override
public String talk(String message) {
if("一起吃饭吧".equals(message)) {
return "你请客。。。";
}
if("一起跑步吧".equals(message)) {
return "我请客。。。";
}
return "好的好的";
}
}
代理角色–用于给汪老师聊天的代理
public class ProxyTalkSubject implements Subject{
//真实对象的引用
private Subject subject;
//真实对象的引用
public ProxyTalkSubject(Subject subject) {
this.subject=subject;
}
@Override
public String talk(String message) {
if("在吗?".equals(message)) {
return "在!";
}
if("你好?".equals(message)) {
return "你好!";
}
// 如果代理类处理不了,就交给真实类处理
return subject.talk(message);
}
}
代理对象可以在执行真实对象操作时,附加其他的操作,相当于对真实对象进行封装。
- 如:一些汪老师不需要的回答,代理可以自动帮他回答了,汪老师只需要关注自己需要的聊天即可
测试方法
public void static main(String [] args) {
//真实角色
Subject wang=new RealSubject();
//代理类依赖于真实类
Subject proxy=new ProxyTalkSubject(wang);
System.out.println(proxy.talk("在吗?"));
System.out.println( proxy.talk("你好?"));
System.out.println( proxy.talk("一起吃饭吧"));
System.out.println(proxy.talk("一起跑步吧"));
System.out.println(proxy.talk("一起LoL吧"));
System.out.println(proxy.talk("一起王者吧"));
}
执行结果
代理之间也可以相互传递
在上一个例子的基础上增加一个统计汪老师聊天次数的代理
统计聊天代理
public class ProxyCountSubject implements Subject {
private Integer count=0;
private Subject subject;
public ProxyCountSubject(Subject subject) {
this.subject=subject;
}
@Override
public String talk(String message) {
System.out.println("聊天:"+(++count));
return subject.talk(message);
}
}
测试代码
public void static main(String [] args) {
//真实角色
Subject wang=new RealSubject();
//获取聊天代理
Subject proxy1=new ProxyTalkSubject(wang);
//记录聊天次数
Subject proxy2=new ProxyCountSubject(proxy1);
System.out.println(proxy2.talk("在吗?"));
System.out.println( proxy2.talk("你好?"));
System.out.println( proxy2.talk("一起吃饭吧"));
System.out.println(proxy2.talk("一起跑步吧"));
System.out.println(proxy2.talk("一起LoL吧"));
System.out.println(proxy2.talk("一起王者吧"));
}
执行结果:
Java中如何使用JDK动态代理
需求:记录每次数据库操作的时间
年级抽象类
public interface GradeService {
void query();
void add();
}
年级真实类:(此处操作数据库使用伪代码
)
public class GradeServiceImpl implements GradeService{
@Override
public void add() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("年级增加...");
}
@Override
public void query() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("年级查询..");
}
}
年级代理类
public class GradeServiceProxy implements GradeService {
private GradeService gradeService;
public GradeServiceProxy(GradeService gradeService) {
this.gradeService=gradeService;
}
@Override
public void query() {
Long startTime=System.currentTimeMillis();
gradeService.query();
Long endTime=System.currentTimeMillis();
System.out.println("共耗时(s):"+(endTime-startTime)/1000);
}
@Override
public void add() {
Long startTime=System.currentTimeMillis();
gradeService.add();
Long endTime=System.currentTimeMillis();
System.out.println("共耗时(s):"+(endTime-startTime)/1000);
}
}
缺点:
- 代理类需要对重写之后的所有方法前后都加入相同的代码
- 如果有很多类都需要记录数据库操作时间,那么就需要对每一个真实类,编写一个对象的代理类
动态代理
2.继承式静态代理
代理类继承真实类,通过在代理类中调用父类的的方法,实现给方法前后添加操作
真实类
public class Admin {
@Override
public void doSomething() {
System.out.println("Log:admin新增了一个用户");
}
}
代理类
public class AdminProxy extends Admin {
@Override
public void doSomething() {
System.out.println("Log:admin操作开始");
super.doSomething();
System.out.println("Log:admin操作开始");
}
}
测试代码
AdminProxy proxy = new AdminProxy();
proxy.doSomething();
聚合式与继承式静态代理的区别
- 聚合实现方式中代理类聚合了被代理类,且代理类及被代理类都实现了同一个接口,可实现灵活多变。
- 继承式的实现方式则
不够灵活
。
比如:在管理员操作的同时需要进行权限的处理,操作日志的记录,操作后数据的变化三个功能。三个功能的排列组合有6种,也就是说使用继承要编写6个继承了Admin的代理类,而使用聚合,仅需要针对权限的处理、操作日志记录和数据变化三个功能编写代理类,在业务逻辑中根据具体需求改变代码顺序即可。
动态代理
1. 什么是动态代理?
- 一般来说,对代理模式而言,
一个真实类与一个代理类一一对应
,这也是静态代理模式的特点
。
但是,也存在这样的情况,有n各真实类,但是代理类中的“前处理、后处理”都是一样的,仅调用真实类不同。
也就是说,多个真实类类对应一个代理类,共享“前处理,后处理”功能,动态调用所需真实类,大大减小了程序规模,这就是动态代理模式的特点。
JDK动态代理
为了保持行为的一致性,代理角色和真实角色通常会实现相同的接口(抽象角色),所以在调用者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对真实对象的直接访问,也可以很好地隐藏和保护真实对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。
Java 动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念。
2. JDK动态代理
需求: 记录所有dao层 增 删 改 查的运行时间
要求记录所有dao层 增 删 改 查的运行时间
1如果使用静态代理的方式,需要为每一个service设置一个代理
抽象角色
public interface GradeService {
void update(String name);
void delete(String name);
void query(String name);
void add(String name);
}
真实角色
public class GradeServiveImpl implements GradetService {
@Override
public void add(String name) {
try {
Thread.sleep(666);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("添加年级:"+name);
}
@Override
public void query(String name) {
try {
Thread.sleep(2222);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("查询年级:"+name);
}
@Override
public void delete(String name) {
try {
Thread.sleep(1100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("删除年级:"+name);
}
@Override
public void update(String name) {
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("修改年级:"+name);
}
}
代理角色实现InvocationHandler接口,重写invoke()方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class ServiceProxy implements InvocationHandler {
private Object object;
// 1.写一个构造器接收真实对象
public ServiceProxy(Object object) {
this.object=object;
}
//2.重写invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Long startTime=System.currentTimeMillis();
//3.反射调用真实对象的方法 method.invoke(真实对象, 方法实参)
Object obj= method.invoke(object, args);
Long endTime=System.currentTimeMillis();
System.out.println("执行"+method.getName()+"耗时"+(endTime-startTime)+"毫秒\n");
return obj;
}
}
动态创建代理对象,调用代理方法
public class GradeCtrl {
private GradeService gradeService =(GradeService)Proxy.newProxyInstance(
GradeCtrl.class.getClassLoader(),
new Class<?>[] {GradeService.class},
new ServiceProxy(new GradeServiceImpl())
);
@RequestMapping("/grade/add.do")
public void add(HttpServletRequest req, HttpServletResponse resp) throws IOException {
gradeService.add();
resp.getWriter().println("增加年级成功...");
}
@RequestMapping("/grade/query.do")
public void query(HttpServletRequest req, HttpServletResponse resp) throws IOException {
gradeService.query();
resp.getWriter().println("查询年级成功...");
}
}
好处
: 只用编写一个代理类就可以供所有的类来使用,记录时间,减少了很多的重复代码
实现步骤
- 创建代理处理类实现
java.lang.reflect.InvocationHandler
接口
这是调用处理器接口,它自定义了一个invoke
方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。
class MyInvocationHandler implements InvocationHandler{
// 真实对象
private Object realSubject;
public MyInvocationHandler(Object realSubject) {
this.realSubject = realSubject;
}
/**
* 三个参数
* proxy -> 代理对象
* method -> 调用的方法描述
* args -> 调用的实际参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(realSubject, args);
}
- 动态创建代理对象
通过调用Proxy.newProxyInstance方法
来动态创建代理对象
// 三个参数
// 1. 加载接口的类加载器 (一般获取系统默认的,也可以自定义)
(当前类.class.getClassLoder())
// 2. 被代理的抽象类(接口),可以为多个以逗号分隔
(new Class<?> [] {xxxx.class,aaaa.class})
// 3. 调用处理器, 实际交由处理的实现类(真正执行代码的地方)
new StudentServiceProxy(真实类)
//返回了一个动态代理的实例对象,类型为接口类型 如: StudentService studentService`
StudentService studentService =(StudentService ) Proxy.newProxyInstance(
当前类.class.getClassLoader(),// 传入ClassLoader
new Class<?>[] {被代理的抽象类(接口)可以为多个以逗号分隔}, // 传入要实现的接口
new 处理类(真实类)// 传入处理调用方法的InvocationHandler
);
总结
JDK动态代理需要的类与接口
- java.lang.reflect.Proxy
用于创建代理对象 - java.lang.reflect.InvocationHandler
用于实现代理业务逻辑
实现步骤
- 通过
实现 InvocationHandler 接口
创建自己的调用处理器; - 在调用处理器调用invoke方法统一处理所有代理调用,传入调用方法对象,实际参数 ,在方法的前后增加额外操作
- 通过为 Proxy 类指定
ClassLoader 对象,一组 interface,调用处理器实例
来创建动态代理类对象来调用代理方法; - 生成的代理对象调用接口方法
优缺点
-
优点:
可以做到在不修改目标对象的功能前提下,对目标功能扩展 -
美中不足
:
Proxy 已经设计得非常优美,但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持 interface 代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫 Proxy。Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。
因此: 动态代理也叫接口代理
3.CGLIB代理
1.CGLIB是什么?
- CGLIB是一个功能强大,高性能的
代码生成包
。 - 它为
没有实现接口的
"类"
提供代理,为JDK的动态代理提供了很好的补充。 - 通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。
2.Java中如何使用CGLIB代理
代理角色
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class<?> clazz) {
// 设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
// 通过字节码技术动态创建子类实例
return enhancer.create();
}
// 实现MethodInterceptor接口方法
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("前置代理");
// 通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("后置代理");
return result;
}
}
真实角色
public class SayHello {
public void say() {
System.out.println("hello everyone");
}
}
测试方法
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
SayHello proxyImp = (SayHello) proxy.getProxy(SayHello.class);
proxyImp.say();
}
3.CGLIB原理
原理
- 动态生成一个要代理类的子类,子类重写要代理的类中所有不是final的方法。
- 在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。
CGLIB底层:
- 使用字节码处理框架ASM,来转换字节码并生成新的类。
- 不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。,
缺点
对于final方法,无法进行代理。
- CGLib创建的动态代理对象性能比JDK创建的动态代理对象的性能高不少
- 但是CGLib在创建 代理对象 时所花费的时间却比JDK多得多,所以对于单例的对象,
因为无需频繁创建对象,用CGLib合适,反之,使用JDK方式要更为合适一些。同时, - 由于CGLib由于是采用动态创建子类的方法,对于final方法,无法进行代理
https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984