目录
一、定义
二、结构
三、代码实现
四、AOP的基础
五、动态代理
5.1 Invocationhandler 和 Proxy
5.2 代码实现
六、特点
优点
缺点
七、适用场景
网络已经是现在人离不开的东西了,我们每天都要上网浏览信息。上网的方式也很简单。
但是,当我们要访问一些国外的网站时,就发现访问不了了。
这时候就出现了一些VPN,他可以帮助我们去访问一些国内不能访问的网站,也就是说他代理了这个访问过程,把结果返回给了我们。这就是代理模式。
代理模式:给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。
用上面的例子也比较好理解,也就是出现了一个代理类来代理原本的访问。
代理模式一般由三个角色组成:
Subject(抽象主题角色):声明了真实主题和代理主题的共同接口。
Proxy(代理主题角色):具体的代理类,它包含了真实主题的引用,也可以有自己的一些附属操作。
RealSubject(真实主题角色):实现了真实的业务操作。
实现一下上面的例子体会一下代理模式。
先实现一个抽象主题web,用来处理用户的请求。
public interface Web {
// 处理请求
void processRequest();
}
然后写一个可以正常访问的网站实现类:
public class Bilibili implements Web{
@Override
public void processRequest() {
System.out.println("你正在访问哔哩哔哩");
}
}
这时候用户可以正常访问:
public class Client {
public static void main(String[] args) {
Web web = new Bilibili();
web.processRequest();
}
}
然后再来一个不能正常访问的网站:
public class Youtube implements Web{
@Override
public void processRequest() {
System.out.println("你正在访问youtube");
}
}
假设这个时候中间有墙了,我们不能直接访问了,就需要一个VPN代理类帮我们访问。并且这个VPN会有一些其他操作。
你要先登录VPN,然后充钱才能访问,最后要关掉VPN
public class VPN implements Web{
private Web web;
public VPN() {
}
public void setWeb(Web web) {
login();
charge();
this.web = web;
close();
}
@Override
public void processRequest() {
web.processRequest();
}
private void login() {
System.out.println("登录VPN");
}
private void charge() {
System.out.println("给VPN充值");
}
private void close() {
System.out.println("关闭VPN");
}
}
有了VPN,我们就能访问之前访问不了的网站了。
public class Client {
public static void main(String[] args) {
Web web = new Youtube();
VPN = new VPN();
.setWeb(web);
.processRequest();
}
}
这就是代理模式的实现。
AOP:面向切面编程。
什么叫面前切面呢?我们依然可以用前面的例子来解释。
本来所有的网站都是可以访问的,不过就是向一个IP地址发起请求,他再返回数据给你。
但是有一个保安被安排插在了这个过程中间,你的请求全部被保安给拦截了。
这个保安要检查你请求的地址,是否是我们允许的地址。如果是,就给你放行;如果不是,就不让你请求,自然你就访问不到了。
这个过程并不是在网站上动手脚,而是从切面插入一个检测。
再举一个例子,我们的业务基本都是一个线性的流程。
而这时候老板给你提了一个要求,我们要在所有的业务里,添加一个日志功能。你怎么办?难道在每一个业务里的步骤里,一个个去加日志的输出吗?不用,我们利用面向切面的思想,直接把日志功能横插在所有业务中间。
这就是AOP。
那AOP的基础是什么?就是本篇文章的重点代理模式。
回想一下我们代码实现的VPN是怎么做的?它代理了用户的真实请求,帮用户去做这个请求,再把数据返回给用户。这个过程他可以有自己的收费等功能。
再比如日志功能,我们用一个代理类去代理业务里的步骤,并在代理类里加上日志功能。这样就能完成AOP的开发了。
前面的代理模式,我们是将代理类写好,并且创建出来了。但是这种方式有局限性,并且会占用内存。我们更希望是在运行时需要代理类的时候,代理类再生成。于是就有了动态代理。
Java里的动态代理依赖Invocationhandler和Proxy两个类。我们现来看一下这两个类的作用。
首先是Invocationhandler,这是一个很简单的接口,里面就定义了一个方法。这个接口是用来
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
源码里的注释是这样说的:
Processes a method invocation on a proxy instance and returns the result. This method will be invoked on an invocation handler when a method is invoked on a proxy instance that it is associated with.
处理代理实例上的方法调用并返回结果。 当在与其关联的代理实例上调用方法时,将在调用方法时调用此方法。
看不懂没关系,大概知道这个invoke方法是最终代理类代理方法的实现。后面配合代码才更好理解。
然后是Proxy,他的作用是用来创建一个代理类的。最常用的方法就是newProxyInstance()
public static Object newProxyInstance(ClassLoader loader,
Class>[] interfaces,
InvocationHandler h)
这个方法用来返回一个代理类,三个参数分别是:
classLoader, 决定了用哪一个classLoader来创建代理对象。
interfaces,一个接口数组,生产的代理类就会去实现这些接口。
InvocationHandler, 就是前面提到的,用来执行代理方法的。
光看定义太难懂了,我们还是用一个实际例子+写代码来配合理解吧。
我们的系统一般都会适用数据库(Database),数据库一般有增删改查的功能。数据库有很多种,比如MySQL, Oracle等,他们有各自实现增删改查功能的方法。
我们先把这个场景抽象:
public interface Database {
void add();
void delete();
void update();
void query();
}
public class MySQL implements Database{
@Override
public void add() {
// MySQL具体的处理
// ...
System.out.println("add");
}
@Override
public void delete() {
// MySQL具体的处理
// ...
System.out.println("delete");
}
@Override
public void update() {
// MySQL具体的处理
// ...
System.out.println("update");
}
@Override
public void query() {
// MySQL具体的处理
// ...
System.out.println("query");
}
}
public class Oracle implements Database{
@Override
public void add() {
// Oracle具体的处理
// ...
System.out.println("add");
}
@Override
public void delete() {
// Oracle具体的处理
// ...
System.out.println("delete");
}
@Override
public void update() {
// Oracle具体的处理
// ...
System.out.println("update");
}
@Override
public void query() {
// Oracle具体的处理
// ...
System.out.println("query");
}
}
现在开发人员就可以去调用各种数据库进行操作了。
public class Client {
public static void main(String[] args) {
Database database = new MySQL();
database.add();
database.delete();
database.query();
database.update();
}
}
现在,老板让你添加一个日志功能,需要输出你具体调用了哪一个数据库,和调用了哪一个方法。
我们用动态代理实现,写一个DataBaseHandler类去实现InvocationHandler接口。
p
ublic class DatabaseHandler implements InvocationHandler {
private Database database;
public DatabaseHandler(Database database) {
this.database = database;
}
// 获取代理类
public Database getDatabaseProxy() {
return (Database) Proxy.newProxyInstance(this.getClass().getClassLoader(), database.getClass().getInterfaces(),
this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(database.getClass().getName(), method.getName());
return method.invoke(database, args);
}
private void log(String databaseName, String methodName) {
System.out.println("[Debug] 调用了" + databaseName + "的" + methodName + "方法");
}
}
代码不长,我们一点点来理解。
首先这个类有一个成员变量database接口,并且给一个有参构造函数。
然后提供了一个方法getDatabaseProxy()
public Database getDatabaseProxy() {
return (Database) Proxy.newProxyInstance(this.getClass().getClassLoader(), database.getClass().getInterfaces(),
this);
}
这个方法就是调用了Proxy的newProxyInstance方法。这里的classLoader就传入DatabaseHandler的classLoader;接口传入了成员变量database的接口,通过getClass().getClassLoader()即可获取。最后一个参数自然就是this了。
这一步就是去生成了代理类,返回的就是一个实现了Database接口的代理类。
然后来看一下invoke函数
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(database.getClass().getName(), method.getName());
return method.invoke(database, args);
}
private void log(String databaseName, String methodName) {
System.out.println("[Debug] 调用了" + databaseName + "的" + methodName + "方法");
}
由getDatabaseProxy()创建出来的代理类,调用方法最终会走到invoke()方法。这个方法的三个参数分别就是创建的代理类,调用的方法,和方法参数。
这里我们调用method.invoke(database, args)去调用原本实际database的对应方法,然后在这一句前,添加了一个log方法。通过反射获取到了调用方法的名称。这就是在切面添加了一个日志功能。
最后看一下用户的调用。
public class Client {
public static void main(String[] args) {
Database database = new MySQL();
DatabaseHandler handler = new DatabaseHandler(database);
Database proxy = handler.getDatabaseProxy();
proxy.add();
proxy.update();
proxy.query();
proxy.delete();
}
}
首先我们要指定使用哪一个数据库,然后创建一个DatabaseHandler。通过getDatabaseProxy()去获取代理类,最终代理类去运行。最后的结果:
感受一下,这里我们没有去写一个代理类,而是交给Proxy去生成,然后InvocationHandler的invoke方法去实际执行。这个代理类,是在运行阶段生成的,不是我们自己去new出来的。
代理模式是最常用的设计模式之一,因为他包含以下优点:
可以使真实业务角色责任更纯粹,不用包含一些公共业务。
公共业务交给了代理类,实现了解耦。
提供了面向切面编程的基础,使一个横向业务更容易编写
动态代理可以代理多个实现了同一个接口的类。
增加代理类可能会导致业务处理速度变慢。
以下场景适合适用代理模式
当业务无法直接访问某个类时,需要一个代理类去代理访问。
当需要横向添加一些功能,比如日志功能时,可以使用代理模式。