刚接触Java的时候,学长学姐带着学。那个时候有个学长说,其实他很想学java反射机制的。
那个时候听他说,反射机制能自动帮用户生成文件等。那个时候我听了感觉特牛逼,图样图森破啊
这个学期,导师(工头)催的不紧,然后就自己去看一些动态代理的视频,代理模式等。
看了之后,自己也写了一些程序,模拟jdk的动态代理。
lz个人觉得会用动态代理,实现InvocationHandler接口,并重写其invoke方法,加入自己的业务逻辑
调用的时候,interface i = (interface) Proxy.newInstance(loadclass,interfaces,invocationhandler)
jdk的这个方法是返回一个代理类,这个代理类同样的实现的interface这个接口
loadclass lz觉得传进去的参数,是供jdk生成该类的二进制class文件时,直接load进程序时用到的。
lz的猜想:这个loadclass生成的最终的二进制class文件,是已经修改了其里面的内容的。下面lz会解释为什么会这样想
interfaces 注意这个s,是传进去这个代理类需要实现的所有接口类class数组
invocationhandler 当每执行一个方法的时候都调用invocationhandler里我们想要他加入的业务逻辑,因为我们重写了InvocationHandler接口的invoke方法
下面是个简单的例子:
package Interface;//接口包
public interface TestInterface {
public void print();
}
package BLL;//接口实现包
import Interface.TestInterface;
public class TestImpl implements TestInterface{
@Override
public void print() {
System.out.println("你好呀");
}
}
package Proxy;//加入自己的业务逻辑的InvocationHandler类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LoggerProxy implements InvocationHandler{
private Object object = null;
public LoggerProxy(Object object)
{
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object tmpObject = null;
tmpObject = method.invoke(object, args);//object:调用这个方法的实现类,args:传入的参数,若无则null
return tmpObject;
}
}
package test;//测试类
import java.lang.reflect.Proxy;
import org.omg.CORBA.PUBLIC_MEMBER;
import BLL.TestImpl;
import Interface.TestInterface;
import Proxy.LoggerProxy;
public class Test{
public static void main(String[] args) {
TestInterface impl = new TestImpl();//声明TestImpl对象
LoggerProxy proxy = new LoggerProxy(impl);//声明LoggerProxy对象并传入,方法执行实体,ps:invoke(obj,args)
TestInterface api = (TestInterface) Proxy.newProxyInstance(impl.getClass().getClassLoader(), impl.getClass().getInterfaces(), proxy);
api.print();
}
}
这时候我修改实现InvocationHandler接口的类的invoke方法,加入我自己的业务逻辑
我要实现每执行一个方法,都写入日志,包括错误
贴上Porxy包里的代码:
package Proxy;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class LoggerProxy implements InvocationHandler{
private Object object = null;
private boolean sign = false;
private BufferedWriter bw = null;
public LoggerProxy(Object object)
{
this.object = object;
}
private void openStream()
{
if (!sign) {
try {
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("./log.txt")));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
sign = true;
}else {
try {
bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(".\\log.txt",true)));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
this.openStream();
bw.newLine();
bw.write(object.getClass().getName() + " has start method:" + method.getName());
Object tmpObject = null;
try {
tmpObject = method.invoke(object, args);
} catch (Exception e) {
bw.write("\n\t"+e.toString());
for(int i = 0 ; i < e.getStackTrace().length ; i ++)
{
bw.write("\n\t\t\tMethod : "+e.getStackTrace()[i].getMethodName()+
"("+e.getStackTrace()[i].getFileName()+":" + e.getStackTrace()[i].getLineNumber()+")");
}
}
bw.newLine();
bw.write(object.getClass().getName() + " has end method:" + method.getName());
bw.close();
return tmpObject;
}
}
理解了这个过程,就不难理解spring的aop的思想了。
spring已经封装的很好了,真的要会用的话,看一些视频不难学会,比较容易上手。但要在项目中活用的,还得见仁见智。
基本就是想在哪加入业务逻辑就在哪加入!
我现在自己写的,有个前提,基于接口编程。
看这个方法传入的参数 Proxy.newInstance(classloader,interfaces,invocationhandler)
没错,一定是要实现至少一个接口的。继承这种是不行的。
但是!在spring的项目里,你的类不一定要有接口!
如果有,则会调用jdk的Proxy和InvocationHandler帮你实现代理
如果没有,spring则调用cglib直接生成二进制码的方式实现代理
俗话说:知其然更要知其所以然。
jdk里具体是怎么实现的呢?不多说,贴上代码
package cn.Interface;//接口包
public interface FTD {
public void show_Dream(Integer a);
}
package cn.Impl;//实现包
import cn.Interface.FTD;
public class FTDImpl implements FTD{
@Override
public void show_Dream(Integer a) {
System.out.println("This is my Dream!");
}
}
package cn.Invocation;//Invocation包 包括接口跟实现类
import java.lang.reflect.Method;
public interface MyInvocationHandlerInterface {//接口
public Object invoke(Object object , Method m , Object[] args);
}
package cn.Invocation;
import java.lang.reflect.Method;
public class MyInvocationHandler implements MyInvocationHandlerInterface{//实现类
private Object target = null;
public MyInvocationHandler(Object obj){
target = obj;
}
@Override
public Object invoke(Object object, Method m, Object[] args) {
try {
m.invoke(target, args);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
重头戏来了!
package cn.Test;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
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;
import cn.Interface.FTD;
import cn.Invocation.MyInvocationHandler;
import cn.Invocation.MyInvocationHandlerInterface;
public class Proxy {
public static Object newProxyIntance(Class clz , MyInvocationHandlerInterface h) throws Exception{
String rt = "\r\n";//windows下/r/n是换行回车,一般\n也可以。ps:windows读取文件的时候会自动加上\r
String methodStr = "";//方法存储字符串
Method[] methods = clz.getMethods();
for(Method method : methods)
{
Class<?>[] types = method.getParameterTypes();
String typestr = "";//方法的形参和形参类字符串 例如:java.lang.Integer arg0
String paraStr = "";//方法的形参名 例如:arg0
String paraclassStr = "";//方法的形参类名 例如:Integer.class
int i = 0;
for(Class c : types)
{
if(types.length != (i + 1)){
typestr += c.getName() + " arg"+i+", ";
paraStr += "arg"+i+",";
paraclassStr += ","+c.getName()+".class";
}
else{
typestr += c.getName()+ " arg"+i;
paraStr += "arg"+i;
paraclassStr += ","+c.getName()+".class";
}
i++;
}
System.out.println(paraStr);
try {
System.out.println(method.getReturnType());
} catch (Exception e) {
e.printStackTrace();
}
methodStr += "@Override" +rt+
" public "+ method.getReturnType() + " "+method.getName() +"(" + typestr + "){"+rt+
" try{" +rt+
" Method md = "+ clz.getName()+".class.getMethod(\""+ method.getName() +"\""+paraclassStr+");"+rt+
" Object[] objs = {"+paraStr+"};"+rt+
" h.invoke(this,md,objs);" +rt+
" }catch (NoSuchMethodException e){" +rt+
" e.printStackTrace();" +rt+
" }"+rt+
" }";
}
String classStr =
"import java.lang.reflect.Method;"+rt+
"public class TmpTest implements " + clz.getName() + "{"+rt+
" cn.Invocation.MyInvocationHandler h;"+rt+
" public TmpTest(cn.Invocation.MyInvocationHandler h){"+rt+
" this.h = h;"+rt+
" }"+rt+
" "+methodStr+rt+
"}";
String fileName = "f:/TmpFile/TmpTest.java";
File file = new File(fileName);
FileWriter fw = new FileWriter(file);
fw.write(classStr);
fw.flush();
fw.close();
//拿到java的编译器,我MyEclipse 10自带的jdk不能实现,需要导入外部的jdk。我用的是jdk7
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
//第一个参数 监听器:编译错误的话由他收集;第二个:第三个:国际化相关的,用的什么语言,null就是默认的 ,默认GBK;
//iterable 简单理解成数组
Iterable units = fileMgr.getJavaFileObjects(fileName);
CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
//load into memory and create an instance
//把硬盘里的弄到内存里 ,ps:可以从网上load类
URL[] urls = new URL[]{new URL("file:/f:/TmpFile/")};
URLClassLoader ul = new URLClassLoader(urls);
Class c = ul.loadClass("TmpTest");//把生成的.class文件load进程序
System.out.println(c);
Constructor ctr = c.getConstructor(MyInvocationHandler.class);//返回该类的形参为MyInvocationHandler类对象的构造函数
Object obj = ctr.newInstance(h);//实例化
return obj;
}
}
下面是测试类
package cn.Test;
import cn.Impl.FTDImpl;
import cn.Interface.FTD;
import cn.Invocation.MyInvocationHandler;
public class Test {
public static void main(String[] args) throws Exception {
FTDImpl ftdImpl = new FTDImpl();
MyInvocationHandler h = new MyInvocationHandler(ftdImpl);
FTD ftd = (FTD) Proxy.newProxyIntance(FTD.class, h);
ftd.show_Dream(1);
}
}
再把生成的代理文件,即 .java文件,贴上来
import java.lang.reflect.Method;
public class TmpTest implements cn.Interface.FTD{
cn.Invocation.MyInvocationHandler h;
public TmpTest(cn.Invocation.MyInvocationHandler h){
this.h = h;
}
@Override
public void show_Dream(java.lang.Integer arg0){
try{
Method md = cn.Interface.FTD.class.getMethod("show_Dream",java.lang.Integer.class);
Object[] objs = {arg0};
h.invoke(this,md,objs);
}catch (NoSuchMethodException e){
e.printStackTrace();
}
}
}
这里我来回答下之前的猜想
lz实现的Proxy.newInstance方法里只需要传入两个参数,就能够实现代理
但是jdk需要传入三个参数
jdk还需要传入classloader
在lz实现里,用URL把生成的.class文件地址传入,然后load进代理类。
如果他这么做,那就只要两个参数就行了,不需要再传入classloader(当然,可能jdk还有其他需要用到classloader的地方,请知道的大牛们点拨)
所以lz的猜想是:jdk在生成classloader的class文件的时候就已经把代理的方法也放进去了。
jdk的Proxy.newInstance方法返回的其实就是classloader生成的class文件的对象,只不过是已经修改过的。