`

使用Spring进行面向切面(AOP)编程

    博客分类:
  • JAVA
阅读更多
1.aop理论知识
横切性关注点:对哪些方法拦截,拦截后怎么处理,这些关注就称之为横切性关注点.
Aspect(切面):指横切性关注点的抽象即为切面,它与类相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象。
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点,实际上joinpoint还可以是field或类构造器。
Pointcut(切入点):所谓切入点是指我们要对那些joinpoin进行拦截的定义。
Advice(通知):所谓通知是指拦截到joinpoint之后所要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。
Target(目标对象):代理的目标对象
Weave(织入):指将aspects应用到target对象并导致proxy对象创建的过程称为织入。
Introduction(引入):在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field.

     要进行AOP编程,首先我们要在Spring的配置文件中引入aop命名空间,如下面文件中的红色部分所示,配置文件的内容如下:
<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-2.5.xsd
           http://www.springframework.org/schema/context           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop           http://www.springframework.org/schema/aop/spring-aop-2.5.xsd ">
    <aop:aspectj-autoproxy /><!-- 启动对@Aspectj注解的支持-->
</beans>
Spring提供了两种切面使用方式,实际工作中我们可以选用其中任何一种:
1.使用XML配置方式进行AOP开发
2. 基于注解方式进行AOP开发
首先,必须在配置文件中beans的节点下有如下配置信息:<aop:aspectj-autoproxy/> -- 启动对@Aspectj注解的支持

下面通过实例来讲解基于spring的AOP实现:
1.导入spring开发的基本包(包括切面及注解包)
2.定义PersonService接口及bean类PersonServiceBean,如下所示:
Java代码 
package cn.itcast.service; 
 
public interface PersonService { 
    public int save(String name); 
    public void update(String name, Integer id); 
    public String getPersonName(Integer id); 


Java代码 
package cn.itcast.service.impl; 
 
import cn.itcast.service.PersonService; 
 
public class PersonServiceBean implements PersonService{ 
 
    public String getPersonName(Integer id) { 
        System.out.println("我是getPersonName()方法"); 
        return "xxx"; 
    } 
 
    public int save(String name) { 
    //throw new RuntimeException("我爱例外"); 
        //int i = 10/0; 
        System.out.println("我是save()方法"); 
             return 0; 
    } 
 
    public void update(String name, Integer id) { 
        System.out.println("我是update()方法"); 
    } 

3.编写切面类MyInterceptor,代码如下:
Java代码 
package cn.itcast.service; 
 
/**
* 切面
*
*/ 
@Aspect  //声明一个切面 
public class MyInterceptor { 
    @Pointcut("execution (* cn.itcast.service.impl.PersonServiceBean.*(..))") 
    private void anyMethod() {}//声明一个切入点 
     
    @Before("anyMethod() && args(name)")//定义前置通知,拦截的方法不但要满足声明的切入点的条件,而且要有一个String类型的输入参数,否则不会拦截 
    public void doAccessCheck(String name) { 
        System.out.println("前置通知:"+ name); 
    } 
     
    @AfterReturning(pointcut="anyMethod()",returning="result") //定义后置通知,拦截的方法的返回值必须是int类型的才能拦截 
    public void doAfterReturning(int result) { 
        System.out.println("后置通知:"+ result); 
    } 
     
@AfterThrowing(pointcut="anyMethod()",throwing="e") //定义例外通知 
    public void doAfterThrowing(Exception e) { 
        System.out.println("例外通知:"+ e); 
    } 
 
    @After("anyMethod()") //定义最终通知 
    public void doAfter() { 
        System.out.println("最终通知"); 
    } 
     
    @Around("anyMethod()") //定义环绕通知 
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable { 
        //if(){//判断用户是否在权限 
        System.out.println("进入方法"); 
        Object result = pjp.proceed();//当使用环绕通知时,这个方法必须调用,否则拦截到的方法就不会再执行了 
        System.out.println("退出方法"); 
        //} 
        return result; 
    } 
     

   说明:
       1. //* cn.itcast.service..*.*(..))第一个*代表返回的类型可以是任意的,service后面的..代表包service以及下面的所有子包,*代表这些包下面的所有类;.*代表所有这些类下面的所有方法;(..)代表这些方法的输入参数可以是0或多个.
      2.在前置通知的方法中有一个参数,然后再把此参数作为拦截条件(即是说拦截带有一个String类型参数的方法)。args的名字和beforeAdd 方法参数名字相同。
      3. afterReturningRes方法的参数就是要返回的参数类型,returning标记的就是的结果,它的取值与该方法参数名相同。
      4. 环绕通知要注意的是方法的参数及抛出异常类型的固定写法(方法名可以是任意得),另在该方法中必须执行pjp.proceed()才能让环绕通知中的两处打印代码得以执行。即是说要想环绕通知的拦截处理代码起作用必须调用pjp.proceed方法。 补充:环绕通知通常可以用来测试方法的执行时间,在pjp.proceed前获取一个时间,在pjp.proceed方法后再获取一个时间。最后两个时间相减即可得方法执行时间。

上面基于注解的AOP换成基于XML的方式如下所示:
Java代码 
<bean id="aspetbean" class="cn.itcast.service.MyInterceptor" /> 
    <!--配置aop --> 
    <aop:config> 
        <aop:aspect id="asp" ref="aspetbean"><!--声明一个切面类--> 
            <!--定义切入点  --> 
            <aop:pointcut id="mycut" 
                expression="execution(* cn.itcast.service.impl.PersonServiceBean.*(..)) " /> 
            <!-- pointcut-ref :切入点引用--> 
            <!--<aop:before pointcut-ref="mycut" method="doAccessCheck" arg-names="name" />--> 
            <aop:after-returning pointcut-ref="mycut" 
                method="doAfterReturning" returning="result" /> 
            <aop:after-throwing pointcut-ref="mycut" 
                method="doAfterThrowing" throwing="e" /> 
            <aop:after pointcut-ref="mycut" method="doAfter" /> 
            <aop:around pointcut-ref="mycut" method="doBasicProfiling" /> 
        </aop:aspect> 
    </aop:config> 
     当基于xml方式时,遇到一个未解决问题:不能成功传参给前置通知。当我定义一个前置通知如下所示时:
Java代码 
<aop:before pointcut-ref="mycut" method="doAccessCheck" arg-names="name" /> 
运行测试程序,他不能成功拦截方法:
Java代码 
personService.save("xx"); 

     上面的这个拦截类中的拦截方法除了切入点条件外,还必须满足一些辅助条件,使用拦截更精确了,如果你不想太精确,则可以简单点如下所示:
Java代码 
@Aspect 
public class MyInterceptor { 
    @Pointcut("execution(* cn.itcast.service..*.*(..))") 
    private void anyMethod() {}//声明一个切入点 
     
    @Before("anyMethod())")//定义前置通知 
    public void doAccessCheck() { 
        System.out.println("前置通知"); 
    } 
     
    @AfterReturning(pointcut="anyMethod()") //定义后置通知 
    public void doAfterReturning() { 
        System.out.println("后置通知"); 
    } 
     
    @AfterThrowing(pointcut="anyMethod()") //定义例外通知 
    public void doAfterThrowing(Exception e) { 
        System.out.println("例外通知"); 
    } 
     
    .............. 
     

  替换成xml方式为:
Java代码 
<bean id="aspetbean" class="cn.itcast.service.MyInterceptor"/> 
        <aop:config> 
            <aop:aspect id="asp" ref="aspetbean"> 
                <aop:pointcut id="mycut" expression="execution(* cn.itcast.service..*.*(..))"/> 
                <aop:before pointcut-ref="mycut" method="doAccessCheck"/> 
                <aop:after-returning pointcut-ref="mycut" method="doAfterReturning"/> 
                <aop:after-throwing pointcut-ref="mycut" method="doAfterThrowing"/> 
                <aop:after pointcut-ref="mycut" method="doAfter"/> 
                <aop:around pointcut-ref="mycut" method="doBasicProfiling"/> 
            </aop:aspect> 
        </aop:config> 
当不能前置通知传参数时,基于xml方式配置的这个AOP也能成功拦截save()方法.
Java代码 
personService.save("xx"); 

4.将这些内纳入Spring管理(要想切面类起作用,首先要把切面类纳入spring容器管理。),配置文件如下所示:
Xml代码 
<?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-2.5.xsd 
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd 
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> 
        <aop:aspectj-autoproxy/>  
        <bean id="myInterceptor" class="cn.itcast.service.MyInterceptor"/> 
        <bean id="personService" class="cn.itcast.service.impl.PersonServiceBean"></bean> 
</beans> 
5. 编写junit测试单元如下:
Java代码 
public class SpringAOPTest { 
 
    @Test 
    public void interceptorTest(){ 
        ApplicationContext cxt = new ClassPathXmlApplicationContext("beans.xml"); 
        PersonService personService = (PersonService)cxt.getBean("personService"); 
              //PersonServiceBean personService = (PersonServiceBean)cxt.getBean("personService"); 
        //PersonService personService = new PersonServiceBean(); 
        personService.save("xx"); 
               //personService.getPersonName(90); 
    } 

1. 执行测试程序后,控制台打印如下所示:
前置通知:xx
进入方法
我是save()方法
后置通知:0
最终通知
退出方法
从打印的结果可以看出当执行
Java代码 
personService.save("xx"); 
方法时,它有String类型的输入参数,拦截时前置通知执行了,由于它的返回类型为int所以后置通知也执行.

2.执行方法:
Java代码 
personService.getPersonName(90); 
控制台打印的信息如下所示:
进入方法
我是getPersonName()方法
最终通知
退出方法
由于getPersonName()方法是Integer类型的输入参数,同时它的返回类型又是String类型的,所以前置通知和后置通知都没有执行.

3. 将PersonServiceBean中程序
Java代码 
// int i = 10/0; 
前的注释去掉,重新执行
Java代码 
personService.save("xx"); 
控制台打印信息如下所示:
前置通知:xx
进入方法
例外通知:java.lang.ArithmeticException: / by zero
最终通知
    当获取代理对象并调用save方法时会抛出异常,例外通知便会得以执行。

4.由于开启了切面编程功能,所以当我们获取一个被切面类监控管理的bean对象—PersonServiceBean时,它实际上获取的是此对象的一个代理对象,而在spring中对代理对象的处理有如下原则:(1)如果要代理的对象实现了接口,则会按照Proxy的方式来产生代理对象,这即是说产生的代理对象只能是接口类型,比如起用上面注掉的代码
Java代码 
//PersonServiceBean personService = (PersonServiceBean)cxt.getBean("personService"); 
就会报错,报错信息如下所示:java.lang.ClassCastException: $Proxy12 cannot be cast to cn.itcast.service.impl.PersonServiceBean.
(2)要代理的对象未实现接口,则按cglib方式来产生代理对象。让PersonServiceBean不再实现PersonService接口。这时只能执行
Java代码 
//PersonServiceBean personService = (PersonServiceBean)cxt.getBean("personService"); 
(3)另外还要注意:要想spring的切面技术起作用,被管理的bean对象只能是通过spring容器获取的对象。比如这里如果直接new出PersonServiceBean对象,则new出的对象是不能被spring的切面类监控管理。如果执行测试:
Java代码 
PersonService personService = new PersonServiceBean(); 
        personService.save("xx"); 
则不会拦截save()方法,save()方法正常执行,控制台打印信息如下所示:我是save()方法.

小结:(1)声明aspect的切面类要纳入spring容器管理才能起作用。(2)被管理的bean实例要通过容器的getBeans方法获取。 (3)依据被管理的bean是否实现接口,spring采取两种方式来产生代理对象。(4)在xml文件中启用<aop:aspectj-autoproxy/>

有疑问的地方:
如果我在MyInterceptor类中调换后置通知我最终通知的定义顺序,如下所示:
Java代码 
@After("anyMethod()") //定义最终通知 
    public void doAfter() { 
        System.out.println("最终通知"); 
    } 
     
    @AfterReturning(pointcut="anyMethod()",returning="result") //定义后置通知 
    public void doAfterReturning(int result) { 
        System.out.println("后置通知:"+ result); 
    } 
则控制台打倒信息如下所示:
前置通知:xx
进入方法
我是save()方法
最终通知
后置通知:0
退出方法

则最终通知与后置通知的执行顺序与MyInterceptor类中通知定义的顺序相一致.但如果你把最终通知的定义放在MyInterceptor类的最前面,它的执行顺序也与上面的一致.
同时如果将最终通知的定义位于例外通知的前面,则再执行测试的第3步,也会出现与测试3不一致的结果,打印结果如下所示:
前置通知:xx
进入方法
最终通知
例外通知:java.lang.ArithmeticException: / by zero
执行顺序也与它们的定义顺序一致.

不管通知的定义顺序如何,反正前置通知会第一个执行,最终通知与后置通知和例外通知定义的顺序不一致时,他们执行的顺序也会发生改变,最终通知竟可以先于例外通知和后置通知先执行.因为在Spring里有可能在同一个AOP代理里混合通知器和通知类型。 例如,你可以在一个代理配置里使用一个拦截环绕通知,一个异常通知和一个前置通知:Spring将负责自动创建所需的拦截器链。 可能Spring创建拦截器链时与通知定义的顺序有一定关系,所以当最终通知的定义先于后置通知和例外通知时,它也将先于他俩执行.只是我的一种猜测.官方的解释如下:
下面这段话是Spring reference中的原话,不过我也没太看明白是咋回事.
通知顺序
    如果有多个通知想要在同一连接点运行会发生什么?Spring AOP遵循跟AspectJ一样的优先规则来确定通知执行的顺序。 在“进入”连接点的情况下,最高优先级的通知会先执行(所以给定的两个前置通知中,优先级高的那个会先执行)。 在“退出”连接点的情况下,最高优先级的通知会最后执行。(所以给定的两个后置通知中, 优先级高的那个会第二个执行)。
     当定义在不同的切面里的两个通知都需要在一个相同的连接点中运行, 那么除非你指定,否则执行的顺序是未知的。你可以通过指定优先级来控制执行顺序。 在标准的Spring方法中可以在切面类中实现org.springframework.core.Ordered 接口或者用Order注解做到这一点。在两个切面中, Ordered.getValue()方法返回值(或者注解值)较低的那个有更高的优先级。
     当定义在相同的切面里的两个通知都需要在一个相同的连接点中运行, 执行的顺序是未知的(因为这里没有方法通过反射javac编译的类来获取声明顺序)。 考虑在每个切面类中按连接点压缩这些通知方法到一个通知方法,或者重构通知的片段到各自的切面类中 - 它能在切面级别进行排序。

5.解析切入点表达式
下面给出一些通用切入点表达式的例子。
任意公共方法的执行:
execution(public * *(..))
任何一个名字以“set”开始的方法的执行:
execution(* set*(..))
AccountService 接口定义的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
在service包中定义的任意方法的执行:
execution(* com.xyz.service.*.*(..))
在service包或其子包中定义的任意方法的执行:
execution(* com.xyz.service..*.*(..))
如果只拦截返回方法为String类型的,表达式应为::
execution(java.lang.String com.xyz.service..*.*(..))
如果只拦截第一个输入参数为String类型的方法,表达式应为::
execution(* com.xyz.service..*.*(java.lang.String,..))
如果只拦截非void返回类型的方法,表达式应为::
execution(!void  com.xyz.service..*.*(..))
分享到:
评论

相关推荐

    Spring面向切面编程AOP

    面向切面编程(AOP,Aspect Oriented Programming)是Spring框架中的一个重要特性,它提供了一种模块化和声明式的方式来处理程序中的横切关注点,如日志、事务管理、安全控制等。AOP的核心概念包括切面、通知、连接...

    Chapter 6_ 使用Spring进行面向切面编程(AOP)

    面向切面编程(AOP)提供另外一种角度来思考程序结构,通过这种方式弥补了面向对象编程(OOP)的不足。 除了类(classes)以外,AOP提供了 切面。切面对关注点进行模块化,例如横切多个类型和对象的事务管理。

    AOP_使用spring框架进行面向切面编程

    面向切面编程(AOP)是一种编程范式,它旨在减少代码中的重复部分,特别是那些与核心业务逻辑无关但又必须处理的交叉关注点,如日志、事务管理、安全控制等。Spring框架是Java领域中实现AOP的常用工具,它通过提供...

    spring使用动态代理面向切面编程(AOP) xml

    在Spring框架中,面向切面编程(AOP)是一种强大的设计模式,它允许开发者将关注点分离,将横切关注点(如日志、事务管理、权限检查等)与核心业务逻辑解耦。本篇文章将深入探讨如何使用Spring的动态代理机制实现AOP...

    Spring-aop面向切面编程实例

    面向切面编程(Aspect-Oriented Programming,AOP)是Spring框架的核心特性之一,它提供了一种优雅的方式来处理系统的横切关注点,如日志、事务管理、性能监控和权限控制等。在Spring中,AOP主要通过代理模式实现,...

    面向切面编程aop简介

    面向切面编程(AOP,Aspect Oriented Programming)是Spring框架的重要组成部分,它提供了一种在不修改原有业务代码的基础上,插入额外功能的编程模型。Spring AOP使得开发者能够更方便地实现如日志记录、事务管理、...

    Spring AOP面向切面三种实现

    在IT行业中,Spring框架是Java企业级应用开发的首选,其强大的功能之一就是AOP(面向切面编程)。本文将详细解析Spring AOP的三种实现方式,帮助你深入理解这一重要概念。 首先,理解AOP的基本概念至关重要。AOP是...

    面向切面 aop

    面向切面编程(AOP,Aspect Oriented Programming)是一种编程范式,旨在将系统中的关注点分离,使得代码更加模块化,易于维护和扩展。在传统的面向对象编程(OOP)中,业务逻辑往往与日志、事务管理、权限控制等横...

    Spring切面AOP编程的简单模拟实现

    在Spring框架中,AOP(面向切面编程)是一种强大的设计模式,它允许开发者将关注点从核心业务逻辑中分离出来,例如日志记录、事务管理等。本教程将通过模拟Spring AOP来阐述如何实现一个简单的切面编程。我们将讨论...

    Spring4AOP 面向切面编程实例之方法拦截

    Spring4AOP 面向切面编程实例之方法拦截实例 一下利用Spring4的最后一个版本Spring4.3.9,实现简单的方法拦截实例。 Eclipse 建立java工程,导入必要的jar包,工程目录如下:

    spring的aop切面编程实例

    Spring AOP(面向切面编程)是Spring框架的重要组成部分,它允许我们在不修改源代码的情况下对应用程序的行为进行统一管理和控制。在本实例中,我们将深入探讨如何使用AspectJ技术和XML配置来实现AOP。 首先,了解...

    JAVA Spring AOP面向切面编程笔记

    JAVA Spring AOP面向切面编程笔记

    spring AOP切面编程

    Spring AOP(Aspect Oriented Programming,面向切面编程)是Spring框架的重要组成部分,它扩展了传统的面向对象编程,使得开发者可以方便地实现横切关注点,如日志、事务管理、性能监控等。在Spring中,AOP通过代理...

    面向切面编程

    ### 面向切面编程(AOP) 面向切面编程(Aspect-Oriented Programming,简称AOP)是一种编程范式,它旨在提高模块化程度,通过将横切关注点(cross-cutting concerns)从业务逻辑中分离出来,使得代码更加清晰、...

    spring切面AOP所使用的jar包

    Spring框架的AOP(面向切面编程)是其核心特性之一,它允许开发者在不修改原有代码的情况下,通过切面来插入额外的功能,比如日志记录、事务管理、性能监控等。在Spring AOP中,主要涉及到两个重要的库:...

    SpringAOP切面编程依赖jar包.rar

    学习Spring开发的AOP面向切面编程时所需要的jar包,包括com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar

    spring aop 自定义切面示例

    在Spring AOP(面向切面编程)中,自定义切面是实现业务逻辑解耦、增强代码可维护性的重要手段。AspectJ是一个强大的面向切面的编程库,它提供了与Spring AOP集成的能力,使我们可以编写更为灵活和模块化的代码。...

    spring4 AOP 面向切面编程@Aspect

    在Spring框架中,AOP(面向切面编程)是一种强大的设计模式,它允许开发者将关注点从业务逻辑中分离出来,比如日志记录、事务管理、权限检查等。`@Aspect`是Spring AOP的核心注解,用于定义一个切面。下面我们将详细...

    Spring mvc mybatis plus 实现AOP 切面日志系统

    在IT行业中,Spring MVC、MyBatis Plus以及AOP(面向切面编程)是Java Web开发中的重要组件,常用于构建高效、灵活的企业级应用。本项目“Spring MVC Mybatis Plus 实现AOP 切面日志系统”旨在提供一个基础的日志...

Global site tag (gtag.js) - Google Analytics