源码链接(Gitee码云):https://gitee.com/oldou/javadesignpatterns
这里有我整理好的Java23种设计模式的源码以及博客教程,博客教程中介绍了Java23种设计的模式的各种实现方式以及应用场景,非常适用于学习以及提高我们的设计思维,如果对大家有所帮助,请记得star一下给予作者一定的精神支持,你的star是我写出更好的博客的动力,谢谢大家。
代理模式(proxy pattern)
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
核心作用:
- 通过代理,控制对对象的访问!
- 可以详细控制访问某个(某类)对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。(即:AOP的微观实现!)
- 实现将统一流程代码放到代理类中处理。
AOP(Aspect Oriented Programming)
面向切面编程)的核心实现机制!
核心角色
-
抽象角色
– 定义代理角色和真实角色的公共对外方法 -
真实角色
– 实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
– 关注真正的业务逻辑! -
代理角色
– 实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
– 将统一的流程控制放到代理角色中处理
应用场景
- 安全代理:屏蔽对真实角色的直接访问。
- 远程代理:通过代理类处理远程方法调用(RMI)
- 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象。
- 比如你要开发一个大文档查看软件,大文档中有大的图片,有可能一个图片有100MB,在打开文件时不可能将所有的图片都显示出来,这样就可以使用代理模式,当需要查看图片时,用proxy来进行大图片的打开。
分类
(1)静态代理(静态定义代理类)
(2)动态代理(动态生成代理类)
- JDK自带的动态代理
- javaassist字节码操作库实现动态代理
- CGLIB动态代理
- ASM(底层使用指令,可维护性较差)
本文只介绍静态代理模式和JDK动态代理模式的使用。
案例
比如说,我们以生活中的租房为案例,我们租房一般都是找的中介,而中介就是一个代理的角色,他代表房东帮助他把房子租出去,房东只需要提供租房即可,无需提供其他的操作,而房东和中介的最终都是要将房子租出去,因此他们有一个共同的接口,下面我们用代码来实现一下。
静态代理模式
提供租房的接口:
/**
* 租房
*/
public interface Rent {
public void rent();//租房这个事情
}
真实的角色:房东
/**
* 真实的角色:房东
* 需要将房子租出去
*/
public class FangD implements Rent {
@Override
public void rent() {
System.out.println("出租XXX房,两室一厅....");
}
}
代理角色:中介
/**
* 代理角色:中介
* 需要代理房东去租房,因此也要实现租房的方法
*/
public class Proxy implements Rent {
private FangD fd;
//提供一个房东的有参构造
public Proxy(FangD fd) {
this.fd = fd;
}
public Proxy(){
}
@Override
public void rent() {
seeHouse(); //中介这里扩展的操作 看房
fd.rent();//调用房东的租房方法
hetong();//签合同
fare();//收取中介费
}
//看房
public void seeHouse(){
System.out.println("中介带你看房..");
}
//收中介费
public void fare(){
System.out.println("中介收你中介费100元..");
}
//签合同
public void hetong(){
System.out.println("签合同...");
}
}
客户:租房的人
/** 静态代理模式
* 真实的人:需要租房的人--客户
*/
public class Client {
public static void main(String[] args) {
//房东要出租房子
FangD fd = new FangD();
//代理,中介要帮房东出租房子,并且中介还可以做一些附属操作
Proxy proxy = new Proxy(fd);
//不用找房东,直接找中介租房
proxy.rent();
}
}
运行输出:
静态代理模式在业务中的实现
我们平时使用代理模式时,加入需要给我们执行的业务增添一个日志的功能,而在公司中改动源码是大忌,因此我们需要将日志给添加进行就可以通过代理模式进行横向扩展,如下所示:
业务接口:
public interface UserService {
void add();
void delete();
void update();
void query();
}
业务接口的实现类:
public class UserServiceImpl implements UserService {
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("修改了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
业务代理(实现增加日志功能):
/**
* 代理类,我们在此添加一个日志的功能
*/
public class UserServiceProxy implements UserService {
private UserServiceImpl userService;
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
public void log(String msg){
System.out.println("[Debug]-->使用了"+msg+"方法");
}
}
测试:
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
}
}
输出:
静态代理的优点:
- 房东只需要找到一个代理出租房子,其他操作就交给代理对象了
- 这样可以使真实角色的操作更加纯粹,不用去关注一些公共的业务
- 公共业务交给代理角色,实现业务分工
- 公共业务发生扩展的时候,方便集中管理
- 我们在不改变原有业务的基础上,给项目扩展一些功能,代理模式就非常适合,相当于横向开发
缺点:
- 一个真实角色就会产生一个代理角色,代码量会翻倍,开发效率会变低
动态代理模式
- 动态代理和静态代理角色一样;
- 动态代理的代理类是动态生成的,不是我们直接写好的;
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理;
动态代理模式的实现方式主要有以下:
基于接口:JDK动态代理
基于接口:java字节码实现动态代理(javasist)
基于类:CGlib动态代理
ASM(底层使用指令,可维护性较差)
动态代理的优点(相比于静态代理):
抽象角色中(接口)声明的所以方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。
下面逐一介绍。
JDK实现动态代理
JDK的动态代理有两个核心的类:Proxy和InvocationHandler
-
java.lang.reflect.Proxy
: 作用:动态生成代理类和对象 -
java.lang.reflect.InvocationHandler
(处理器接口)
• 可以通过invoke方法实现对真实角色的代理访问。
• 每次通过Proxy生成代理类对象对象时都要指定对应的处理器对象
下面我们还是以租房为案例,来实现以下JDK的动态代理。
代码测试
租房的接口:
/**
* 租房
*/
public interface Rent {
public void rent();//租房这个事情
}
真实的角色:房东
/**
* 真实的角色:房东
* 需要将房子租出去
*/
public class FangD implements Rent {
@Override
public void rent() {
System.out.println("出租XXX房,两室一厅....");
}
}
自动生成代理类的类(重点):
/**
* 这个类会自动的生成代理类
*/
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成得到代理类
//newProxyInstance:返回代理类的一个实例
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
//处理代理实例并返回结果
//proxy指的是代理类
//method指的是被代理的方法
//args指的是该方法的参数对象
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质就是使用反射机制实现的
seeHouse(); //扩展操作
Object result = method.invoke(rent, args);
writeHeTong();//扩展操作
return result;
}
//扩展操作
public void seeHouse(){
System.out.println("中介带你看房子");
}
public void writeHeTong(){
System.out.println("签署合同");
}
}
客户端:
/**
* 客户
*/
public class Client {
public static void main(String[] args) {
//真实角色
FangD fd = new FangD();
//代理角色:现在是没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过调用程序处理角色来处理我们要调用的接口对象
pih.setRent(fd);
//下面的proxy就是动态生成的代理对象
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
测试输出:
总结:
动态代理步骤:
1、创建一个实现接口InvocationHandler的类,它必须实现invoke方法
2、创建被代理的类以及接口
3、通过Proxy的静态方法newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理类
4、通过代理调用方法
从JDK的动态代理实现可以看出,实际上就是利用了反射的机制。
首先通过实现 InvocationHandler 接口创建自己的调用处理器,然后通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类,之后再通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型,最后通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
JDK动态代理在业务中的实现
我们还是以上面给业务添加日志的功能为例,同时将自己的调用处理器代码做一定的修改,变成一个相当于工具类的形式,如下所示:
ProxyInvocationHandler 调用处理器:
/**
* 这个类会自动的生成代理类
*
*/
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的目标接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
//处理代理实例并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质就是使用反射机制实现的
log(method.getName());
Object result = method.invoke(target, args);
return result;
}
//扩展操作--日志
public void log(String msg){
System.out.println("[Debug]--->执行了"+msg+"方法");
}
}
客户端:
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceImpl userService = new UserServiceImpl();
//代理角色 不存在
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//设置要代理的对象
pih.setTarget(userService);
//动态的为其生成代理对象
UserService proxy = (UserService)pih.getProxy();
proxy.add();
}
}
输出:
开发框架中的应用场景
- struts2中拦截器的实现
- 数据库连接池关闭处理
- Hibernate中延时加载的实现
- mybatis中实现拦截器插件
- AspectJ的实现
- spring中AOP的实现
• 日志拦截
• 声明式事务处理 - web service
- RMI远程方法调用
- 实际上,随便选择一个技术框架都会用到代理模式