Spring AOP介绍

来源:互联网 发布:手机版 淘宝 人工客服 编辑:程序博客网 时间:2024/05/17 01:38

Spring AOP介绍(面试必问)


AOP就是写代码的时候 把各个模块中需要重复写的抽取出来,弄成一个切面。例如日志,权限。

切面的具体表现就是实现公共方法的类

通知:在实际应用中通常是切面类中的一个方法

在传统的编写业务逻辑处理代码时,我们通常会习惯性地做几件事情:日志记录、事务控制及权限控制等,然后才是编写核心的业务逻辑处理代码。当代码编写完成回头再看时,不禁发现,扬扬洒洒上百行代码中,真正用于核心业务逻辑处理才那么几行,如图6-4所示。方法复方法,类复类,就这样子带着无可奈何遗憾地度过了多少个春秋。这倒也罢,倘若到了项目的尾声,突然决定在权限控制上需要进行大的变动时,成千上万个方法又得一一"登门拜访",痛苦"雪上加霜"。

 

如果能把图6-4中众多方法中的所有共有代码全部抽取出来,放置到某个地方集中管理,然后在具体运行时,再由容器动态织入这些共有代码的话,最起码可以解决两个问题:

Java EE程序员在编写具体的业务逻辑处理方法时,只需关心核心的业务逻辑处理,既提高了工作效率,又使代码变更简洁优雅。

在日后的维护中由于业务逻辑代码与共有代码分开存放,而且共有代码是集中存放的,因此使维护工作变得简单轻松。

面向切面编程AOP技术就是为解决这个问题而诞生的,切面就是横切面,如图6-5所示,代表的是一个普遍存在的共有功能,例如,日志切面、权限切面及事务切面等。

 

下面我们以用户管理业务逻辑组件UserService的AOP实现过程(见图6-6)为例,深度剖析一下AOP技术的实现原理。AOP技术是建立在Java语言的反射机制与动态代理机制之上的。业务逻辑组件在运行过程中,AOP容器会动态创建一个代理对象供使用者调用,该代理对象已经按Java EE程序员的意图将切面成功切入到目标方法的连接点上,从而使切面的功能与业务逻辑的功能同时得以执行。从原理上讲,调用者直接调用的其实是AOP容器动态生成的代理对象,再由代理对象调用目标对象完成原始的业务逻辑处理,而代理对象则已经将切面与业务逻辑方法进行了合成。

 

现将图6-6中涉及到的一些概念解释如下。

切面(Aspect):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。

通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。

连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但spring只支持方法级的连接点。

切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。

目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。

代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。

织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。

Spring的AOP是上面代理模式的深入。使用Spring AOP,开发者无需实现业务逻辑对象工厂,无需实现代理工厂,这两个工厂都由Spring容器充当。Spring AOP不仅允许使用XML文件配置目标方法,ProxyHandler也允许使用依赖注入管理,Spring AOP提供了更多灵活的选择。
在下面Spring AOP的示例中,InvocationHandler采用动态配置,需要增加的方法也采用动态配置,一个目标对象可以有多个拦截器(类似于代理模式中的代理处理器)。
下面是原始的目标对象:
//目标对象的接口
public interface  Person
{
 //该接口声明了两个方法
 void info();
 void run();
}
下面是原始目标对象的实现类,实现类的代码如下:
//目标对象的实现类,实现类实现Person接口
public class PersonImpl implements Person
{
 //两个成员属性
 private String name;
 private int age;
 //name属性的 setter方法
 public void setName(String name)
 {
  this.name = name;
 }
 //age属性的setter方法
public void setAge(int age)
 {
  this.age = age;
 }
 //info方法,该方法仅仅在控制台打印一行字符串
 public void info()
 {
  System.out.println("我的名字是:  " + name + " , 今年年龄为:  " + age);
 }
 //run方法,该方法也在控制台打印一行字符串。
 public void run()
 {
  if (age < 45)
  {
   System.out.println("我还年轻,奔跑迅速...");
  }
  else
  {
   System.out.println("我年老体弱,只能慢跑...");
  }
 }
}
该Person实例将由Spring容器负责产生和管理,name属性和age属性也采用依赖注入管理。
为了充分展示Spring AOP的功能,此处为Person对象创建三个拦截器。第一个拦截器是调用方法前的拦截器,代码如下:
//调用目标方法前的拦截器,拦截器实现MethodBeforeAdvice接口
public class MyBeforeAdvice implements MethodBeforeAdvice
{
 //实现MethodBeforeAdvice接口,必须实现before方法,该方法将在目标
 //方法调用之前,自动被调用。
     public void before(Method m, Object[] args, Object target) throws Throwable
 {
  System.out.println("方法调用之前...");
  System.out.println("下面是方法调用的信息:");
  System.out.println("所执行的方法是:" + m);
  System.out.println("调用方法的参数是:" + args);
  System.out.println("目标对象是:" + target);
     }
}
第二个拦截器是方法调用后的拦截器,该拦截器将在方法调用结束后自动被调用,拦截器代码如下:
//调用目标方法后的拦截器,该拦截器实现AfterReturningAdvice接口
public class MyAfterAdvice implements AfterReturningAdvice
{
 //实现AfterReturningAdvice接口必须实现afterReturning方法,该方法将在目标方法
 //调用结束后,自动被调用。
     public void afterReturning(Object returnValue, Method m, Object[] args, Object target)throws Throwable
 {
  System.out.println("方法调用结束...");
System.out.println("目标方法的返回值是 : " + returnValue);
  System.out.println("目标方法是 : " + m);
  System.out.println("目标方法的参数是 : " + args);
  System.out.println("目标对象是 : " + target);
    }
}
第三个拦截器是是Around拦截器,该拦截器既可以在目标方法之前调用,也可以在目标方法调用之后被调用。下面是Around拦截器的代码:
//Around拦截器实现MethodInterceptor接口
public class MyAroundInterceptor implements MethodInterceptor
{
 //实现MethodInterceptor接口必须实现invoke方法
     public Object invoke(MethodInvocation invocation) throws Throwable
 {
  //调用目标方法之前执行的动作
         System.out.println("调用方法之前: invocation对象:[" + invocation + "]");
  //调用目标方法
         Object rval = invocation.proceed();
  //调用目标方法之后执行的动作
         System.out.println("调用结束...");
         return rval;
    }
}
利用Spring AOP框架,实现之前的代理模式相当简单。只需要实现对应的拦截器即可,无需创建自己的代理工厂,只需采用Spring容器作为代理工厂。下面在Spring配置文件中配置目标bean,以及拦截器。
下面是Spring配置文件的代码:
<?xml version="1.0" encoding="gb2312"?>
<!--  Spring配置文件的文件头-->
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
 "http://www.springframework.org/dtd/spring-beans.dtd">
<!--  Spring配置文件的根元素-->
<beans>
 <!--  配置目标对象-->
 <bean id="personTarget" class="lee.PersonImpl">
  <!--  为目标对象注入name属性值-->
  <property name="name">
   <value>Wawa</value>
  </property>
  <!--  为目标对象注入age属性值-->
  <property name="age">
   <value>51</value>
  </property>
 </bean>
 <!--  第一个拦截器-->
 <bean id="myAdvice" class="lee.MyBeforeAdvice"/>
 <!--  第二个拦截器-->
 <bean id="myAroundInterceptor" class="lee.MyAroundInterceptor"/>
<!--  将拦截器包装成Advisor,该对象还确定代理对怎样的方法增加处理-->
 <bean id="runAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
  <!--  advice属性确定处理bean-->
  <property name="advice">
   <!-- 此处的处理bean定义采用嵌套bean,也可引用容器的另一个bean-->
   <bean class="lee.MyAfterAdvice"/>
  </property>
  <!--  patterns确定正则表达式模式-->
  <property name="patterns">
   <list>
    <!--  确定正则表达式列表-->
    <value>.*run.*</value>
   </list>
  </property>
 </bean>
 <!--  使用ProxyFactoryBean 产生代理对象-->
 <bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
  <!--  代理对象所实现的接口-->
  <property name="proxyInterfaces">
   <value>lee.Person</value>
  </property>
  <!--  设置目标对象-->
  <property name="target">
   <ref local="personTarget"/>  
  </property>
  <!--  代理对象所使用的拦截器-->
  <property name="interceptorNames">
   <list>
    <value>runAdvisor</value>
    <value>myAdvice</value>
    <value>myAroundInterceptor</value>
   </list>
  </property>
 </bean>
</beans>
该配置文件使用ProxyFactoryBean来生成代理对象,配置ProxyFactoryBean工厂bean时,指定了target属性,该属性值就是目标对象,该属性值为personTarget,指定代理的目标对象为personTarget。通过interceptorNames属性确定代理需要的拦截器,拦截器可以是普通的Advice,普通Advice将对目标对象的所有方法起作用,拦截器也可以是Advisor,Advisor是Advice和切面的组合,用于确定目标对象的哪些方法需要增加处理,以及怎样的处理。在上面的配置文件中,使用了三个拦截器,其中myAdvice、myAroundInterceptor都是普通Advice,它们将对目标对象的所有方法起作用。而runAdvisor则使用了正则表达式切面,匹配run方法,即该拦截器只对目标对象的run方法起作用。

下面是测试代理的主程序:
public class BeanTest
{
    public static void main(String[] args)throws Exception
{
  //创建Spring容器
ApplicationContext ctx = new FileSystemXmlApplicationContext("bean.xml");
  //获取代理对象
  Person p = (Person)ctx.getBean("person");
  //执行info方法
  p.info();
         System.out.println("===========================================");
  //执行run方法
  p.run();
    }
}
下面是程序的执行结果:
方法调用之前...
下面是方法调用的信息:
所执行的方法是:public abstract void lee.Person.info()
调用方法的参数是:null
目标对象是:lee.PersonImpl@b23210
调用方法之前: invocation对象:[invocation: method 'info', arguments
[]; target is of class [lee.PersonImpl]]
我的名字是:  Wawa , 今年年龄为:  51
调用结束...
===========================================
方法调用之前...
下面是方法调用的信息:
所执行的方法是:public abstract void lee.Person.run()
调用方法的参数是:null
目标对象是:lee.PersonImpl@b23210
调用方法之前: invocation对象:[invocation: method 'run', arguments [
]; target is of class [lee.PersonImpl]]
我年老体弱,只能慢跑...
调用结束...
方法调用结束...
目标方法的返回值是 : null
目标方法是 : public abstract void lee.Person.run()
目标方法的参数是 : null
目标对象是 : lee.PersonImpl@b23210
程序的执行结果中一行“=”用于区分两次调用的方法。在调用info方法时,只有myAdvice和myAroundInterceptor两个拦截器起作用,调用run方法时候,三个拦截器都起作用了。

       通过上面的介绍,可看出Spring的AOP框架是对代理模式简化,并拓展了代理模式的使用。
Spring AOP是Spring声明式事务的基础。了解Spring AOP对深入理解Spring的声明式事务管理是非常有好处的。Spring AOP还可以完成很多功能,例如基于AOP的权限检查。

0 0
原创粉丝点击