Spring AOP应用


叙述:文章是自己学习摸索然后总结,里面可能会有不足之处,如有疑问,多查找网上博客类同资料为准。


1. Spring AOP简介

 AOP(Aspect Oriented Programming),切面编程,是对OOP的补充。 AOP将应用系统分为两部分,核心业务逻辑(Core business concerns)及横向的通用逻辑,也就是所谓的方面Crosscutting enterprise concerns,例如,所有大中型应用都要涉及到的持久化管理(Persistent)、事务管理(Transaction Management)、安全管理(Security)、日志管理(Logging)和调试管理(Debugging)等。而且它比较明显的有点就是它无侵入性,还有和业务代码无关性。


2. Spring AOP概念

方面(Aspect): 一个关注点的模块化,这个关注点实现可能 另外横切多个对象。事务管理是J2EE应用中一个很好的横切关注点例子。方面用Spring的 Advisor或拦截器实现。


连接点(Joinpoint): 程序执行过程中明确的点,对于Spring AOP,Jointpoint指的就是Method。


通知(Advice): Advice又叫通知,或者增强。在特定的连接点,AOP框架执行的动作。各种类 型的通知包括“around”、“before”和“throws”、”after“等通知。通知类型将在下面讨论。许多AOP框架 包括Spring都是以拦截器做通知模型,维护一个“围绕”连接点的拦截器链来进行操作。


切入点(Pointcut): 指定一个通知将被引发的一系列连接点的集合。AOP框架必须允许开发者指定切入点。简单的说就是Pointcut可以通过正则表达式过滤来使用代码控制所有的Joinpoint来进行相关权限,日志等操作。


引入(Introduction): 添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。

目标对象(Target Object): 包含连接点的对象。也被称作 被通知或被代理对象。


AOP代理(AOP Proxy): AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。


织入(Weaving): 把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。 这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。 Spring和其他纯Java AOP框架一样,在运行时完成织入。


3. Spring AOPAdvice通知类型

Around通知: 包围一个连接点的通知,如方法调用。这是最强大的通知。Aroud通知在方法调用前后完成自定义的行为。它们负责选择继续执行连接点或通过 返回它们自己的返回值或抛出异常来短路执行,例如:通过的话是:invocation.proceed(); 在里面使用元素进行声明。


Before通知: 在一个连接点之前执行的通知,但这个通知 不能阻止连接点前的执行(除非它抛出一个异常)。在里面使用元素进行声明。


Throws通知: 在方法抛出异常时执行的通知。Spring提供 强类型的Throws通知,因此你可以书写代码捕获感兴趣的异常(和它的子类),不需要从Throwable 或Exception强制类型转换。在里面使用元素进行声明。


After returning通知: 在连接点正常完成后执行的通知, 例如,一个方法正常返回,没有抛出异常。里面使用元素进行声明。


After通知:当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。在里面使用元素进行声明

4. Proxy代理方式对类的切面

Spring AOP功能很强大,它可以满足对一些日志管理,权限控制,安全检查等功能的控制,可以很少的减少代码和提供更快捷优秀的功能服务。


当我们的系统中需要对某一个类进行监控,例如某个类中的方法,检查当前用户是否有权限访问到里面的数据,又或者想记录某一个类的运行的日志,使用OOP思想可以办到,但是有了Spring AOP,这一切都不是问题,而且还可以对一组或者特定的符合正则表达式规范的方法、参数、类名、包进行控制。下来就来一个示例介绍下如何对一个类进行控制。


(1)创建一个用户的POJO类,带getter和setter。


 Spring AOP应用开发_第1张图片

(2)然后是UserLoginService接口和UserLoginServiceImpl

       /**

        *

        * 用户功能的Service

        * <功能详细描述>

        */

       public interface UserLoginService {

       /*

        * 登录

        */

       public String login(String userName, String pass);

       

       /*

        * 查询列表信息

        */

       public String query();

       

       /**

        *

        * 用户功能的Service的实现类

        * <功能详细描述>

        */

       public class UserLoginServiceImpl implements UserLoginService {

          public String login(String userName, String pass) {

           String result; // 设置返回的结果

           if (userName.equals("andy")) {

              result = "登录成功";

           } else {

              result = "登录失败";

           }

       

       // 返回

          return result;

       }

       

       public String query() {

          return"返回查询到的列表信息";

       }

       

看一下UserLoginAdvice这个通知类,在项目模块下,new一个包,叫advice,类UserLoginAdvice的代码如下。


       import java.lang.reflect.Method;

       import org.aopalliance.intercept.*;

       import org.springframework.aop.*;

       import com.gsw.demo.service.UserLoginService;

       

       /**

        *

        * 此方法说明如何对一个对象进行日志,安全权限等监控

        * <功能详细描述>

        */

       public class UserLoginAdvice implements MethodBeforeAdvice, AfterAdvice, AfterReturningAdvice, ThrowsAdvice, MethodInterceptor {

       /*

        * 前置通知

        */

       public void before(Method method, Object[] args, Object target) throws Throwable {

             System.out.println("对" + target.getClass().getName() + "的" + method.getName() + "()方法前置通知");

       

       }

       

       /*

        * 后置通知

        */

       public void afterReturning(Object args1, Method method, Object[] args, Object target) throws Throwable {

           System.out.println("对" + target.getClass().getName() + "的" + method.getName() + "()方法进行后置通知");

           }

       

       /*

        * 后置异常通知

        */

       public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable {

           System.out.println("对" + target.getClass().getName() + "的" + method.getName() + "()方法后置异常通知");

         }

       

       /*

        * 环绕通知,它和别的通知组合是一样的,环绕通知是一个多功能的通知,完全可以代替别的几个

        */

       public Object invoke(MethodInvocation invocation) throws Throwable {

           System.out.println("环绕通知前");

           Object result = null;

           

           String name = invocation.getArguments()[0].toString(); // 获得name的值

           String pass = invocation.getArguments()[1].toString(); // 获得pass的值

           

           // 获得目标方法的全路径以及方法名

           String method = invocation.getThis().getClass().getName() + "." + invocation.getMethod().getName();

           System.out.println(method + "类中方法的参数值为 name:" + name + ", pass:" +

           pass);

       

       // 判断用户名是否合法

           if (name.equals("andy")) {

              System.out.println("用户名合法," + userLoginService.query());

           } else {

              System.out.println("您没有权限访问列表信息");

           }

           

           // 执行成功

           result = invocation.proceed();

           System.out.println("环绕通知后");

           

           // 返回结果

           return result;

       }

       

       /*

        * 最后完全通知

        */

       public void after(Method method, Object[] args, Object target) {

           System.out.println("对" + target.getClass().getName() + "的" + method.getName() + "()方法后置完全通知");

       }

       

       // 将userLoginService注入到里面进行操作

       private UserLoginService userLoginService;

       public void setUserLoginService(UserLoginService userLoginService) {

          this.userLoginService = userLoginService;

       }

       

     }

       

(4)Spring的XML里面配置

       <bean id="userLoginService" class="com.gsw.demo.service.impl.UserLoginServiceImpl">bean>

       <bean id="userLoginAdvice"class="com.gsw.demo.advice.UserLoginAdvice">

          <property name="userLoginService"ref="userLoginService">property>

       bean>

       

       <bean id="userLogin" class="org.springframework.aop.framework.ProxyFactoryBean">

          <property name="proxyInterfaces"><value>com.gsw.demo.service.UserLoginServicevalue>property>

          <property name="interceptorNames"><list><value>userLoginAdvicevalue>list>property>

          <property name="target"ref="userLoginService">property>

       bean>

       

       

(5)测试代码

       /*

        * 下面是测试的代码

        */

       public static void main(String[] args) {

           ApplicationContext context = new FileSystemXmlApplicationContext("src/spring-system.xml");

           UserLoginService service = (UserLoginService)context.getBean("userLogin");

           System.out.println(service.login("anddy", "123"));

       }

       

       5. @Aspectj注解方式对业务的切面

 Spring AOP可以通过配置正则表达式,对一系列满足其表达式的方法和类名等进行监控操作。对于下面的例子,可以基于以上的基础,POJI和Service和ServiceImpl都不变,只需要添加新的包和类,然后注销掉Spring XML中的配置即可。

 5.1 .XML配置方式

(1)在项目模块下面new一个aspect包,UserLoginAspect的代码如下

       import org.aspectj.lang.*;

       import org.aspectj.lang.annotation.*;

       import com.gsw.demo.service.UserLoginService;

       

       /**

        * 用户的控制权限的切面类 <一句话功能简述>

        * <功能详细描述>

        */

       public class UserLoginAspect {

       

           /**

            *

            * 前置通知

            * <功能详细描述>

            */

           public void before(JoinPoint point) {

           

               System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的前置通知");

           }

           

           /**

            *

            * 环绕通知

            * <功能详细描述>

            */

           public Object around(ProceedingJoinPoint point) throws Throwable {

           

               System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的前置环绕通知");

               

               Object result = null;

               

               // 从参数中去取出值

               String name = point.getArgs()[0].toString();

               

               // 判断用户名是否合法

               if (name.equals("andy")) {

                  System.out.println("用户名合法," + userLoginService.query());

               } else {

                  System.out.println("您没有权限访问列表信息");

               }

               

               // 执行成功

               result = point.proceed();

               

               System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的后置环绕通知");

               // 返回结果

               return result;

           }

           

           /**

            *

            * 后置异常通知

            * <功能详细描述>

            */

           public void throwing(JoinPoint point, Throwable ex) {

               System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的异常通知");

               System.out.println(ex.getMessage());

           }

           

           /**

            *

            * 后置通知

            * <功能详细描述>

            */

           public void afterReturning(JoinPoint point) {

               System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的后置通知");

           }

           

           /**

            *

            * 最终通知

            * <功能详细描述>

            */

           public void after(JoinPoint point) {

               System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的最终通知");

           }

           

           // 将userLoginService注入到里面进行操作

           private UserLoginService userLoginService;

           public void setUserLoginService(UserLoginService userLoginService) {

             this.userLoginService = userLoginService;

           }

       }

       

(2)Spring中XML配置

       

       <aop:aspectj-autoproxy/>

       

       <bean id="userLoginService" class="com.gsw.demo.service.impl.UserLoginServiceImpl">bean>

       <bean id="aspectBean"class="com.gsw.demo.aspect.UserLoginAspect">

          <property name="userLoginService" ref="userLoginService">property>

       bean>

       

       

       <aop:config>

           <aop:pointcut id="loginPointcut" expression="execution(* com.gsw.demo.service.*.*.login*(..))"/>

           <aop:aspect id="userAspect" ref="aspectBean">

               <aop:before pointcut-ref="loginPointcut" method="before"/>

               <aop:after pointcut-ref="loginPointcut" method="after"/>

               <aop:around pointcut-ref="loginPointcut" method="around"/>

               <aop:after-throwing pointcut-ref="loginPointcut" method="throwing" throwing="ex"/>

           aop:aspect>

       aop:config>

       

(3)测试代码

       /*

        * 下面是测试的代码

        */

       public static void main(String[] args) {

           ApplicationContext context = new FileSystemXmlApplicationContext("src/spring-system.xml");

           UserLoginService service = (UserLoginService) context.getBean("userLoginService");

           System.out.println(service.login("and的y", "123"));

       }

       (4)控制台输出代码


   <>当用户名输入错误的时候


 Spring AOP应用开发_第2张图片

<>当用户名输入andy的时候

 Spring AOP应用开发_第3张图片


 5.2. Aspectj注解方式

注解方式需要注意两个地方,第一个

(1)在项目模块下面new一个aspect包,UserLoginAspect类


import org.aspectj.lang.*;

import org.aspectj.lang.annotation.*;

import com.gsw.demo.service.UserLoginService;


/**

* 用户的控制权限的切面 <一句话功能简述>

* <功能详细描述>

*/


@Aspect

public class UserLoginAspect {


   /**

    * 定义一个切点

    */

   @Pointcut("execution(* com.gsw.demo.service.*.*.login(..))")

   public void pointCutMethod(){}

   /**

    *

    * 前置通知

    * <功能详细描述>

    */

   @Before("pointCutMethod()")

   public void before(JoinPoint point) {

   

       System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的前置通知");

   }

   

   /**

    *

    * 环绕通知

    * <功能详细描述>

    */

   @Around("pointCutMethod()")

   public Object around(ProceedingJoinPoint point) throws Throwable {

       System.out.println("进入方法---环绕通知");

       Object result = null;

       // 从参数中去取出值

       String name = point.getArgs()[0].toString();

       

       // 判断用户名是否合法

       if (name.equals("andy")) {

           System.out.println("用户名合法," + userLoginService.query());

       } else {

           System.out.println("您没有权限访问列表信息");

       }

       

       // 执行成功

       result = point.proceed();

       System.out.println("退出方法---环绕通知");

       // 返回结果

       return result;

   }

   

   /**

    *

    * 后置异常通知

    * <功能详细描述>

    */

   @AfterThrowing(pointcut = "pointCutMethod()", throwing = "ex")

   public void throwing(JoinPoint point, Throwable ex) {

       System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的异常通知");

       System.out.println(ex.getMessage());

   }

   

   /**

    *

    * 后置通知

    * <功能详细描述>

    */

   @AfterReturning(pointcut = "pointCutMethod()", returning = "result")

   public void afterReturning(JoinPoint point,String result) {

       System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的后置通知");

       System.out.println("-------"+result+"-------");

   }

   

   /**

    *

    * 最终通知

    * <功能详细描述>

    */

   @After("pointCutMethod()")

   public void after(JoinPoint point) {

       System.out.println(point.getTarget().getClass().getName() + "." + point.getSignature().getName() + "()方法的最终通知");

    }

   

   // 将userLoginService注入到里面进行操作

   private UserLoginService userLoginService;

   public void setUserLoginService(UserLoginService userLoginService) {

     this.userLoginService = userLoginService;

   }

 }


(2)Spring的XML中的配置

这里面注意要加上这个标签来来使得可以注解。

 Spring AOP应用开发_第4张图片

(3)控制台输出结果

<>当输入用户名正确的时候


 Spring AOP应用开发_第5张图片

<>当用户名输入错误的时候

 Spring AOP应用开发_第6张图片


6. 总结

Spring功能如此强大,值得好好学习,总结下通知各种类型中用到的一些方法。任何通知(Advice)方法可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型。JoinPoint接口提供了一系列有用的方法, 比如 getArgs() (返回方法参数)、getThis() (返回代理对象)、getTarget() (返回目标)、getSignature() (返回正在被通知的方法相关信息)和 toString() (打印出正在被通知的方法的有用信息。

其中getSignature()返回的Signature对象可强制转换为MethodSignature,其功能非常强大,能获取包括参数名称在内的一切方法信息。