代理模式
定义
代理模式为另一个对象提供一个替身或占位符以控制对这个对象访问。
代理模式 (英语: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、缓存。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。