代理模式
java动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
cglib动态代理:利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- 概念
- 真实对象:被代理的对象
- 代理对象
- 代理模式:(代理对象代理真实对象,达到增强真实对象功能的目的)
- 实现方式:
- 静态代理:有一个类文件描述代理模式
- 动态代理:在内存中形成代理类
- 实现步骤:
- 代理对象和真实对象实现相同的接口
- 代理对象 =
Proxy.newProxyInstance();
- 使用代理对象调用方法。
- 增强方法
- 增强方式:
- 增强参数列表
- 增强返回值类型
- 增强方法体执行逻辑
- 实现步骤:
代理对象和被代理对象实现了相同的一些接口必须是相同的!由于是面向接口编程,多态的思想,所以可以不改变源码的基础上添加功能,可扩展性高,非常方便。
动态代理终极版.png
- java.lang.reflex.Proxy.newProxyInstance方法(下文简称“NPI方法”)创建代理对象的步骤
NPI方法需要三个参数,
①加载相关接口的类加载器loader、
②代理对象要实现的接口类对象interface数组、
③代理对象最终执行方法所属的匿名内部类IH并实现该接口的invoke方法
NPI方法利用反射机制通过类加载器将代理对象要实现的接口类加载到方法区,在堆内存中创建了代理对象,这个代理对象可牛逼了,实现了接口中的各个方法(JDK的底层实现细节,暂时不懂!而且似乎是可变长列表),然后在各个方法中通过反射机制获得了对应方法的类对象Method,然后在方法内调用匿名内部类IH的invoke方法,传入三个参数,
①代理对象本身的引用this、
②通过反射机制实现的 调用匿名内部类IH的invoke方法的方法所对应的Method对象、
③方法对象运行需要的对应参数。
最后,NPI方法返回代理对象的引用
- 代理对象调用代理方法执行流程如下:
调用
代理对象.代理方法(代理参数可变长列表)
。
会找到对应的代理方法,传入代理参数并入栈执行。
这个代理方法中会生成被代理方法对应的Method对象,然后执行匿名内部类的invoke方法,匿名内部类invoke方法中传入的参数为代理对象、代理方法,传入的代理参数列表;在匿名内部类invoke方法中就可以实现 ①增强传入的参数②增强返回值③在被代理对象的方法执行前后添加逻辑代码; 代理对象通过Method对象执行被代理对象的方法(反射机制)method.invoke(被代理的对象,参数)其实,说白了,动态代理的本质就是执行匿名内部类的invoke方法,这个方法内可以在被代理方法执行前后以及被代理方法的参数、返回值进行一些装饰加工。python中的装饰器也可以实现这种功能,python中可以直接将函数当做参数,而且可以在函数内嵌套定义函数,理解起来很容易!
如下案例演示:
- 被代理对象实现的接口
public interface SaleComputer {
public String sale(double money);
public void show();
public Object test();
}
- 被代理对象的逻辑代码
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("花了" + money + "元买了一台联想电脑");
return "联想电脑"; }
// 展示电脑
public void show() {
System.out.println("展示电脑...."); }
@Override
public Object test() { return null; }
}
- 使用代理增强案例
public static void main(String[] args)
...
// 1.创建真实对象
Lenovo lenovo = new Lenovo();
/** 和【python】中的【函数装饰器@】功能如出一辙
* 三个参数:
* 1. 类加载器:真实对象.getClass().getClassLoader()
* 2. 接口数组:真实对象.getClass()..getInterfaces()
* 3. 处理器:new InvocationHandler() 匿名内部内实现
* invoke(代理对象,代理方法,代理方法参数) 方法
*/
// 使用动态代理增强lenovo对象
SaleComputer proxy_lenovo = (SaleComputer)Proxy.newProxyInstance(
lenovo.getClass().getClassLoader(), // 负责加载相关的接口
lenovo.getClass().getInterfaces(), // 被加载执行的接口
new InvocationHandler() { // 接口协议,
/**
* 代理逻辑编写的方法,代理对象调用的所有方法都会触发该方法执行。
* 参数:
* 1. proxy: 代理对象
* 2. method: 代理对象调用的方法,被封装为的对象。
* 3. args: 代理对象调用方法时,传递的实际参数。
*/
@Override
public Object invoke(Object proxy, // 代理对象,就是proxy_lenovo,方法里面使用this访问的为该匿名内部类
Method method, // 被代理执行的方法
Object[] args) // 被代理方法的参数
throws Throwable {
if(method.getName().equals("sale")) {
// 1.增强参数案例演示【修改函数执行的参数】
double money = (double)args[0] * 0.85;
// 2.1 增强方法逻辑演示【在方法执行前后加一些代码】
System.out.println("==========");
System.out.println("专车接.....");
// @@@@@@@ 使用真实对象调用该方法 @@@@@@@
Object obj = method.invoke(lenovo, money);
// @@@@@@@ ================== @@@@@@@
// 2.2 增强方法逻辑演示
System.out.println("专车送.....");
System.out.println("==========");
// 3. 增强返回值演示【在返回值上做手脚】
return (String)obj + ",赠送一个_美女机器人";
} else if(method.getName().equals("test"))
return proxy;
else
return method.invoke(lenovo, args);
}
}
);
// sale方法被代理增强了
String computer = proxy_lenovo.sale(8000);
System.out.println(computer);
// show方法原样调用
proxy_lenovo.show();
// test方法进行测试
Object obj = proxy_lenovo.test();
System.out.println("判断是为同一个对象!:" + (obj == proxy_lenovo)); // true
}
filter拦截器过滤敏感词汇案例
思路:返回值增强,在调用被代理方法之后对参数进行判断替换!
- 步骤,
- 实现动态代理对象
- 将动态代理对象放行
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
@WebFilter("/*")
public class SensitiveFilter implements Filter {
private String[] sensitive = null;
/**
* 敏感词汇过滤器,使用动态代理增强的方式
* 进行加强,使用返回值加强方式进行加强
*/
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
ServletRequest newProxyInstance = (ServletRequest)Proxy.newProxyInstance(
req.getClass().getClassLoader(),
req.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 判断getParameter方法!
if (method.getName().equals("getParameter")) {
String str = (String)method.invoke(req, args);
for (String s : sensitive) {
if (str.contains(s))
str = str.replaceAll(s, "***");
}
return str;
}
// 判断getParameterMap方法
// Map<String, String[]> map = req.getParameterMap();
if(method.getName().equals("getParameterMap")) {
Map<String, String[]> map = (Map<String, String[]>)method.invoke(req, args);
Map<String, String[]> newMap = new HashMap<>();
for (String s : map.keySet()) {
String[] temp = map.get(s);
for (int i = 0; i < temp.length; i++) {
for (String m : sensitive) {
if(temp[i].contains(m))
temp[i] = temp[i].replaceAll(m, "***");
}
}
newMap.put(s, temp);
}
return newMap;
}
return method.invoke(req, args);
}
}
);
// 将代理对象放行
chain.doFilter(newProxyInstance, resp);
}
// 初始化过滤的字符,这里以一个数组演示!
public void init(FilterConfig config) throws ServletException {
sensitive = new String[]{"混蛋", "坏蛋", "王八蛋", "王八", "锤子"};
}
public void destroy() { }
}
网友评论