作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬
学习必须往深处挖,挖的越深,基础越扎实!
码哥源码部分
码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】
码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】
码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】
码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】
打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】
代理模式
定义
代理模式为另一个对象提供一个替身或占位符以控制对这个对象访问。
代理模式 (英语:Proxy Pattern)是程序设计中的一种设计模式。所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网络连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。
著名的代理模式例子为引用计数(英语:reference counting)指针对象。
当一个复杂对象的多份副本须存在时,代理模式可以结合享元模式以减少存储器用量。典型作法是创建一个复杂对象及多个代理者,每个代理者会引用到原本的复杂对象。而作用在代理者的运算会转送到原本对象。一旦所有的代理者都不存在时,复杂对象会被移除。
(引用维基百科)
比如要访问远程的机器,由于某些原因,直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问对象时加上一个对此对象的访问层。比如RMI
技术。
类图
实现
1. JDK实现
定义Subject接口
public interface PersonBean {
String getName();
String getGender();
String getInterest();
int getHotOrNotRating();
void setName(String name);
void setGender(String gender);
void setInterest(String interest);
void setHotOrNotRating(int rating);
}
定义Subject实现接口
public class PersonImpl implements PersonBean {
String name;
String gender;
String interests;
int rating;
int ratingCount = 0;
@Override
public String getName() {
return name;
}
@Override
public String getGender() {
return gender;
}
@Override
public String getInterest() {
return interests;
}
@Override
public int getHotOrNotRating() {
if (ratingCount == 0) return 0;
return (rating / ratingCount);
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public void setGender(String gender) {
this.gender = gender;
}
@Override
public void setInterest(String interest) {
this.interests = interest;
}
@Override
public void setHotOrNotRating(int rating) {
this.rating += rating;
ratingCount++;
}
}
定义InvocationHandler实现类
/**
* 步骤一: 创建InvocationHandler类。这个类与Proxy互相配合。遵循单一职责。Proxy负责动态生成代理对象。而InvocationHandler则是实现业务逻辑
* 个人代理对象: 允许获取和设计自己属性
*/
public class OwnerInvocationHandler implements InvocationHandler {
PersonBean personBean ;
public OwnerInvocationHandler(PersonBean personBean) {
this.personBean = personBean;
}
/**
* 业务上的增加实现。
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("get")) {
return method.invoke(personBean, args);
} else if (method.getName().equals("setHotOrNotRating")) {
throw new IllegalAccessException();
} else if (method.getName().startsWith("set")) {
return method.invoke(personBean, args);
}
return null;
}
}
/**
* 步骤一: 创建InvocationHandler类
*/
public class NonInvocationHandler implements InvocationHandler {
PersonBean personBean;
public NonInvocationHandler(PersonBean personBean) {
this.personBean = personBean;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("get")) {
return method.invoke(personBean, args);
} else if (method.getName().equals("setHotOrNotRating")) {
method.invoke(personBean, args);
} else if (method.getName().startsWith("set")) {
throw new IllegalAccessException();
}
return null;
}
}
Main
/**
* 1. 动态代理之所以称为动态: 是因为在运行时才将它的类创建出来。代码开始执行时,还没有proxy类,它是根据需要从你传入的接口集创建的。
* 2. InvocationHandle不是proxy,它只是一个帮助proxy的类,proxy会把调用转发给它处理。Proxy本身是复用静态的Proxy.newProxyInstance()方法在运行时动态地创建的。
* 3. 对于newProxyInstance()的接口数据。如果接口不是public,就必须属于同一个package,不同的接口内,不可以有名称和参数完全一样的方法。
* 4. 动态代理有点像工厂模式,对于不同的需要被代理的对象产生对应的代理对象。
*/
public class M {
public static void main(String[] args) {
PersonBean joe = getJoe();
PersonBean ownerProxy = getOwnerProxy(joe);
System.out.println("姓名: " + joe.getName());
ownerProxy.setInterest("我喜欢篮球");
try {
ownerProxy.setHotOrNotRating(10);
} catch (Exception e) {
System.out.println("不能自己设定评分哦,需要别人对自己评分才有意义");
}
System.out.println("当前Joe评分:" + joe.getHotOrNotRating());
PersonBean nonProxy = getNonProxy(joe);
System.out.println("姓名: " + nonProxy.getName());
nonProxy.setHotOrNotRating(10);
try {
nonProxy.setInterest("hey");
} catch (Exception e) {
System.out.println("当前兴趣爱好不能被其他人设置");
}
System.out.println("当前评分: " + nonProxy.getHotOrNotRating());
}
public static PersonBean getTom() {
PersonBean joe = new PersonImpl();
joe.setName("Tom");
joe.setGender("Man");
joe.setHotOrNotRating(1);
joe.setInterest("Painting");
return joe;
}
public static PersonBean getJoe() {
PersonBean joe = new PersonImpl();
joe.setName("Joe");
joe.setGender("Man");
joe.setHotOrNotRating(1);
joe.setInterest("Painting");
return joe;
}
public static PersonBean getOwnerProxy(PersonBean personBean) {
return (PersonBean) Proxy.newProxyInstance(personBean.getClass().getClassLoader(), personBean.getClass().getInterfaces(), new OwnerInvocationHandler(personBean));
}
public static PersonBean getNonProxy(PersonBean personBean) {
return (PersonBean) Proxy.newProxyInstance(
personBean.getClass().getClassLoader(),
personBean.getClass().getInterfaces(),
new NonInvocationHandler(personBean));
}
}
2. CbLib实现
1. 引入Maven
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
2. 创建目标类
public class Target {
public void targetMethod(String s1) {
System.out.println(String.format("当前Classloader%s, 方法参数: %s", this.getClass().getClassLoader(), s1));
}
}
3. 实现方法拦截器
public class CgLibProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy proxy) throws Throwable {
beforeMethod(args);
Object result = proxy.invokeSuper(o, args);
afterMethod(args);
return result;
}
public void beforeMethod(Object[] args) {
System.out.println("Aop 前置 参数校验");
if (args.length != 1) {
throw new IllegalArgumentException("只允许有一个参数");
}
args[0] = "参数校验成功: " + args[0];
}
public void afterMethod(Object[] args) {
System.out.println("Aop 后置 增强操作!");
}
}
4. 创建代理对象
public class M {
public static void main(String[] args) {
Enhancer eh = new Enhancer();
// 设置被代理的类(目标类)
eh.setSuperclass(Target.class);
// 使用回调
eh.setCallback(new CgLibProxy());
// 创建 代理
Object proxy = eh.create();
Target t = (Target) proxy;
t.targetMethod("h1");
}
}
总结
-
代理模式属于结构型模式。
-
代理模式基于接口而非实现编程的设计,将原始类对象替换为代理类对象的时候,为了让代码改动较少,代理类和原始类都需要实现相同的接口。但如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的,对于这种外部类的扩展,一般是采用继承方式实现。
-
动态代理指不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。
-
应用场景
- 业务系统的非功能性需求开发。如监控、统计、鉴权、限流、事务、幂等、日志。
- RPC、缓存。