目录
-
重要概念
-
切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类TestAspect所关注的具体行为,例如,AServiceImpl.barA()的调用就是切面TestAspect所关注的行为之一。“切面”在ApplicationContext中<aop:aspect>来配置。
-
连接点(Joinpoint) :程序执行过程中的某一行为,例如,UserService.get的调用或者UserService.delete抛出异常等行为。
-
通知(Advice) :“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志记录的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,例如ServiceAspect。
-
切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。例如,TestAspect中的所有通知所关注的连接点,都由切入点表达式execution(* com.spring.service.*.*(..))来决定。
-
目标对象(Target Object) :被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
-
AOP代理(AOP Proxy) :在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将 <aop:config>的 proxy-target-class属性设为true。
-
-
通知(Advice)类型:
-
前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在<aop:aspect>里面使用<aop:before>元素进行声明。例如,TestAspect中的doBefore方法。
-
后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在<aop:aspect>里面使用<aop:after>元素进行声明。例如,ServiceAspect中的returnAfter方法,所以Teser中调用UserService.delete抛出异常时,returnAfter方法仍然执行。
-
返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在<aop:aspect>里面使用<after-returning>元素进行声明。
-
环绕通知(Around advice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在<aop:aspect>里面使用<aop:around>元素进行声明。例如,ServiceAspect中的around方法。
-
抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。ApplicationContext中在<aop:aspect>里面使用<aop:after-throwing>元素进行声明。例如,ServiceAspect中的returnThrow方法。
-
-
AOP和OOP是面向不同领域的两种设计思想。
1
1、OOP(面向对象编程)针对问题领域中以及业务处理过程中存在的实体及其属性和操作进行抽象和封装,面向对象的核心概念是纵向结构的,其目的是获得更加清晰高效的逻辑单元划分;
2、AOP则是针对业务处理过程中的切面进行提取(操作日志、权限控制、性能监测等等))
2
AOP是OOP的延续和补充
3
-
Aspect切面
Bean中已经看到了,每个通知方法第一个参数都是JoinPoint
在Spring中,任何通知(Advice)方法都可以将第一个参数定义为 org.aspectj.lang.JoinPoint类型用以接受当前连接点对象。
JoinPoint接口如下:
1
getArgs()
返回方法参数
2
getThis()
返回代理对象
3
getTarget()
返回目标
4
getSignature()
返回正在被通知的方法相关信息
5
toString()
打印出正在被通知的方法的有用信息
-
Spring控制事务的方法
事务是数据库概念
a.基于XML配置(经常使用)
一旦配置好之后,类上不需要添加任何东西。
如果Action作为目标对象切入事务,需要在<aop:config>元素里添加proxy-target-class="true"属性。原因是通知Spring框架采用CGLIB技术生成具有事务管理功能的Action类。
b.基于注解(配置简单,经常使用)
在applicationContext.xml中开启事务注解配置。(applicationContext.xml中只需定义Bean并追加以下元素)
<bean id="txManager" class="...">
<property name="sessionFactory">
</property>
<tx:annotation-driven transaction-manager="txManager"/>
-
有两种使用Spring AOP的方式:XML配置文件 、 注解的方式
1、XML配置文件
https://www.jianshu.com/p/007bd6e1ba1b
2、注解方式
1
定义目标对象
1、定义接口
package com.bytebeats.spring4.aop.annotation.service; public interface BankService { boolean transfer(String from, String to, int amount); }
2、实现接口
package com.bytebeats.spring4.aop.annotation.service; import org.springframework.stereotype.Service; @Service public class BankServiceImpl implements BankService { @Override public boolean transfer(String from, String to, int amount) { if(amount<1){ throw new IllegalArgumentException("transfer amount must be a positive number"); } System.out.println("["+from+"]向["+to+ "]转账金额"+amount); return false; } }
2
定义切面
为了复用切点,推荐使用@Pointcut来定义切点,然后在 @Before、@After等注解中引用定义好的切点,代码如下:
package com.bytebeats.spring4.aop.annotation; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; @Order(1) @Aspect @Component public class TransferLogAdvice { @Pointcut("execution(* com.bytebeats.spring4.aop.annotation.service.BankServiceImpl.*(..))") public void pointcut1() { } /** * 前置通知:在方法执行前执行的代码 * @param joinPoint */ @Before(value = "pointcut1()") public void beforeExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); List<Object> args = Arrays.asList(joinPoint.getArgs()); System.out.println(this.getClass().getSimpleName()+ " before execute:"+methodName+ " begin with "+args); } /** * 后置通知:在方法执行后执行的代码(无论该方法是否发生异常),注意后置通知拿不到执行的结果 * @param joinPoint */ @After(value = "pointcut1()") public void afterExecute(JoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println(this.getClass().getSimpleName()+ " after execute:"+methodName+" end!"); } /** * 后置返回通知:在方法正常执行后执行的代码,可以获取到方法的返回值 * @param joinPoint */ @AfterReturning(value = "pointcut1()", returning="result") public void afterReturning(JoinPoint joinPoint, Object result){ String methodName = joinPoint.getSignature().getName(); System.out.println(this.getClass().getSimpleName()+ " afterReturning execute:"+methodName+" end with result:"+result); } /** * 后置异常通知:在方法抛出异常之后执行,可以访问到异常信息,且可以指定出现特定异常信息时执行代码 * @param joinPoint */ @AfterThrowing(value = "pointcut1()", throwing="exception") public void afterThrowing(JoinPoint joinPoint, Exception /**NullPointerException*/ exception){ String methodName = joinPoint.getSignature().getName(); System.out.println(this.getClass().getSimpleName()+ " afterThrowing execute:"+methodName+" occurs exception:"+exception); } /** * 环绕通知, 围绕着方法执行 */ @Around(value = "pointcut1()") public Object around(ProceedingJoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); System.out.println(this.getClass().getSimpleName()+ " around:"+methodName+" execute start"); Object result = null; try { result = joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println(this.getClass().getSimpleName()+ " around:"+methodName+" execute end"); return result; } }
3
启用注解扫描
<?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/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 自动扫描的包 --> <context:component-scan base-package="com.bytebeats.spring4.aop.annotation"> </context:component-scan> <!-- 启用AspectJ自动代理 --> <aop:aspectj-autoproxy /> </beans>
4
Tester
package com.bytebeats.spring4.aop.annotation; import com.bytebeats.spring4.aop.annotation.service.BankService; import com.bytebeats.spring4.aop.xml.service.UserService; import org.springframework.context.support.ClassPathXmlApplicationContext; public class SpringAopAnnotationApp { public static void main(String[] args) { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:spring-aop-annotation.xml"); BankService bankService = ctx.getBean(BankService.class); bankService.transfer("jordan", "kobe", 2000); System.out.println("*********************"); bankService.transfer("jordan", "kobe", 0); ctx.close(); } }
-
Spring AOP切入点表达式
切入点指示符用来指示切入点表达式目的,在Spring AOP中目前只有执行方法这一个连接点,表达式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
pattern的类型
modifier-pattern?
修饰符匹配(其中后面跟着“?”的是可选项)
ret-type-pattern
返回值匹配
declaring-type-pattern?
类路径匹配(其中后面跟着“?”的是可选项)
name-pattern
方法名匹配
param-pattern
参数匹配
throws-pattern?
异常类型匹配(其中后面跟着“?”的是可选项)
我们来看几个例子:
1、匹配所有方法execution(* *(..))
2、匹配 com.bytebeats.spring4.aop.xml.service.UserService中所有的公有方法
execution(public * com.bytebeats.spring4.aop.xml.service.UserService.*(..))
3、匹配com.bytebeats.spring4.aop.xml.service 包及其子包下的所有方法
execution(* com.bytebeats.spring4.aop.xml.service..*.*(..))
另外,@Pointcut 定义切点时,还可以使用 &&、|| 和 !,如下:
@Pointcut("execution(* com.bytebeats.mq.MqProducer.*(..))")
private void mqSender(){ }
@Pointcut("execution(* com.bytebeats.mq.MqConsumer.*(..))")
private void mqReceiver(){ }
@Pointcut("mqSender() || mqReceiver()")
private void mqTrace(){ }