Spring AOP
aop:面向切面编程(Aspect Oriented Programming)
spring实现aop使用的是代理模式
代理
What is?
例如:微商,简单来说就是代替厂家卖商品
厂家对于用户是不可见的,而代理商又扩大了厂家的销售途径
Why use?
将上面的厂家和代理商进行抽象,厂家就是目标类代理商就是代理类
这样可以隐藏目标类的具体实现
还可以在不修改目标类的代码的情况下对其功能进行增强
具体的静态代理,和动态代理的实现什么的,等以后在细究底层吧,现在看的真是一愣一愣的不是很明白
AOP介绍
介绍
面向切面编程,就是将交叉的业务逻辑封装成切面,利用AOP的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的,与主业务逻辑无关的代码,如安全检查,事务,日志等。
不是用AOP,则会出现代码纠缠,即交叉业务逻辑于主业务逻辑混合在一起。这样会使主业务逻辑变得混杂不清。
基本术语
- 切面
- 切面泛指交叉业务逻辑。常用的切面有通知和顾问,实际上就是对主业务罗杰的一种增强
- 织入
- 织入是指将切面代码插入到目标对象的过程
- 连接点
- 连接点指切面可以织入的位置
- 切入点
- 切入点指切面具体织入的位置
- 通知(Advice)
- 切面的一种实现,可以完成简单的织入功能(织入功能就是在这里完成的)。
- 顾问(Advisor)
- 顾问是切面的另一种实现,能将通知更为复杂的织入目标对象中,事故将通知包装为更复杂切面的装配器。不仅制定了切入的时间点,还可以指定具体的切入点
基于Schema-based通知
spring对aop的实现
前置通知(MethodBeforeAdvice)
创建切面,前置通知类
前置通知类需要实现MethodBeforeAdvice接口
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知的before执行");
}
}
参数 | 作用 |
---|---|
method | 目标方法 |
args | 目标方法的参数列表 |
target | 目标对象 |
xml配置注册目标类,注册切面,注册代理
注册目标类
<bean id="someServiceImpl" class="top.byfree.service.impl.SomeServiceImpl"/>
注册切面
<bean id="myMethodBeforeAdvice" class="top.byfree.aspects.MyMethodBeforeAdvice"/>
注册代理
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--指定目标对象-->
<property name="target" ref="someServiceImpl"/>
<!--指定目标类的实现所有接口-->
<property name="interfaces" value="top.byfree.service.SomeService"/>
<!--指定切面-->
<property name="interceptorNames" value="myMethodBeforeAdvice"/>
</bean>
注册代理property标签name属性值的含义
值 | 含义 |
---|---|
target | 目标对象,可以理解位之前注册的目标类 |
interfaces | 目标类实现的所有接口,可能有多个,就得用多个标签声明了 |
interceptorNames | 指定切面,就是我们之前注册的那个切面 |
后置通知(AfterReturningAdvice)
创建切面,后置通知类
实现AfterReturningAdvice接口
public class MyAfterReturningAdvice implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("后置通知的afterReturning()方法执行目标方法的返回值:" + returnValue );
if (returnValue != null) {
System.out.println(((String)returnValue).toUpperCase());
}
}
}
参数 | 作用 |
---|---|
returnValue | 返回值 |
method | 目标方法 |
args | 目标方法的参数列表 |
target | 目标 |
xml配置与上前置通知一致,只需要换成后置通知类即可
环绕通知(MethodInterceptor)
创建切面,环绕通知类
实现MethodInterceptor接口
public class MyMethodInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕通知,目标方法执行之前");
// 调用执行目标方法
Object result = methodInvocation.proceed();
if (result != null) {
result = ((String) result).toUpperCase();
}
System.out.println("环绕通知,目标方法执行之后");
return result;
}
}
参数 | 作用 |
---|---|
methodInvocation | 方法调用器 |
methodInvocation.proceed() | 执行目标方法 |
return | 这里的返回值会替换源目标方法的返回值 |
xml配置一致,不在赘述
异常通知
创建切面,异常通知类
实现ThrowsAdvice接口
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception e) {
System.out.println("异常通知执行!");
}
}
没有参数,只有目标类有异常时会执行
xml配置一致,不在赘述
基于AspectJ通知(XML配置方式实现)
环境搭建
引入maven依赖
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId> org.aspectj</groupId >
<artifactId> aspectjweaver</artifactId >
<version> 1.6.11</version >
</dependency>
添加配置文件dtd约束
<?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: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.xsd">
切入点表达式
用于标识切入点,通俗点讲我理解的是匹配要增强的方法
execution(
[modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
[declaring-type-pattern] 全限定类名
name-pattern(param-pattern) 方法名(参数名)
[throws-pattern] 抛出异常类型
)
解释:
[]包括的表示可以省略,没有[]的是不可以省略的部分
名称 | 作用 | 示例 |
---|---|---|
[modifiers-pattern] | 访问权限类型 | public,private~ |
ret-type-pattern | 返回值类型 | void,int~ |
[declaring-type-pattern] | 全限定类名 | top.byfree.service~ |
name-pattern(param-pattern) | 方法名(参数名) | toDo(int n)~ |
[throws-pattern] | 抛出异常类型 | IOException~ |
可以使用下面的符号进行通配:
符号 | 意义 |
---|---|
* | 0到多个任意字符 |
.. | 在方法参数中,表示多个参数。在包名后,表示当前包及其子包的路径 |
+ | 用在类名后,表示当前类及其子类。接口后表示当前接口及其子接口 |
基本步骤
1. 创建切面类
public class MyAspect2 {
// 前置通知
public void before () {
System.out.println("前置通知");
}
// 前置通知带参
public void before2 (JoinPoint jt) {
System.out.println("带参数的前置通知" + jt);
}
// 后置通知
public void afterReturning (Object result) {
System.out.println("后置通知方法执行目标方法的返回值是:" + result);
}
// 环绕通知
public Object around (ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知,目标方法执行前");
String result = (String)pjp.proceed();
System.out.println("之后");
if (result != null) {
result = result.toUpperCase();
}
return result;
}
// 异常通知
public void throwing (Exception e) {
System.out.println("异常信息" + e);
}
// 最终通知
public void after () {
System.out.println("最终通知执行");
}
}
2. 在切面类中创建各种通知方法
上述代码
细节
前置通知的JoinPoint参数,可以得到的是我们写入的切入点表达式
后置通知的Object参数,代表目标方法的返回值
环绕通知的ProceedingJoinPoint参数,可以执行目标方法
环绕通知的return,这个return会代替目标方法的return
异常通知的Exception参数,代表的是异常信息
3. xml文件配置切面
<!--注册目标方法bean-->
<bean id="someServiceImpl" class="top.byfree.service.impl.SomeServiceImpl"/>
<!--注册切面-->
<bean id="myAspect" class="top.byfree.aspects.MyAspect2"/>
<!--使用aop配置-->
<aop:config>
<!--定义切入点-->
<aop:pointcut id="doSomePc" expression="execution(* *..service.*.doSome(..))"/>
<aop:pointcut id="doOtherPc" expression="execution(* *..service.*.doOther(..))"/>
<aop:aspect ref="myAspect">
<!--前置通知-->
<aop:before method="before" pointcut-ref="doSomePc"/>
<!--带参数的通知方法执行-->
<aop:before method="before2" pointcut-ref="doSomePc"/>
<!--后置通知-->
<aop:after-returning method="afterReturning" returning="result" pointcut-ref="doOtherPc"/>
<!--环绕通知-->
<aop:around method="around" pointcut-ref="doOtherPc"/>
<!--异常通知-->
<aop:after-throwing method="throwing" pointcut-ref="doSomePc" throwing="e"/>
<!--最终通知-->
<aop:after method="after" pointcut-ref="doSomePc"/>
</aop:aspect>
</aop:config>
<aop:config>
标签的子标签
标签 | 作用 |
---|---|
<aop:advisor> | 配置顾问的,目前不深入这部分 |
<aop:aspect> | 配置通知切面 |
<aop:pointcut> | 用来声明切入点,注意这个也可以不直接声明。在aspect的标签的子标签pointcut属性写入也可以 |
<aop:aspect>
标签的子标签
标签 | 作用 |
---|---|
<aop:beforer> | 配置前置通知 |
<aop:after-returning> | 配置后置通知 |
<aop:around> | 配置环绕通知 |
<aop:after-throwing> | 配置异常通知 |
<aop:after> | 配置最终通知 |
各种通知配置标签的属性
属性 | 作用 |
---|---|
method | 指定切面方法 |
pointcut | 指定切入点,直接写切面表达式 |
pointcut-ref | 链接之前声明的切入点,写入<aop:pointcut> 的id属性 |
arg-names | 设置目标方法的参数获取,还需要在<aop:pointcut> 或者pointcut属性的切面表达式后添加参数信息,具体看下述 |
基于AspectJ通知(注解开发方式)
基本步骤
1. 创建切面类
@Aspect
public class MyAspect {
}
使用@Aspect表示该类是一个切面类
2. 在切面类中创建具体的通知方法
@Before("execution(* *..service.*.doSome(..))")
public void before () {
System.out.println("前置通知方法执行");
}
这里演示的是前置通知使用@Before
注解传入参数是value值是一个切入点表达式,因为只有一个参数所以这里省略了value
注解 | 含义 |
---|---|
@Before | 前置通知 |
@AfterReturning | 后置通知 |
@Around | 环绕通知 |
@AfterThrowing | 异常通知 |
@After | 最终通知 |
上述注解的参数问题
第一个参数是一个切入点表达式
这个切入点表达式可以在这里书写,也可以引用切入点方法
使用@Pointcut注解一个切入点方法
@Pointcut(value = "execution(* *..service.*.jojo(..)) && args(a)", argNames = "a")
public void pointcut (int a) {}
引用切入点方法
@Before(value = "pointcut(a)", argNames = "a")
public void before (int a) {
System.out.println("前置通知方法执行" + a);
}
第二个参数是argNames:用来匹配切入点表达式里面的args配和获取目标方法的参数用的
这里的@Pointcut教程上没有讲,我查寻了资料,理解为一种代码复用的方法,声明一个切入点方法后,其他的通知就可以直接引用了。
Q.E.D.