当打算在现有的方法前后加上一些逻辑(比如在前后加上日志),同时又不想去更改现有的代码,就可以使用动态代理。
个人理解动态代理的实现逻辑是:
生成一份源码(FileIO),
并且编译她(JavaCompiler/StandardJavaFileManager/Iterable/CompilationTask),
加载到内存里面(URL[],URLClassLoader.loadClass()),
获取构造函数(class(具体).getConstructor,ctr.newInstance),
调用具体的方法(这个方法使用反射机制来写invoke)
就相当于打算调用class1的method1方法,在自己生成源码的时候,就需要在calss1的method1的方法里面写:
@Override
public void method1() {
try{
Method md = com.lizhao.class1.class.getMethod("method1");
h.invoke(this,md); // InvocationHandler h; InvocationHandler是自己写的实现方法,可以看下面
}catch(Exception e){
e.printStackTrace();
}
}
具体映射的时候是调用这个方法:
package com.lizhao;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
private Object object;
public TimeInvocationHandler(Object object){
this.object=object;
}
@Override
public void invoke(Object proxy, Method method) throws Exception {
System.out.println("start time: "+System.currentTimeMillis());//前逻辑
//proxy.getClass().getMethod(name, null);
method.invoke(object);//这里是使用底层的反射,后面加上参数
System.out.println("end time: "+System.currentTimeMillis());//后逻辑
}
}
下面是自己实现的动态代理类:
//代理类 Proxy.java
package com.lizhao;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import javax.tools.JavaCompiler.CompilationTask;
//代理类
public class Proxy {
public Object newProxyInstance(Class infac,InvocationHandler h) throws Exception {
String rt = "\r\n";
Method[] methods = infac.getMethods();
String strMethod = "";
//获取传入的类的所有方法
for(Method m:methods){
System.out.println(m.getName());
// strMethod+=
// " @Override " + rt
// + " public "+m.getReturnType()+" "+m.getName() + "() { " + rt
// + " System.out.println(\"start time: \"+System.currentTimeMillis()); " + rt
// + " t."+m.getName() + "(); " + rt
// + " System.out.println(\"end time: \"+System.currentTimeMillis()); " + rt
// + " } " + rt ;
strMethod+=
" @Override " + rt
+ " public "+m.getReturnType()+" "+m.getName() + "() { " + rt
+ " try{"+ rt
+ " Method md = "+infac.getName()+".class.getMethod(\""+m.getName() + "\");" + rt
+ " h.invoke(this,md); " + rt
+ " }catch(Exception e){e.printStackTrace();}"+ rt
+ " } " + rt ;
}
String src = "package com.lizhao;" + rt
+ " import java.lang.reflect.Method; " + rt
+ "public class TimeProxyN implements " + infac.getName()+"{ " + rt
+ " private InvocationHandler h ; " + rt
+ " public TimeProxyN( InvocationHandler h) { " + rt
+ " this.h = h; " + rt
+ " } "+ rt
+ strMethod +rt
+ "} "+ rt;
//String fileName = "file:/D:/studyDir/codeDir/temSrc/com/lizhao/com/lizhao/TimeProxy.java";// java文件存放的路径
String fileName = "D:/studyDir/codeDir/temSrc/com/lizhao/TimeProxyN.java";// java文件存放的路径
System.out.println(fileName);
// 写入文件
File f = new File(fileName);
//f.createNewFile();
FileWriter fw;
fw = new FileWriter(f);
fw.write(src);// 将内容写到文件里面
fw.flush();
fw.close();
// 编译文件
JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();// java的编译类
StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null, null, null);// 管理类
Iterable units = standardFileManager.getJavaFileObjects(fileName);// 将路径传入
CompilationTask task = systemJavaCompiler.getTask(null, standardFileManager, null, null, null, units);
task.call();
standardFileManager.close();
// 将类加载到内存里面
URL[] urls = new URL[] { new URL("file:/D:/studyDir/codeDir/temSrc/") };
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class c = urlClassLoader.loadClass("com.lizhao.TimeProxyN");
System.out.println(c.getName());
// 获取构造函数,里面传入的值就是代表 需要传入的类型,
// 比如可以传入 (int a,int b),就表示寻找构造构造函数Const(int a,int b)
Constructor ctr = c.getConstructor(InvocationHandler.class);
Object m = ctr.newInstance(h);
return m;
}
}
InvocationHandler.java接口
package com.lizhao;
import java.lang.reflect.Method;
public interface InvocationHandler {
public void invoke(Object proxy, Method method) throws Exception;
}
实现接口的类:
TimeInvocationHandler .java
package com.lizhao;
import java.lang.reflect.Method;
public class TimeInvocationHandler implements InvocationHandler {
private Object object;
public TimeInvocationHandler(Object object){
this.object=object;
}
@Override
public void invoke(Object proxy, Method method) throws Exception {
System.out.println("start time: "+System.currentTimeMillis());
//proxy.getClass().getMethod(name, null);
method.invoke(object);
System.out.println("end time: "+System.currentTimeMillis());
}
}
package com.lizhao;
public interface Moveable {
public void move();
}
package com.lizhao;
public class Tank implements Moveable{
@Override
public void move() {
System.out.println("Tank move start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Tank move end");
}
}
package com.lizhao.test;
import com.lizhao.Moveable;
import com.lizhao.Proxy;
import com.lizhao.Tank;
import com.lizhao.TimeInvocationHandler;
public class Main {
public static void main(String[] args) throws Exception {
Tank t = new Tank();
Moveable newProxyInstance = (Moveable) new Proxy().newProxyInstance(Moveable.class,new TimeInvocationHandler(t));
newProxyInstance.move();
}
}
String fileName = System.getProperty("user.dir")+"\\src\\com\\lizhao\\TimeProxy.java";//java文件存放的路径、
这个路径是要写文件的全名称,之前就写到了src目录,然后会报找不到文件的错误
JavaCompiler systemJavaCompiler = ToolProvider.getSystemJavaCompiler();//java的编译类
StandardJavaFileManager standardFileManager = systemJavaCompiler.getStandardFileManager(null, null, null );//管理类、
这是获取jdk给我们提供的编译类,如果你是直接使用jre的话就会报空指针错误。因为jre里面是无法获取这个类的。
解决办法:1.window--preference--java--installed jres--》右边编辑框Search,找到自己jdk的目录,也可以是上一层,比如:C:\Program Files\Java\,在这个目录下面查找,然后列表里面就会出现jdk。
2.项目目录上面右键--properties--Java Build Path--remove原来的jre,点击右边的 Add Library,点击第二个Aleernate Jre,这时候就可以选择jdk了
PS:这个地方之前在网上找原因说是jdk版本的问题,然后我去重新下载了个最新的,从1.8.5到1.8.112。中间因为先下载了32位的jdk,然后配置好环境变量后,打开eclipse就报错"exit code=13",是因为jdk版本位数和eclipse版本位数不匹配导致的,又重新下回来了、。。
根据视频 马士兵---【设计模式--动态代理】
虽然原理是这样,但是在我们具体用的时候就不会这个麻烦的,下面就拿SSH框架中的DAO层举个例子:
我们知道在mvc框架里面程序的运行方式是这样的:Client--》Action--》Service--》Dao--model
当我们想要在Dao层加入一些自己的逻辑,同时又不想去改变DAO原来的代码,就像下面这么写:
test.java
package com.bjsxt.service;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.net.URLClassLoader;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.bjsxt.dao.UserDAO;
import com.bjsxt.invoke.LogInvocationHandler;
import com.bjsxt.model.User;
public class UserServiceTest {
@Test
public void test() throws Exception {
//使用了spring,和动态代理关系不大,这个就相当于读取配置文件
BeanFactory beanFaction = new ClassPathXmlApplicationContext("beans.xml");
//第一个参数ClassLoader loader
URL[] urls = new URL[] { new URL("file:/"+System.getProperty("user.dir")+"/src/") };
URLClassLoader urlClassLoader = new URLClassLoader(urls);
//第二个参数 Class>[] interfaces,
Class classes[] = new Class[] {UserDAO.class};
//第三个参数InvocationHandler h
UserService userService = (UserService) beanFaction.getBean("userService");
LogInvocationHandler logInvocationHandler = new LogInvocationHandler(userService.getUserDAO());
//获取代理对象
UserDAO newProxyInstance = (UserDAO) Proxy.newProxyInstance(urlClassLoader ,classes , logInvocationHandler);
userService.setUserDAO(newProxyInstance);
userService.add();
}
}
一些我认为比较重要的地方做了注释
相关的类我也贴出来:
UserDAO.class
package com.bjsxt.dao;
public abstract interface UserDAO {
public void sava();
}
package com.bjsxt.dao.impl;
import com.bjsxt.dao.UserDAO;
public class UserMySqlDao implements UserDAO{
private String daoName;
public String getDaoName() {
return daoName;
}
public void setDaoName(String daoName) {
this.daoName = daoName;
}
public void sava(){
System.out.println("UserMySqlDao:sava()");
}
@Override
public String toString() {
return "daoName:"+daoName;
}
public void init(){
System.out.println("init UserMySqlDao");
}
}
LogInvocationHandler.java
package com.bjsxt.invoke;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LogInvocationHandler implements InvocationHandler {
private Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("LogInvocationHandler: log printing");
method.invoke(target);
System.out.println("LogInvocationHandler: log end");
return null;
}
}
UserService.java
package com.bjsxt.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.bjsxt.dao.UserDAO;
import com.bjsxt.model.User;
public class UserService {
UserDAO userDAO;
public UserService(){
}
public UserDAO getUserDAO() {
return userDAO;
}
@Autowired
public void setUserDAO(@Qualifier("UserMySql2")UserDAO userDAO) {
this.userDAO = userDAO;
}
public void add(User user){
this.userDAO.sava();
}
public void add(){
this.userDAO.sava();
}
public void init(){
System.out.println("init UserService");
}
public void destroy(){
System.out.println("desrory UserService");
}
}
init UserMySqlDao
init UserService
LogInvocationHandler: log printing
UserMySqlDao:sava()
LogInvocationHandler: log end