Spring系列(四)Spring Aop基本用法

 

一、AOP

相对AOP(面向切面编程)而言大家一定更了解OOP(面向对象程序设计)

面向对象设计是一种模块化方法:把相关的数据(相关的对象)及处理方法放在一起。

面向切面编程也是一种模块化机制:核心思想是从关注点中分离出横切关注点。(这样关注的问题从代码的业务逻辑独立出来,解耦)

个人理解:面向切面编程,可以把任意的两个切面随意的组合成一个整体,这样可以在不影响原有业务逻辑的情况下进行功能的增强

先来了解一下 AOP 的相关概念

 

1、横切关注点

对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点

2、切面(aspect)

类是对物体特征的抽象,切面就是对横切关注点的抽象,面向切面编程,就是指 对很多功能都有的重复的代码抽取,再在运行的时候往业务方法上动态植入“切面类代码”。

3、连接点(joinpoint)

被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器

4、切入点(pointcut)

切入点在AOP中的通知和切入点表达式关联,指定拦截哪些类的哪些方法, 给指定的类在运行的时候动态的植入切面类代码。

5、通知(advice)

所谓通知指的就是指拦截到连接点之后要执行的代码,

Spring切面可以应用5种类型的通知:

前置通知(Before):在目标方法被调用之前调用通知功能;

后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;

返回通知(After-returning):在目标方法成功执行之后调用通知;

异常通知(After-throwing):在目标方法抛出异常后调用通知;

环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

注:可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。

6、目标对象

被一个或者多个切面所通知的对象。

7、织入(weave)

将切面应用到目标对象并导致代理对象创建的过程

8、引入(introduction)

在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

9、AOP代理(AOP Proxy)

在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。

 

使用 Spring AOP 可以基于两种方式,一种是比较方便和强大的注解方式,另一种则是中规中矩的 xml

配置方式。

二、注解方式Spring + AspectJ

 

对切面类添加 @Aspect 注解,将切面类和目标类放入到 IOC 容器中,可以通过 <context:component-scan base-package=""/> 进行扫描。

添加增强方法(包括增强类型和切点表达式,以及连接点)。

在 Spring 配置文件 文件中添加 <aop:aspectj-autoproxy proxy-target-class="true"/>, proxy-target-class属性,false 只能代理接口(JDK动态代理),true 代理类(CGLIB动态代理)

 

<?xml version="1.0" encoding="UTF-8"?>

  <beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:context="http://www.springframework.org/schema/context"

       xmlns:aop="http://www.springframework.org/schema/aop"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

     <!-- 注解驱动加-->

   <aop:aspectj-autoproxy proxy-target-class="true"/>

   <context:component-scan base-package="springinaction3.wiringbean.qiyi"></context:component-scan>

</beans>

 

2.定义切面类(包括了5中通知)

//声明这是一个组件

  @Component

  //声明这是一个切面Bean,AnnotaionAspect是一个切面,由框架实现的

  @Aspect

  public class AnnotaionAspect {

   private final static Logger log = Logger.getLogger(AnnotaionAspect.class);

   //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点

   //切点的集合,这个表达式所描述的是一个虚拟面(规则)

   //就是为了Annotation扫描时能够拿到注解中的内容

   @Pointcut("execution(* springinaction3.wiringbean.qiyi..*(..))")

   public void aspect(){}

   

   /*

    * 配置前置通知,使用在方法aspect()上注册的切入点

    * 同时接受JoinPoint切入点对象,可以没有该参数

    */

   @Before("aspect()")

   public void before(JoinPoint joinPoint){

  

      log.info("before " + joinPoint);

   }

   

   //配置后置通知,使用在方法aspect()上注册的切入点

   @After("aspect()")

   public void after(JoinPoint joinPoint){

      log.info("after " + joinPoint);

   }

   

   //配置环绕通知,使用在方法aspect()上注册的切入点

   @Around("aspect()")

   public void around(JoinPoint joinPoint){

      long start = System.currentTimeMillis();

      try {

         ((ProceedingJoinPoint) joinPoint).proceed();

         long end = System.currentTimeMillis();

         log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms!");

      } catch (Throwable e) {

         long end = System.currentTimeMillis();

         log.info("around " + joinPoint + "\tUse time : " + (end - start) + " ms with exception : " + e.getMessage());

      }

   }

   

   //配置后置返回通知,使用在方法aspect()上注册的切入点

   @AfterReturning("aspect()")

   public void afterReturn(JoinPoint joinPoint){

      log.info("afterReturn " + joinPoint);

   }

   

   //配置抛出异常后通知,使用在方法aspect()上注册的切入点

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

   public void afterThrow(JoinPoint joinPoint, Exception ex){

      log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());

   }

   

}

 

注意:配置切入点需要额外注意:@Pointcut

3.配置log4j.properties

### set log levels ###

  log4j.rootLogger = INFO , console , debug , error  

  

  ### console ###

  log4j.appender.console = org.apache.log4j.ConsoleAppender  

  log4j.appender.console.Target = System.out  

  log4j.appender.console.layout = org.apache.log4j.PatternLayout  

  log4j.appender.console.layout.ConversionPattern = %-d{yyyy-MM-dd HH\:mm\:ss} [%p]-[%c] %m%n  

  

  ### log file ###

  log4j.appender.debug = org.apache.log4j.DailyRollingFileAppender  

  log4j.appender.debug.File = ../logs/debug.log  

  log4j.appender.debug.Append = true  

  log4j.appender.debug.Threshold = INFO  

  log4j.appender.debug.layout = org.apache.log4j.PatternLayout  

  log4j.appender.debug.layout.ConversionPattern =%-d{yyyy-MM-dd HH\:mm\:ss} [%p]-[%c] %m%n  

  

  ### exception ###

  log4j.appender.error = org.apache.log4j.DailyRollingFileAppender  

  log4j.appender.error.File = ../logs/error.log  

  log4j.appender.error.Append = true  

  log4j.appender.error.Threshold = ERROR  

  log4j.appender.error.layout = org.apache.log4j.PatternLayout  

  log4j.appender.error.layout.ConversionPattern =%-d{yyyy-MM-dd HH\:mm\:ss} [%p]-[%c] %m%n  



  log4j.appender.stdout=org.apache.log4j.ConsoleAppender  

  log4j.appender.stdout.Target=System.out  

  log4j.appender.stdout.layout=org.apache.log4j.PatternLayout  

  log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %l %c%n%p: %m%n  

 

 

三、Xml方式:

Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>

  <beans xmlns="http://www.springframework.org/schema/beans"

       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:context="http://www.springframework.org/schema/context"

       xmlns:aop="http://www.springframework.org/schema/aop"

       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

       http://www.springframework.org/schema/context

       http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


  <!--

   <aop:aspectj-autoproxy proxy-target-class="true"/>

-->
  

   <context:component-scan base-package="springinaction3.wiringbean.qiyi"></context:component-scan>
   

   <bean id="xmlAspect" class="springinaction3.wiringbean.qiyi.aspect.XmlAspect"></bean>

    <!--AOP配置 -->

   <aop:config>

    <!--声明一个切面,并注入切面Bean,相当于@Aspect -->

   <aop:aspect ref="xmlAspect">

    <!--配置一个切入点,相当于@Pointcut -->

   <aop:pointcut expression="execution(* springinaction3.wiringbean.qiyi..*(..))" id="simplePointcut"/>

    <!--配置通知,相当于@Before、@After、@AfterReturn、@Around、@AfterThrowing -->

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

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

   <aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>

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

   </aop:aspect>

   </aop:config>

</beans>

 

切面类

public class XmlAspect {

  

    private final static Logger log = Logger.getLogger(XmlAspect.class);

    /*

     * 配置前置通知,使用在方法aspect()上注册的切入点

     * 同时接受JoinPoint切入点对象,可以没有该参数

     */

    public void before(JoinPoint joinPoint){

//    System.out.println(joinPoint.getArgs()); //获取实参列表

//    System.out.println(joinPoint.getKind());   //连接点类型,如method-execution

//    System.out.println(joinPoint.getSignature()); //获取被调用的切点

//    System.out.println(joinPoint.getTarget()); //获取目标对象

//    System.out.println(joinPoint.getThis());   //获取this的值


        log.info("before " + joinPoint);

    }

  
    //配置后置通知,使用在方法aspect()上注册的切入点

    public void after(JoinPoint joinPoint){

        log.info("after " + joinPoint);

    }
  

    //配置环绕通知,使用在方法aspect()上注册的切入点

    public void around(JoinPoint joinPoint){

        long start = System.currentTimeMillis();

        try {

            ((ProceedingJoinPoint) joinPoint).proceed();

            long end = System.currentTimeMillis();

            log.info("around " + joinPoint + "\ttime : " + (end - start) + " ms!");

        } catch (Throwable e) {

            long end = System.currentTimeMillis();

            log.info("around " + joinPoint + "\ttime : " + (end - start) + " ms with exception : " + e.getMessage());

        }

    }

  
    //配置后置返回通知,使用在方法aspect()上注册的切入点

    public void afterReturn(JoinPoint joinPoint){

        log.info("afterReturn " + joinPoint);

    }
  

    //配置抛出异常后通知,使用在方法aspect()上注册的切入点

    public void afterThrow(JoinPoint joinPoint, Exception ex){

        log.info("afterThrow " + joinPoint + "\t" + ex.getMessage());

    }

  

}

 

个人感觉:注解方式更方便些

 

学习难点

 

掌握和使用切入点表达式。

切入点表达式的配置规则。

通常情况下,表达式中使用”execution“就可以满足大部分的要求。

表达式格式如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?

 

 

modifiers-pattern:方法的操作权限

ret-type-pattern:返回值

declaring-type-pattern:方法所在的包

name-pattern:方法名

parm-pattern:参数名

throws-pattern:异常

其中,除 ret-type-pattern 和 name-pattern 之外,其他都是可选的。

上例中,execution(* com.spring.service.*.*(..))表示 com.spring.service 包下,返回值为任意类型;方法名任意; 参数不作限制的所有方法。

 

最后说一下通知参数 可以通过 args 来绑定参数,这样就可以在通知(Advice)中访问具体参数了。

例如, 配置如下: 上面的代码 args(msg,..)是指将切入点方法上的第一个 String 类型参数添加到参数名为 msg 的通知 的入参上,这样就可以直接使用该参数啦。

访问当前的连接点 在上面的 Aspect 切面 Bean 中已经看到了,每个通知方法第一个参数都是 JoinPoint。其实,在 Spring 中,任何通知(Advice)方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint 类型用以 接受当前连接点对象。JoinPoint 接口提供了一系列有用的方法, 比如 getArgs() (返回方法参数)、 getThis() (返回代理对象)、getTarget() (返回目标)、getSignature() (返回正在被通知的 方法相关信息)和 toString() (打印出正在被通知的方法的有用信息)。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值