代理是什么?
为某个对象提供一个代理,以控制对这个对象的访问。使用jdk代理那就必然要定义和实现接口。
动态代理本质:通过反射创建对象。
一、静态代理实现
静态代理可以由继承实现,或者是接口实现。明显接口实现要要优于继承实现。
讲一下静态代理的实现,分为委托类和被代理的类,以及代理接口。其中委托类和被代理的类都要实现代理接口。
用代码来举例:
1、单个代理
代理接口:
package Proxy;
public interface UserDao {
public void save();
}
被代理的类:
package Proxy;
public class UserDaoImpl implements UserDao{
@Override
public void save() {
System.out.println("存钱");
}
}
委托类:
委托类成员中需要被代理的类对象,所以要加上构造方法。
package Proxy;
public class Log1 implements UserDao {
private UserDao userDao;
public Log1(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
System.out.println("log1 start ...");
userDao.save();
}
}
下面进行简单的测试,
public class StaticProxy {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
Log1 log1 = new Log1(userDao);
log1.save();
// Log2 log2 = new Log2(log1);
// log2.save();
}
}
运行结果:
可以看到一个简单的代理生效了。
那怎么添加多个代理呢?很简单,一个代理增加一个类就可以了,同样也实现代理接口。
2、双代理
委托类2:
package Proxy;
public class Log2 implements UserDao {
private UserDao userDao;
public Log2(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
System.out.println("log2 start ...");
userDao.save();
}
}
将之前的代码里面的注释去掉,在将log1.save();
也注释掉,可以看到下面的运行结果:
可以看到两个代理也成功了。
二、手撸动态代理
但是如果开发中用静态代理也太蠢了,每次要增加代理都要增加相对应的类,文件会非常多。因此动态代理出现了。
跟着视频敲了手撸动态代理的代码,这个动态代理只用反射创建了一个委托对象,jdk1.7提供的是通过Class数组创建了委托对象的数组。看了下面的原理,相信会对后期框架AOP的学习很有帮助。
1、得到一个委托对象的java文件
//编写java文件
String content = "";
Class targetInfo = target.getClass().getInterfaces()[0]; //java是多实现
String targetInfoName = targetInfo.getSimpleName();
// String packageContent = "package Proxy;";
String packageContent = "";
String importContent = "import " + targetInfo.getName() + ";";
String firstContent = "public class $Proxy implements " + targetInfoName + "{";
String fieldContent = "private " + targetInfoName + " target;";
String constructorContent = "public $Proxy(" + targetInfoName + " target){" +
"this.target=target;" + "}";
Method[] methods = targetInfo.getDeclaredMethods();
String methodContent = "";
for (Method method : methods) {
String returnTypeName = method.getReturnType().getSimpleName();
String methodName = method.getName();
Class[] parameterTypes = method.getParameterTypes();
String paramsContent = "";
String argsContent = "";
int i = 0;
for (Class parameterType : parameterTypes) {
String simpleName = parameterType.getSimpleName();
argsContent += simpleName + " p" + i + ",";
paramsContent += "p" + i + ",";
i++;
}
if (argsContent.length() > 0) {
argsContent = argsContent.substring(0, argsContent.length() - 1);
paramsContent = paramsContent.substring(0, paramsContent.length() - 1);
}
methodContent += "public " + returnTypeName + " " + methodName + "(" + argsContent + "){" +
"System.out.println(\"log kkk proxy...\");" +
"target." + methodName + "(" + paramsContent + ");" +
"}";
}
content += packageContent + importContent + firstContent + fieldContent + constructorContent + methodContent + "}";
File file = new File("src/$Proxy.java");
FileWriter fw = null;
if (!file.exists()) {
try {
file.createNewFile();
fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
上面的代码就是简单的拼接一下委托对象,拼接完后结果如下图:
2、将委托对象的java文件编译程字节码文件
//jdk1.6把文件编译成class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
t.call();
try {
fileManager.close();
} catch (IOException e) {
e.printStackTrace();
}
3、通过反射创建对象(显示的用URLClassLoader)
jdk1.7封装好的是直接使用ClassLoader类加载器,将字节码文件加载到内存。而且还能一次性加载多个字节码文件。
URL url = null;
URL[] urls = null;
try {
url = file.toURI().toURL();
String temp = url.toString();
String finalPath = temp.substring(0, temp.lastIndexOf("/"));
System.out.println(finalPath);
urls = new URL[]{new URL(finalPath)};
} catch (MalformedURLException e) {
e.printStackTrace();
}
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class cla = null;
Constructor constructor = null;
Object proxy = null;
try {
//通过反射实例化对象
cla = urlClassLoader.loadClass("$Proxy"); //只加载了一个class文件
constructor = cla.getConstructor(targetInfo);
proxy = constructor.newInstance(target);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
4、返回委托对象
return proxy;
运行结果:
踩的坑:URLClassLoader这个类加载器的loadClass方法,应该是在当前url下从前往后寻找匹配的Class文件,找到就返回。
手写单层的完整代码以及测试代码:click
三、使用jdk1.7封装好的动态代理
test文件:
其中Proxy.newProxyInstance函数的参数分别是被代理对象的类加载器、被代理对象的接口、方法执行器。通过前面的手写动态代理也知道了前两个参数的意义,前两个参数就是为第三个参数创建委托对象服务的。
package Proxy;
import java.lang.reflect.Proxy;
public class TestDemoFZ {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl();
MyHandle myHandle = new MyHandle(userDao); //委托对象
UserDao userProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), myHandle);
userProxy.save(); //代理对象调用自己的方法
}
}
委托对象代码:
package Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyHandle implements InvocationHandler {
private UserDao userDao;
public MyHandle(UserDao userDao) {
this.userDao = userDao;
}
/**
*
* @param proxy 代理对象, 不要去动它
* @param method 当前要执行的被代理对象的方法
* @param args 这个方法调用时外界传入的参数值
* @return 返回的代理对象
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before log...");
method.invoke(userDao); //反射调用被代理对象的方法(方法.对象)
System.out.println("after log...");
return proxy;
}
}
运行结果:
java jdk1.7动态代理,其中方法执行器中的invoke方法返回的是代理对象,其实感觉像也就是某个接口的对象!
搞明白了动态代理,希望接下去看AOP的时候能明白一点!