AOP动态代理技术详解
1. 什么是代理?
联想生活着的代理: 不用亲自跑去北京联想总部去买联想电脑,在本地联想代理就可以买,虽然贵了300块,但省了很多功夫和路费
且 代理 可以在 出售前后可以自己搞小动作:提高售价,搞销售活动,赠品自取等。
程序中代理:
给你一个类,没有源码,你如何在它的功能上增加功能?
要完成这件事(不修改源码):
首先这个类必须实现了一个接口;
并且在主函数中 是通过读取配置文件获得类的对象,且使用接口 变量名 来接收。
这样就可以实现不修改源码添加功能了:
我们创建一个 代理类 也实现同样的接口,然后重写 类中需要修改的方法:
method() { addmethod(); x.method(); addmethod2();}
最后,在配置文件中把 目标类 换成 代理类。这样就可以实现不修改源码添加功能啦!
总结一下: 要是想让我们的程序可以支持 动态代理,我们设计程序时也要遵守上面的规则: 面向接口编程 和 读取配置文件获取对象。
其实也不用非得实现接口,没有接口的我们造一个子类,重写父类方法添加功能。
2. 什么是AOP?
Aspect Oriented program 面向切面编程。
什么是交叉业务?
本质就是重复代码。它就是在各个方法中都会使用到的业务代码。如 事务,安全,日志等功能。
交叉业务的编程问题极为面向方面编程(AOP),AOP的目标就是要是交叉业务模块化,将 切面代码 移动到原始方法的周围,这与直接在方法中编写切面代码运行效果是一样的。
method1 { method2 { method3 {
------------------------------------------切面
-------------------------------------------切面
} } }
------------------------------------------切面
method1 { method2 { method3 {
} } }
-------------------------------------------切面
3. 动态代理技术
要为系统中的各种 接口的类 增加代理功能,那就需要写很多的代理类,这种静态代理的方式,非常麻烦。于是引出了动态代理类。
动态代理类分两种
· 有接口(jdk动态代理:动态生成代理类)
· 没有接口(cglib动态代理:动态生成子类)
4. JVM动态代理技术
import java.lang.reflect.Proxy;
Class clazz = Proxy.getProxyClass(ClassLoader ,Class interface);
//Constructor[] cons = clazz.getConstaructor();
Constractor cons = clazz.getConstructor(InvocationHandler.class);
cons.newInstance();
原来获取一个动态类 需要:
·先获取 动态类字节码 对象(类加载器和实现的接口)
·通过构造器获取 动态类对象(实现InvocationHandler接口的类)
合二为一:
Foo f = (Foo) Proxy.newProxyInstance(
Foo.class.getClassLoader(),
new Class[] {Foo.class},
new InvocationHandler() {
public Object invoke(Object proxy,Method method,Object[] args)
throw Throwable{
//method.invoke(target,args);
return null;
}
}
);
举个例子:JVM生成的Collection代理类
Class Proxy implements Collection{
private InvocationHandler handler;
public Porxy(InvocationHandler handler) {
this.handler = handler;
}
public void add(){
handler.invoke(Proxy,add,args[]);
}
public void size(){
handler.invoke(Proxy,size,args[]);
}
...
}
问题: 为什么syso(proxy.toString());不报错?而proxy1.size();报空指针异常呢?
因为其实无论调用任何方法,其实都是调用 InvocationHandler接口中的 invoke()方法,而我们没有填写东西导致返回null,syso(null)还好,size()方法返回的是int,null转换int就会出错:空指针异常。
补充: 对于Object的方法,只有hashCode,equals和toString三个方法交给 InvocationHandler实现,其它的都没有,所以其他方法 插入的代码 无效。
AOP实现原理(注释相关的代码都封装起来就OK):
Collection proxy =(Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler() {
//这里写死没有任何意义,所以AOP会将目标类当做参数//传进去
ArrayList target = new ArrayList();
public Object invoke(Object proxy,Method method,Object args[]){
//这些就属于 交叉业务代码了,写死也没有意义,
//处理方法同样是传对象,传入log4j对象调用log4j
//的日志方法
long begin=System.currentTimeMillis();
Object retVal=method.invoke(target,args);
long end=System.currentTimeMillis();
syso(method.getName()+(end-begin));
return retVal;
}
}
);
而eclipse重构出一个getProxy方法绑定接收目标并返回代理对象,让调用者更懒惰,更方便,调用甚至都不用接触任何Java的代理相关API。
下面我们自己来封装成一个简单生成代理的框架:
public static Object getProxy(final Object target,final Advice advice) {
Object proxy=Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
advice.beforeMethod(method);
Object retVal=method.invoke(target,args);
advice.afterMethod(method);
return retVal;
}
}
);
return proxy;
}
pubnlic interface Advice{
void beforeMethod();
void afterMethod();
}
//用户调用
Collection c=getProxy(new ArrayList(),new MyAdvice());
5. 实现类似spring的可配置的AOP框架!
· 代理工厂类(封装好 产生代理类 的函数)
public class ProxyFactoryBean {
private Advice advice;
private Object target;
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public static Object getProxy(Object target, Advice advice) {
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.beforeMethod(method);
//调用 目标类 的原方法
Object result = method.invoke(target, args);
advice.afterMethod(method);
return result;
}
});
return proxy;
}
}
· Advice接口
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}
· BeanFactory(负责 通过加载 配置文件 产生代理Bean)
public class FactoryBean {
Properties prop = new Properties();
public FactoryBean(InputStream ips) {
try {
prop.load(ips);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Object getBean(String name) {
String className = prop.getProperty(name);
Object bean = null;
try {
Class clazz = Class.forName(className);
bean = clazz.newInstance();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(bean instanceof ProxyFactoryBean) {
Object proxy = null;
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
try {
Advice advice =(Advice) Class.forName(prop.getProperty(name+".advice")).newInstance();
Object target = Class.forName(prop.getProperty(name+".target")).newInstance();
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
proxy = ProxyFactoryBean.getProxy(target, advice);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return proxy;
}
return bean;
}
}
· config.properties 配置文件,想用代理配置一下就好
#想用哪个 取消注释哪个
#xxx=java.util.ArrayList
#xxx=gc.aop.bean.ProxyFactoryBean
xxx.advice=gc.aop.advice.MyAdvice
xxx.target=java.util.ArrayList
· 编写MyAdvice 添加自己想加入的功能
public class MyAdvice implements Advice{
@Override
public void beforeMethod(Method method) {
// TODO Auto-generated method stub
System.out.println("切面编程,方法前 执行");
}
@Override
public void afterMethod(Method method) {
// TODO Auto-generated method stub
System.out.println("切面编程,方法后 执行");
}
}
· 测试
public class AopFramworkTest {
public static void main(String[] args) {
InputStream ips = AopFramworkTest.class.getResourceAsStream("config.properties");
FactoryBean fb = new FactoryBean(ips);
Object bean = fb.getBean("xxx");
System.out.println(bean.getClass().getName());
((Collection)bean).clear();
}
}
1. 什么是代理?
联想生活着的代理: 不用亲自跑去北京联想总部去买联想电脑,在本地联想代理就可以买,虽然贵了300块,但省了很多功夫和路费
且 代理 可以在 出售前后可以自己搞小动作:提高售价,搞销售活动,赠品自取等。
程序中代理:
给你一个类,没有源码,你如何在它的功能上增加功能?
要完成这件事(不修改源码):
首先这个类必须实现了一个接口;
并且在主函数中 是通过读取配置文件获得类的对象,且使用接口 变量名 来接收。
这样就可以实现不修改源码添加功能了:
我们创建一个 代理类 也实现同样的接口,然后重写 类中需要修改的方法:
method() { addmethod(); x.method(); addmethod2();}
最后,在配置文件中把 目标类 换成 代理类。这样就可以实现不修改源码添加功能啦!
总结一下: 要是想让我们的程序可以支持 动态代理,我们设计程序时也要遵守上面的规则: 面向接口编程 和 读取配置文件获取对象。
其实也不用非得实现接口,没有接口的我们造一个子类,重写父类方法添加功能。
2. 什么是AOP?
Aspect Oriented program 面向切面编程。
什么是交叉业务?
本质就是重复代码。它就是在各个方法中都会使用到的业务代码。如 事务,安全,日志等功能。
交叉业务的编程问题极为面向方面编程(AOP),AOP的目标就是要是交叉业务模块化,将 切面代码 移动到原始方法的周围,这与直接在方法中编写切面代码运行效果是一样的。
method1 { method2 { method3 {
------------------------------------------切面
-------------------------------------------切面
} } }
------------------------------------------切面
method1 { method2 { method3 {
} } }
-------------------------------------------切面
3. 动态代理技术
要为系统中的各种 接口的类 增加代理功能,那就需要写很多的代理类,这种静态代理的方式,非常麻烦。于是引出了动态代理类。
动态代理类分两种
· 有接口(jdk动态代理:动态生成代理类)
· 没有接口(cglib动态代理:动态生成子类)
4. JVM动态代理技术
import java.lang.reflect.Proxy;
Class clazz = Proxy.getProxyClass(ClassLoader ,Class interface);
//Constructor[] cons = clazz.getConstaructor();
Constractor cons = clazz.getConstructor(InvocationHandler.class);
cons.newInstance();
原来获取一个动态类 需要:
·先获取 动态类字节码 对象(类加载器和实现的接口)
·通过构造器获取 动态类对象(实现InvocationHandler接口的类)
合二为一:
Foo f = (Foo) Proxy.newProxyInstance(
Foo.class.getClassLoader(),
new Class[] {Foo.class},
new InvocationHandler() {
public Object invoke(Object proxy,Method method,Object[] args)
throw Throwable{
//method.invoke(target,args);
return null;
}
}
);
举个例子:JVM生成的Collection代理类
Class Proxy implements Collection{
private InvocationHandler handler;
public Porxy(InvocationHandler handler) {
this.handler = handler;
}
public void add(){
handler.invoke(Proxy,add,args[]);
}
public void size(){
handler.invoke(Proxy,size,args[]);
}
...
}
问题: 为什么syso(proxy.toString());不报错?而proxy1.size();报空指针异常呢?
因为其实无论调用任何方法,其实都是调用 InvocationHandler接口中的 invoke()方法,而我们没有填写东西导致返回null,syso(null)还好,size()方法返回的是int,null转换int就会出错:空指针异常。
补充: 对于Object的方法,只有hashCode,equals和toString三个方法交给 InvocationHandler实现,其它的都没有,所以其他方法 插入的代码 无效。
AOP实现原理(注释相关的代码都封装起来就OK):
Collection proxy =(Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),
new Class[]{Collection.class},
new InvocationHandler() {
//这里写死没有任何意义,所以AOP会将目标类当做参数//传进去
ArrayList target = new ArrayList();
public Object invoke(Object proxy,Method method,Object args[]){
//这些就属于 交叉业务代码了,写死也没有意义,
//处理方法同样是传对象,传入log4j对象调用log4j
//的日志方法
long begin=System.currentTimeMillis();
Object retVal=method.invoke(target,args);
long end=System.currentTimeMillis();
syso(method.getName()+(end-begin));
return retVal;
}
}
);
而eclipse重构出一个getProxy方法绑定接收目标并返回代理对象,让调用者更懒惰,更方便,调用甚至都不用接触任何Java的代理相关API。
下面我们自己来封装成一个简单生成代理的框架:
public static Object getProxy(final Object target,final Advice advice) {
Object proxy=Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler(){
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable{
advice.beforeMethod(method);
Object retVal=method.invoke(target,args);
advice.afterMethod(method);
return retVal;
}
}
);
return proxy;
}
pubnlic interface Advice{
void beforeMethod();
void afterMethod();
}
//用户调用
Collection c=getProxy(new ArrayList(),new MyAdvice());
5. 实现类似spring的可配置的AOP框架!
· 代理工厂类(封装好 产生代理类 的函数)
public class ProxyFactoryBean {
private Advice advice;
private Object target;
public Advice getAdvice() {
return advice;
}
public void setAdvice(Advice advice) {
this.advice = advice;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public static Object getProxy(Object target, Advice advice) {
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
advice.beforeMethod(method);
//调用 目标类 的原方法
Object result = method.invoke(target, args);
advice.afterMethod(method);
return result;
}
});
return proxy;
}
}
· Advice接口
public interface Advice {
void beforeMethod(Method method);
void afterMethod(Method method);
}
· BeanFactory(负责 通过加载 配置文件 产生代理Bean)
public class FactoryBean {
Properties prop = new Properties();
public FactoryBean(InputStream ips) {
try {
prop.load(ips);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public Object getBean(String name) {
String className = prop.getProperty(name);
Object bean = null;
try {
Class clazz = Class.forName(className);
bean = clazz.newInstance();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(bean instanceof ProxyFactoryBean) {
Object proxy = null;
ProxyFactoryBean proxyFactoryBean = (ProxyFactoryBean)bean;
try {
Advice advice =(Advice) Class.forName(prop.getProperty(name+".advice")).newInstance();
Object target = Class.forName(prop.getProperty(name+".target")).newInstance();
proxyFactoryBean.setAdvice(advice);
proxyFactoryBean.setTarget(target);
proxy = ProxyFactoryBean.getProxy(target, advice);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return proxy;
}
return bean;
}
}
· config.properties 配置文件,想用代理配置一下就好
#想用哪个 取消注释哪个
#xxx=java.util.ArrayList
#xxx=gc.aop.bean.ProxyFactoryBean
xxx.advice=gc.aop.advice.MyAdvice
xxx.target=java.util.ArrayList
· 编写MyAdvice 添加自己想加入的功能
public class MyAdvice implements Advice{
@Override
public void beforeMethod(Method method) {
// TODO Auto-generated method stub
System.out.println("切面编程,方法前 执行");
}
@Override
public void afterMethod(Method method) {
// TODO Auto-generated method stub
System.out.println("切面编程,方法后 执行");
}
}
· 测试
public class AopFramworkTest {
public static void main(String[] args) {
InputStream ips = AopFramworkTest.class.getResourceAsStream("config.properties");
FactoryBean fb = new FactoryBean(ips);
Object bean = fb.getBean("xxx");
System.out.println(bean.getClass().getName());
((Collection)bean).clear();
}
}