最近在看Spring这块,对AOP的代理有些疑惑,特整理如下。
1、*Java为什么要引入AOP代理的概念?*
AOP(面向切面编程)通常,系统由很多组件组成,每个组件负责一部分功能,然而,这些组件也经常带有一些除了核心功能之外的附带功能 。系统服务如日志、事务管理和安全经常融入到一些其他功能模块中。这些系统服务通常叫做交叉业务,这是因为它们总是分布在系统的很多组件中。通过将这些业务分布在多个组件中,给我们的代码引入了双重复性。
(1) 实现系统级业务的代码在多个组件中复制。这意味着如果你要改变这些业务逻辑,你就必须到各个模块去修改。就算把这些业务抽象成一个独立模块,其它模块只是调用它的一个方法,但是这个方法调用也还是分布在很多地方。
(2) 组件会因为那些与自己核心业务无关的代码变得杂乱。一个向地址录中添加条目的方法应该只关心如何添加地址,而不是关心它是不是安全或支持事务的。此时,我们该怎么办呢?这正是AOP用得着的地方。AOP帮助我们将这些服务模块化,并把它们声明式地应用在需要它们的地方,使得这些组件更加专注于自身业务,完全不知道其它涉及到的系统服务。 这里的概念切面,就是我们要实现的交叉功能,是应用系统模块化的一个方面或领域。切面的最常见例子就是日志记录。日志记录在系统中到处需要用到,利用继承来重用日志模块是不合适的,这样,就可以创建一个日志记录切面,并且使用AOP在系统中应用。下图展示了切面应用方式 :
其中,通知Advice是切面的实际实现。连接点Joinpoint是应用程序执行过程中插入切面的地点,这个地点可以是方法调用,异常抛出,甚至可以是要修改的字段,切面代码在这些地方插入到你的应用流程中,添加新的行为。切入点Pointcut定义了Advice应该应用在那些连接点,通常通过指定类名和方法名,或者匹配类名和方法名式样的正则表达式来指定切入点[3]。
AOP的主要功能是日志记录、性能统计、安全控制、事务处理、异常处理等。主要的意图是将日志记录、性能统计、安全控制、事务处理、异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,希望可以将他们独立到非知道业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。SpringAOP是在一个服务的流程中,插入与改服务的业务逻辑无关的系统服务逻辑,如日志、安全等。这样的逻辑称为横切关注点,将横切关注点独立出来设计为一个对象,这样的特殊对象称为切面。这样的服务逻辑可应用到服务程序的很多地方。在AOP中,他们只需要编写一次,就可以将其自动在整个目标应用程序中实施。AOP重点关注切面的设计及他们在目标应用中的植入。
2、AOP在Spring中如何实现?
基于AOP,业界存在各种各样的AOP实现,比如,JBoss AOP、Spring AOP、AspectJ、Aspect Werkz等。各自实现的功能也不一样。AOP实现的强弱在很大程度上取决于连接点模型。目前,Spring只支持方法级的连接点。这和一些其他AOP框架不一样,如AspectJ和JBoss,它们还提供了属性接入点,这样可以防止你创建特别细致的通知,如对更新对象属性值进行拦截。然而,由于Spring关注于提供一个实现J2EE服务的框架,所以方法拦截可以满足大部分要求,而且Spring的观点是属性拦截破坏了封装,让Advice触发在属性值改变而不是方法调用上无疑是破坏了这个概念。
Spring的AOP框架的关键点如下:
(1)Spring实现了AOP联盟接口。在Spring AOP中,存在如下几种通知(Advice)类型[2]
Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice;
Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.MethodBeforeAdvice;
Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。
(2)用java编写Spring通知,并在Spring的配置文件中,定义在什么地方应用通知的切入点。
(3)Spring的运行时通知对象。 代理Bean只有在第一次被应用系统需要的时候才被创建。如果你使用的是ApplicationContext,代理对象在BeanFactory载入所有Bean的时候被创建。Spring有两种代理创建方式。如果目标对象实现了一个或多个接口暴露的方法,Spring将使用JDK的java.lang.reflect.Proxy类创建代理。这个类让Spring动态产生一个新的类,它实现所需的接口,织入了通知,并且代理对目标对象的所有请求。如果目标对象没有实现任何接口,Spring使用CGLIB库生成目标对象的子类。在创建这个子类的时候,Spring将通知织入,并且将对目标对象的调用委托给这个子类。此时,需要将Spring发行包lib/cglib目录下的JAR文件发布到应用系统中。
3、AOP静态代理与动态代理的异同有哪些,及动态代理的优势?
静态代理:这个比较容易理解。由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:JDK代理,CGLIB动态代理。动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。 但是,JDK的动态代理依靠接口实现,如果有些类并没有实现接口,则不能使用JDK代理(通过接口创建代理),这就要使用CGLIB动态代理(通过子类创建代理)了。
先来看静态代理模式代码:
package test;
public interface Subject
{
public void doSomething();
}
package test;
public class RealSubject implements Subject
{
public void doSomething()
{
System.out.println( "call doSomething()" );
}
}
package test;
public class SubjectProxy implements Subject
{
Subject subimpl = new RealSubject();
public void doSomething()
{
subimpl.doSomething();
}
}
package test;
public class TestProxy
{
public static void main(String args[])
{
Subject sub = new SubjectProxy();
sub.doSomething();
}
}
刚开始我会觉得SubjectProxy定义出来纯属多余,直接实例化实现类完成操作不就结了吗?后来随着业务庞大,你就会知道,实现proxy类对真实类的封装对于粒度的控制有着重要的意义。但是静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。另外,观察代码可以发现每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能, 所以引入动态代理来解决此类问题。
请看动态代理代码:
package test;
public interface Subject
{
public void doSomething();
}
package test;
public class RealSubject implements Subject
{
public void doSomething()
{
System.out.println( "call doSomething()" );
}
}
package test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyHandler implements InvocationHandler
{
private Object tar;
//绑定委托对象,并返回代理类
public Object bind(Object tar)
{
this.tar = tar;
//绑定该类实现的所有接口,取得代理类
return Proxy.newProxyInstance(tar.getClass().getClassLoader(),
tar.getClass().getInterfaces(),
this);
}
public Object invoke(Object proxy , Method method , Object[] args)throws Throwable
{
Object result = null;
//这里就可以进行所谓的AOP编程了
//在调用具体函数方法前,执行功能处理
result = method.invoke(tar,args);
//在调用具体函数方法后,执行功能处理
return result;
}
}
public class TestProxy
{
public static void main(String args[])
{
ProxyHandler proxy = new ProxyHandler();
//绑定该类实现的所有接口
Subject sub = (Subject) proxy.bind(new RealSubject());
sub.doSomething();
}
}
看完代码,动态代理的作用很明显:
Proxy类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;
可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用;
解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变。
下面在用一个实例对比看下动态代理优势。
这个代码相信大家在熟悉不过了:
package com.me.services.imp;
import com.me.entity.User;
import com.me.services.UserService;
import com.me.daos.UserDAO;
import com.me.util.TransationManager;
import com.me.util.PerformanceMonitor;
public class UserServiceImp implements UserService {
private TransationManager trans = new TransationManager();
private PerformanceMonitor pmonitor = new PerformanceMonitor();
private UserDAO userDao = new UserDAO();
public void deleteUser(int userId) {
**<span><span style="color: #000000;"><span>pmonitor.start("com.me.services.imp.UserServiceImp.deleteUser");//1</span>
<span>trans.beginTransation();//2</span></span></span>**
userDao.deleteUser(userId);
<span><span style="color: #000000;"><span>trans.commitTransation();</span>
<span>pmonitor.end();</span></span></span>
}
public void insertUser(User user) {
<span style="color: #000000;">pmonitor.start("com.me.services.imp.UserServiceImp.insertUser");//1
trans.beginTransation();//2</span>
userDao.inserUser(user);
<span style="color: #000000;">trans.commitTransation();
pmonitor.end();</span>
}
}
这段代码就是用于监控添加用户和删除用户的性能,除了业务逻辑外,大量的事务处理和性能监控重复代码(1、2代码),
那能不能也抽象一个父类,当然不行,因为继承是纵向抽取机制,这里就是用到AOP横向切割的方式抽取一个独立的模块。
下面来看一段代码:
将代码一处1、2代码部分去掉
public void deleteUser(int userId) {
//pmonitor.start("com.me.services.imp.UserServiceImp.deleteUser");
//trans.beginTransation();
userDao.deleteUser(userId);
//trans.commitTransation();
//pmonitor.end();
}
public void insertUser(User user) {
//pmonitor.start("com.me.services.imp.UserServiceImp.insertUser");
//trans.beginTransation();
userDao.inserUser(user);
//trans.commitTransation();
//pmonitor.end();
}
JDK动态代理代码:
package com.me.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class PerformanceHandler implements InvocationHandler {
private Object target;
public PerformanceHandler(Object target){
this.target = target;
}
/**
* @param proxy 最终生成的代理实例,一般不会用到
* @param method 业务代理中某个具体方法,发起目标实例方法的反射调用
* @param args 业务代理中方法的参数,在方法反射时调用
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//自定义的一个性能监控类
PerformanceMonitor pMonitor = new PerformanceMonitor();
//传入一个性能监控的方法的全限名,即包名+类名+方法名
pMonitor.start(target.getClass().getName()+"."+method.getName());
//通过反射方法调用业务类的目标业务,返回一个业务代理
Object obj = method.invoke(target,args);
pMonitor.end();
return obj;
}
}
编写测试类:
package com.me.test;
import com.me.entity.User;
import com.me.services.UserService;
import com.me.services.imp.UserServiceImp;
import com.me.util.PerformanceHandler;
import java.lang.reflect.Proxy;
public class TestUserService {
public static void main(String[] args) {
//通过JDK创建代理类实例
UserService target = new UserServiceImp();//希望被代理的业务类
PerformanceHandler handler = new PerformanceHandler(target);
//根据业务逻辑类和实现InvocationHandler接口性能横切逻辑创建代理实例
UserService proxy = (UserService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
//参数一:类加载器 参数二:创建代理实例需要的接口 参数三:业务逻辑与横切逻辑编程器对象
proxy.deleteUser(1);
User user = new User();
proxy.insertUser(user);
}
我们知道JDK动态代理是通过接口定义业务方法类的,那么不用接口定义业务类,我们就要用到CGLib动态创建代理了。
CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截父类方法的调用,并顺势织入横切逻辑。下面来看段代码:
package com.me.util;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
//这里引入一个enhancer,查了一下,它的原理就是用Enhancer生成一个原有类的子类,并且设置好callback到proxy, 则原有类的每个方法调用都会转为调用实现了MethodInterceptor接口的proxy的intercept() 函数
private Enhancer enhancer = new Enhancer();
@SuppressWarnings("unchecked")
public Object getProxy(Class clazz){
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object target, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
PerformanceMonitor pm = new PerformanceMonitor();
pm.start(target.getClass().getName()+"."+method.getName());
//通过代理类调用父类中的方法
Object obj = proxy.invokeSuper(target,args);
pm.end();
return obj;
}
}
测试cglib创建动态代理
package com.me.test;
import com.me.entity.User;
import com.me.services.UserService;
import com.me.services.imp.UserServiceImp;
import com.me.util.CglibProxy;
public class TestUserService {
public static void main(String[] args) {
//cglib创建代理实例
CglibProxy proxy = new CglibProxy();
UserService service = (UserServiceImp)proxy.getProxy(UserServiceImp.class);
service.deleteUser(1);
service.insertUser(new User());
}
}
在控制台输出我们可以看到”
那么两种动态代理要怎么取舍呢?
cglib比jdk创建动态代理的性能高大约10倍,但是cglib在创建代理对象时间比jdk多8倍。因此,对于创建singleton或实例池对象代理用cglib方式比较合适,因为无须频繁创建代理对象,但由于cglib采用动态创建子类的方式生成代理对象,所以对目标类中的final方法进行代理。
参考链接:http://www.iteye.com/topic/258010
http://www.zhihu.com/question/20794107
http://my.oschina.net/willSoft/blog/33295
http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html