众生周知,AOP实现原理是基于动态代理。
什么是代理?
增强一个类,或者一个对象的功能。就可以说是代理。
如:买火车票? app 12306 就是一个代理,他代理了火车站。
代理对象
:增强后的对象
目标对象
:被增强的对象
他们的身份不是绝对的,会根据情况发生变化
1.1静态代理
代理对象继承目标对象,重写需要增强的方法。完成静态代理!调用即可。。面向接口编程就是静态代理
继承方式代理会有一个问题。每次增强都需要继承。。如果需要多次(如下聚合)
增强,会产生很多代理类。相对复杂。
目标对象,和代理对象实现同一个接口。并且代理对象包含 目标对象;
核心示例代码:
传入目标对象,对目标对象增强!切换不同的类,就可以对不同的类完成代理。
//代理对象1
public class UserDaoLog implements UserDao {
//装饰者模式,传入对象
private UserDao dao;
public UserDaoLog(UserDao dao){
this.dao=dao;
}
/**
* 对目标对象做增强处理
*/
@Override
public void query() {
System.out.println("打印日志。");
dao.query();
}
//代理对象2
public class UserDaoTime implements UserDao {
//装饰者模式,传入对象
private UserDao dao;
public UserDaoTime(UserDao dao){
this.dao=dao;
}
/**
* 对目标对象做增强处理
*/
@Override
public void query() {
System.out.println("打印时间。");
dao.query();
}
}
//传入目标对象完成代理,要代理其他类,只需要切换对象。
如果老板说,既要完成打印时间,又要完成打印日志该怎么办呢?
public static void main(String[] args) {
UserDao dao = new UserDaoImpl();
// UserDao prox = new UserDaoTime(dao);
UserDao prox = new UserDaoLog(dao);
prox.query();
}
思考:如果我们要完成多重代理呢?改怎么办?(既要完成打印时间,又要完成打印日志)
1.1.1 多重代理(多次增强!)
对多个代理对象,代理。。完成双重代理。(如下代码片段,只需要传入 UserDaoLog+UserDaoImpl)
然后再对他们做代理。。继承的方式 就会产生很多类。聚合实现起来就更加简单。
public static void main(String[] args) {
UserDao traget = new UserDaoLog(new UserDaoImpl());
UserDao prox = new UserDaoTime(traget);
prox.query();
}
---------------------控制台输出------------------------
打印时间。
打印日志。
假装查询数据库
假设这个时候,老板要求先打印日志,再打印时间呢?
public static void main(String[] args) {
UserDao traget = new UserDaoTime(new UserDaoImpl());
UserDao prox = new UserDaoLog(traget);
prox.query();
}
---------------------控制台输出------------------------
打印日志。
打印时间。
假装查询数据库
(userDao)
做代理,如果对多个类做代理呢?就算用聚合的方式一样会产生很多类。只不过比继承少一点,如上每个类做一次双重代理,就需要两个代理类。这个问题该怎么解决呢?
如果在不确定的情况下,尽量不要使用静态代理。因为一但写代码就会产生类。一但产生类,就会写到爆炸。
那什么场景会用到静态代理呢?JDK中的IO流,BufferIO流,其实都是使用的装饰者模式。就是静态代理的方式。1.2 手写动态代理
面试官:什么是动态代理?
初中级开发:基于java反射,增强啊,调用者和实现者之间解耦啊。张口就来了
网上博客:1:抽象类接口,2:被代理类(具体实现抽象接口的类),3:动态代理类:实际调用被代理类的方法和属性的类。InvocationHandler 方法啊等等等,都是只懂皮毛
我们是不是得先有一个类?去继承或者实现目标对象?才能去反射?可是我们连类都没有,拿什么去反射?所以反射只是动态代理的冰山一角。那么现在我们不能在项目中去创建一个类,该怎么办?可以把这个类定义在代码中吗?不管可不可以我们先来试一下
。如下:假设我们要对UserDaoImpl 做代理。如下图所示,我们已经得到了要代理UserDaoImpl的所有内容
public class UserDaoImpl implements UserDao{
public void query(){
System.out.println("假装查询数据库");
}
public String query(String aa){
return aa;
}
}
public static void main(String[] args) {
//获取UserDaoImpl实现的接口 我们这是山寨版的动态代理,暂时只处理第一个接口
Class targetInf = new UserDaoImpl().getClass().getInterfaces()[0];
//获取到接口的方法
Method methods[] = targetInf.getDeclaredMethods();
//定义换行
String line = "\n";
//定义tab空格
String tab = "\t";
//获取到实现的接口名字 这里就是UserDao
String infName = targetInf.getSimpleName();
String content = "";
//我们要写一个类的类容 第一行是不是就是外面的包名?这里就写的叼一点google
String packageContent = "package com.google;" + line;
//第二行 是不是我们要导包?类.getName是不是就得到了类的全路径 com.luban.dao.UserDao
String importContent = "import " + targetInf.getName() + ";" + line;
//创建一个类,类名为$Proxy 实现了接口UserDao
String clazzFirstLineContent = "public class $Proxy implements " + infName + "{" + line;
//定义私有变量 private UserDao target;
String filedContent = tab + "private " + infName + " target;" + line;
//定义构造方法 传入 UserDao target 并且给私有成员变量赋值
String constructorContent = tab + "public $Proxy (" + infName + " target){" + line
+ tab + tab + "this.target =target;"
+ line + tab + "}" + line;
String methodContent = "";
//遍历接口的所有方法
for (Method method : methods) {
//获取到方法的返回类型
String returnTypeName = method.getReturnType().getSimpleName();
//获取到方法名称
String methodName = method.getName();
// 获取到方法的入参类型,如Sting.class String.class
Class argse[] = method.getParameterTypes();
String argsContent = "";
String paramsContent = "";
int flag = 0;
for (Class arg : argse) {
//遍历入参类型数组,获取到入参的对象名称 如 String
String temp = arg.getSimpleName();
System.out.println(temp);
//拼装参数 如 ,String p0,Sting p1,
argsContent += temp + " p" + flag + ",";
//拼装形参(需要调用父类方法用到) 如 , p0, p1,
paramsContent += "p" + flag + ",";
flag++;
}
//截取掉参数的最后一个逗号
if (argsContent.length() > 0) {
argsContent = argsContent.substring(0, argsContent.lastIndexOf(",") - 1);
paramsContent = paramsContent.substring(0, paramsContent.lastIndexOf(",") - 1);
}
//我们有了 返回类型,有了方法名,有了入参,还有了调用父类的方法。就可以拼装 实现的方法
methodContent += tab + "public " + returnTypeName + " " + methodName + "(" + argsContent + ") {" + line;
//判断返回值不等于void return
if (!returnTypeName.equals("void")) {
methodContent+=tab + tab + "System.out.println(\"带参数测试增强打印log\");" + line
+ tab + tab + "return target." + methodName + "(" + paramsContent + ");" + line
+ tab + "}" + line;
}else {
methodContent+= tab + tab + "System.out.println(\"测试假装增强打印log\");" + line
+tab + tab + "target." + methodName + "(" + paramsContent + ");" + line
+ tab + "}" + line;
}
}
//最后把全部加起来。就是外面一个类里面的内容
content = packageContent + importContent + clazzFirstLineContent + filedContent + constructorContent + methodContent + "}";
System.out.println(content);
}
---------------- ---------------------控制台输出-------------------------------------------------
package com.google;
import com.luban.dao.UserDao;
public class $Proxy implements UserDao{
private UserDao target;
public $Proxy (UserDao target){
this.target =target;
}
public void query() {
System.out.println("测试假装增强打印log");
target.query();
}
public String query(String p) {
System.out.println("测试假装增强打印log");
return target.query(p);
}
}
如上图,我们已经得到了要代理UserDaoImpl代码内容,得到了代码内容我们是不是还得将内容产生一个.java文件?是不是还得编译.java文件得到.class文件?是不是还得new 这个对象 才能完成代理?
1、代码接上面部分。(伸手党拷贝的同学注意了)
如下,我们把内容写到G盘上产生.java文件。并且编译它。 //代码接上面部分+
File file1 =new File("G:\\com\\google");
File file =new File("G:\\com\\google\\$Proxy.java");
//判断是否是一个目录
if (!file1.isDirectory())
//创建目录
file1.mkdirs();
if (!file.exists())
//创建文件
file.createNewFile();
//将字符串 写出到磁盘
FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close();
//引用java编译器。编译java文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
//传入要编译的文件
Iterable units = fileMgr.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
2.接下来我们.java 文件也有了,.class文件也有了,我们怎么得到这个对象呢?这些代码都在我们的本地磁盘中,怎么加载到我们的项目中呢?还记得我们的类加载器嘛?这里就派上用场了。
伸手党拷贝
的同学注意了)//创建需要加载.class的地址
URL[] urls = new URL[]{new URL("file:G:\\\\")};
URLClassLoader urlClassLoader = new URLClassLoader(urls);
//加载class对象
Class clazz = urlClassLoader.loadClass("com.google.$Proxy");
//反射获取到构造方法 构造方法带参数,所以传入接口对象
Constructor constructor = clazz.getConstructor(targetInf);
//构造方法传入目标对象 就拿到了增强后的代理对象
UserDao proxy = (UserDao)constructor.newInstance(new UserDaoImpl());
System.out.println(proxy);
//代理对象调用无参方法
proxy.query();
//代理对象调用有参方法
System.out.println(proxy.query("哈哈哈哈哈哈哈哈测试成功"));
---------------- ---------------------控制台输出-------------------------------------------------
com.google.$Proxy@482f8f11
测试假装增强打印log
假装查询数据库
带参数测试增强打印log
哈哈哈哈哈哈哈哈测试成功