天天看点

SpringAOP详解--配置文件配置--注解配置

目录

Spring AOP中的基本概念:

SpringAOP入门案例:

Spring的五大通知类型:

SpringAOP的注解使用方式:

Spring AOP中的基本概念:

连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中,一个连接点总是表示一个方法的执行。

解释:

层于层之间调用的过程中,目标层中可供调用的方法,就称之为连接点。

切入点(Pointcut):匹配连接点的断言。通知和一个切入点表达式关联,并在满足这个切入点的连接点上运行(例如,当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是AOP的核心:Spring缺省使用AspectJ切入点语法。

解释:

在连接点的基础上 增加上切入规则 选择出需要进行增强的切入点 这些基于切入规则选出来的连接点 就称之为切入点。

切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用基于模式)或者基于@Aspect注解的方式来实现。

解释:

狭义上就是 当spring拦截下切入点后 将这些切入点 交给 处理类 进行功能的增强,这个处理类就称之为切面。

广义上来讲 将spring底层的代理 切入点 和 处理类 加在一起 实现的 对层与层之间调用过程进行增强的机制 称之为切面。

通知(Advice):在切面的某个特定的连接点上执行的动作。其中包括了“around”、“before”和“after”等不同类型的通知(通知的类型将在后面部分进行讨论)。许多AOP框架(包括Spring)都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。

解释:

在spring底层的代理拦截下切入点后,将切入点交给切面类,切面类中就要有处理这些切入点的方法,这些方法就称之为通知(也叫增强 增强方法)。针对于切入点执行的过程,通知还分为不同的类型,分别关注切入点在执行过程中的不同的时机。

目标对象(Target Object): 被一个或者多个切面所通知的对象。也被称做被通知(advised)对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个被代理(proxied)对象。

解释:

就是真正希望被访问到的对象。spring底层的动态代理对他进行了代理,具体能不能真的访问到目标对象,或在目标对象真正执行之前和之后是否做一些额外的操作,取决于切面。

切入规则:制定一系列规则,进行连接点的筛选,筛选出切入点。

SpringAOP详解--配置文件配置--注解配置

SpringAOP入门案例:

创建切面类,并定义切面方法(通知):

@Component

public class Aspect {

public void before(){

System.out.println("FBB经纪人打开了话筒");

}

}
           

在applicationContext.xml文件中配置切面:

<!-- 配置 切入点和切入规则-->

             <aop:config>

                     <!-- expression: 配置切入规则  id:定义切入点名称-->

                     <aop:pointcut expression="within(com.spring.aop.Fbb)" id="f"/>

                     <!-- 配置切面  ref:切面类的bean id-->

                     <aop:aspect ref="aspect">

                             <!-- method:切面类中方法的名称  pointcut-ref:需要绑定的切入点-->

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

                     </aop:aspect>

             </aop:config>
           

获取被调用者bean,执行相应的方法:

@Service

public class Fbb {

public void song(int x){

System.out.println("FBB在唱歌!!"+x);

}

}

@Test

public void test1(){

ApplicationContext context=new ClassPathXmlApplicationContext("applictionContext.xml");

Fbb f=(Fbb)context.getBean("fbb");

f.song(1);

}
           

输出结果:

FBB经纪人打开了话筒

FBB在唱歌!!1

在入门案例中使用到了切入点表达式:--->详细会在后面章节仔细介绍

目前先知道within()为通过全路径名进行匹配包,和包中的类,是其进行AOP。

SpringAOP的原理(建议了解代理模式,可先行查看本博客代理模式介绍):

Spring会在用户获取对象时,生成目标对象的代理对象,之后根据切入点规则,匹配用户连接点,得到切入点,当切入点被调用时,不会直接去找目标对象,而是通过代理对象拦截之后交由切面中的指定的通知执行来进行拦截增强。

Spring生成代理对象规则,默认情况下,如果目标对象实现过接口,则采用java的动态代理,如果目标对象没有实现过接口,则采用JDK动态代理。开发者可以在Spring中进行配置,要求无论目标对象是否实现过接口,都强制使用cglib动态代理。

配置方式:<aop:config proxy-target-class="true"></aop:config>

Spring的五大通知类型:

前置通知

在目标方法执行之前执行的通知:

前置通知方法,可以没有参数,也可以额外接受一个JoinPoint,spring会自动将给对象传入,代表当前的连接点,通过该对象可以获取目标对象和目标方法相关的信息。

<!-- 配置 切入点和切入规则-->

             <aop:config>

                     <!-- expression: 配置切入规则  id:定义切入点名称-->

                     <aop:pointcut expression="within(com.spring.aop.Fbb)" id="f"/>

                     <!-- 配置切面  ref:切面类的bean id-->

                     <aop:aspect ref="aspect">

                             <!-- method:切面类中方法的名称  pointcut-ref:需要绑定的切入点-->

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

                     </aop:aspect>

             </aop:config>

           
//前置通知

//JoinPoint连接点对象

public void before(JoinPoint jp){

System.out.println("FBB经纪人打开了话筒,前置通知");

//获取执行方法名

String name=jp.getSignature().getName();

//获取执行类信息

Class class1 = jp.getTarget().getClass();

System.out.println("...."+name+","+class1);

}
           

注:如果接受JoinPoint,必须保证其为方法的第一个参数,否则报错

环绕通知:

在目标方法执行之前和之后都可以执行额外代码的通知。

在环绕通知中必须显式的调用目标方法,目标方法才会执行,这个显式调用时通过ProceedingJoinPoint来实现的,可以在环绕通知中接收一个此类型的形参,spring容器会自动将该对象传入,注意这个参数必须处在环绕通知的第一个形参位置。

**要注意,只有环绕通知可以接收ProceedingJoinPoint,而其他通知只能接收JoinPoint。

环绕通知需要返回返回值,否则真正调用者将拿不到返回值,只能得到一个null。

<aop:around method="around" pointcut-ref="f"/>
           
//环绕通知

public Object around(ProceedingJoinPoint pjp) throws Throwable  {

System.out.println("FBB经纪人将电话递给巧巧..around");

//获取目标方法的参数集合

Object[] args = pjp.getArgs();

System.out.println("参数:"+Arrays.toString(args));

Object o=pjp.proceed();

System.out.println("放下了话筒!around...");

return o;

}
           

环绕通知有 :

控制目标方法是否执行、有控制是否返回值、甚至改变返回值的能力

环绕通知虽然有这样的能力,但一定要慎用,不是技术上不可行,而是要小心不要破坏了软件分层的“高内聚 低耦合”的目标。

后置通知

在目标方法执行之后执行的通知。

在后置通知中也可以选择性的接收一个JoinPoint来获取连接点的额外信息,但是这个参数必须处在参数列表的第一个。

在后置通知中,还可以通过配置获取返回值

<aop:after-returning method="after" returning="msg" pointcut-ref="f"/>
           

//后置通知

public void after(JoinPoint jp,Object msg){

System.out.println(msg);

System.out.println("这个是后置通知");



}
           

异常通知

在目标方法抛出异常执行的通知,可以配置传入就收目标方法抛出的异常对象

配置方法:

抛出异常:

public void song(int x){

System.out.println("FBB在唱歌!!"+x);

x=x/0;

}
           
<aop:after-throwing method="throwing" pointcut-ref="f" throwing="e"/>
           
//异常通知

public void throwing(JoinPoint jp,Throwable e){

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

}
           

最终通知

是在目标方法执行之后执行的通知。和后置通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返回例如抛出异常,则后置通知不会执行。而最终通知无论如何都会在目标方法调用过后执行,即使目标方法没有正常执行完成。另外后置通知可以通过配置得到返回值,而最终通知无法得到。

最终通知也可以额外接收一个JoinPoint参数,来获取目标对象和目标方法相关信息,但一定要保证必须是第一个参数。

<aop:after method="afterz" pointcut-ref="f"/>
           
//最终通知

public void afterz(JoinPoint jp){

System.out.println("最终通知。。。。");

}

           

以上五种通知的执行顺序

1.在目标方法没有抛出异常的情况下:

前置通知

环绕通知的调用目标方法之前的代码

目标方法

环绕通知的调用目标方法之后的代码

后置通知

最终通知

2.在目标方法抛出异常的情况下:

前置通知

环绕通知的调用目标方法之前的代码

目标方法 抛出异常 异常通知

最终通知

3.如果存在多个切面:

多切面执行时,采用了责任链设计模式。

切面的配置顺序决定了切面的执行顺序,多个切面执行的过程,类似于方法调用的过程,在环绕通知的proceed()执行时,去执行下一个切面或如果没有下一个切面执行目标方法,从而达成了如下的执行过程:

SpringAOP详解--配置文件配置--注解配置

如果目标方法抛出异常:

SpringAOP详解--配置文件配置--注解配置

五种通知的常见使用场景:

前置通知 记录日志(方法将被调用)
环绕通知 控制事务 权限控制
后置通知 记录日志(方法已经成功调用)
异常通知 异常处理 控制事务
最终通知 记录日志(方法已经调用,但不一定成功)

SpringAOP的注解使用方式:

Spring也支持注解实现AOP,使实现方式更加简单:

<!-- 开启aop注解 -->

             <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
           

使用@Aspect标志该类是一个切面类。

SpringAOP详解--配置文件配置--注解配置

利用注释配置相应的切入点规则:

  1. 前置通知 @Before
  2. 环绕通知@Around
  3. 后置通知@AfterReturning
  4. 异常通知@AfterThrowing
  5. 最终通知@After

如果一个切面中多个通知使用同一个切入点表达式,则可以将该切入点表达式单独定义,来调用。

注:在当前切面中通过注解定义的切入点只在当前切面中器作用,器它切面看不到。

在后置通知的注解中,也可以额外配置一个returning属性,来指定一个参数名接受目标方法执行后的返回值

相同在异常通知的注解中也可以接受异常对象的名字

举例:

public class Aspect1 {

//配置切入点方法

@Pointcut("within(com.spring.aop.Fbb)")

public void pointcut(){}

//前置通知

//JoinPoint连接点对象

@Before("pointcut()")

public void before(JoinPoint jp){

System.out.println("FBB经纪人打开了话筒,前置通知");

//获取执行方法名

String name=jp.getSignature().getName();

//获取执行类信息

Class class1 = jp.getTarget().getClass();

System.out.println("...."+name+","+class1);

}

//环绕通知

@Around("pointcut()")

public Object around(ProceedingJoinPoint pjp) throws Throwable  {

System.out.println("FBB经纪人将电话递给巧巧..around");

//获取目标方法的参数集合

Object[] args = pjp.getArgs();

System.out.println("参数:"+Arrays.toString(args));

Object o=pjp.proceed();

System.out.println("放下了话筒!around...");

return o;

}

//后置通知

@AfterReturning(value="pointcut()",returning="msg")

public void after(JoinPoint jp,Object msg){

System.out.println(msg);

System.out.println("这个是后置通知");



}

//异常通知

@AfterThrowing(value="pointcut()",throwing="e")

public void throwing(JoinPoint jp,Throwable e){

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

}

//最终通知



public void afterz(JoinPoint jp){

System.out.println("最终通知。。。。");

}

}