前面几篇分别介绍了AOP是什么,AOP实现的内部原理,SpringAOP和AspectJ之间的关系。只有在初识AOP的末尾通过一个demo展示了SpringAOP,并未在SpringAOP的配置和实现上做过多的停留,本篇将介绍一下SpringAOP的配置和说明。
1.基本术语
1.通知(Advice)
通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。
1.前置通知[Before advice]:在连接点前面执行,前置通知不会影响连接点的执行,除非此处抛出异常。使用注解@Before
2.正常返回通知[After returning advice]:在连接点正常执行完成后执行,如果连接点抛出异常,则不会执行。
使用注解@AfterReturning(value="execution(* com..*.*(..))",returning="result"),这里的result只是一个参数名,一定要有
3.异常返回通知[After throwing advice]:在连接点抛出异常后执行。 使用注解@AfterThrowing(value="execution(* com..*.*(..))",throwing="ex"),这里的ex也是一个参数名,要有
4.返回通知[After (finally) advice]:在连接点执行完成后执行,不管是正常执行完成,还是抛出异常,都会执行返回通知中的内容。使用注解@After
5.环绕通知[Around advice]:环绕通知围绕在连接点前后,比如一个方法调用的前后。这是最强大的通知类型,能在方法调用前后自定义一些操作。环绕通知还需要负责决定是继续处理join point(调用ProceedingJoinPoint的proceed方法)还是中断执行。使用注解@Around
2.连接点(Joinpoint)
程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。
3.切入点(Pointcut)
通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定
4.切面(Aspect)
通知和切入点共同组成了切面:时间、地点和要发生的“故事”。
5.引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)
6.目标(Target)
即被通知的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事(AOP让他做爱做的事)
7.代理(proxy)
应用通知的对象,详细内容参见设计模式里面的代理模式
8.织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
1.编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
2.类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
3.运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术
2.SpringAOP实现
1.经典的基于代理的AOP
接口
package com.jd.aop4;
public interface EatAble {
public void eat();
}
实现
package com.jd.aop4;
public class IamEat implements EatAble{
public void eat() {
System.out.println("我吃饭");
}
}
代理
package com.jd.aop4;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
public class EatHelper implements MethodBeforeAdvice,AfterReturningAdvice{
public void afterReturning(Object arg0, Method arg1, Object[] arg2,Object arg3) throws Throwable {
System.out.println("吃完饭漱口。。。");
}
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("吃饭前洗手。。。");
}
}
配置文件
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<bean id="eatHelper" class="com.jd.aop4.EatHelper"></bean>
<bean id="iamEat" class="com.jd.aop4.IamEat"></bean>
<bean id="eatPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*eat"/>
</bean>
<bean id="eatHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="eatHelper"/>
<property name="pointcut" ref="eatPointcut"/>
</bean>
<bean id="myProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="iamEat"/>
<property name="interceptorNames" value="eatHelperAdvisor" />
<property name="proxyInterfaces" value="com.jd.aop4.EatAble" />
</bean>
</beans>
测试
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class EatTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext(new String[]{"springaop4.xml"});
EatAble eat = (EatAble)ac.getBean("myProxy");
eat.eat();
}
}
结果
吃饭前洗手。。。
我吃饭
吃完饭漱口。。。
2.@AspectJ注解驱动的切面
package com.jd.aop;
/**
* 计算器接口
*/
public interface Calculator {
public int add(int a,int b);
}
package com.jd.aop;
import org.springframework.stereotype.Component;
@Component
public class CalculatorImpl implements Calculator{
public int add(int a, int b) {
int result = a + b;
return result;
}
}
import java.util.Arrays;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 日志切面
* 1)把该类放入到ioc容器@Component@Aspect,声明切面
* 2)制定该切面在哪些方法之前执行@before
* 3)指定切面的优先级,值越小优先级越高
*/
@Order(1)
@Aspect
@Component
public class LoggerAspect {
@Before("execution(* com..*.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(name+" invoke beforeMethod ..logger..,args:"+args);
}
@After("execution(* com..*.*(..))")
public void afterMethod(JoinPoint joinPoint){
String name = joinPoint.getSignature().getName();
List<Object> args = Arrays.asList(joinPoint.getArgs());
System.out.println(name+" invoke afterMethod ..logger..,args:"+args);
}
}
这中方案是在每个方法上面都写一个表达式,也可以在类中定义一个切点方法,在其他方法上面引用该切点方法,如下:
/**
* 如果每个方法上面都配置切点,重复性太多,所以定义一个切入点
* 拦截的切入点方法,注解的在方法级别之上,但是不执行方法体,只表示切入点的入口
*/
@Pointcut(value="execution(* com..*.*(..))")
public void pointCut(){
}
//使用定义的切入点,不同包下需要添加包名和类型如:com.jd.aop2.LoggerAspectJAnnotation.pointCut()
@Before("pointCut()")
private void testPointCut(){
System.out.println("pointCut....");
}
配置文件
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- 配置包扫描 -->
<context:component-scan base-package="com.jd.aop"></context:component-scan>
<!-- 使AspectJ注解起作用 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试
public class AopTest {
private ApplicationContext ac = null;
private Calculator calculator = null;
{
ac = new ClassPathXmlApplicationContext("springaop.xml");
calculator = ac.getBean(Calculator.class);
}
@Test
public void testAop(){
System.out.println("计算开始");
int result = calculator.add(4, 2);
System.out.println("计算结束");
// System.out.println("==>result:"+result);
}
}
结果
计算开始
add invoke beforeMethod ..logger..,args:[4, 2]
计算结果:6
add invoke afterMethod ..logger..,args:[4, 2]
计算结束
3.注入式AspectJ切面
package com.jd.aop5;
public interface Computer {
int add(int a,int b);
}
package com.jd.aop5;
public class ComputerImpl implements Computer{
public int add(int a, int b) {
System.out.println("计算结果:"+a+b);
return a+b;
}
}
package com.jd.aop5;
public class LoggerAspectJXml {
public void myBeforeMethod(){
System.out.println("myBeforeMethod ....");
}
public void myAfterMethod(){
System.out.println("myAfterMethod ....");
}
}
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<bean id="computer" class="com.jd.aop5.ComputerImpl"></bean>
<bean id="loggerAspectJXml" class="com.jd.aop5.LoggerAspectJXml"></bean>
<!-- 配置Aop -->
<aop:config>
<!-- 配置切点表达式 -->
<aop:pointcut expression="execution(* com..*.*(..))" id="mypointcut"/>
<!-- 配置切面及通知 -->
<aop:aspect ref="loggerAspectJXml" order="1">
<aop:before method="myBeforeMethod" pointcut-ref="mypointcut"/>
<aop:after method="myAfterMethod" pointcut-ref="mypointcut"/>
</aop:aspect>
<!-- 配置其他切面及通知 -->
</aop:config>
</beans>
测试
//测试通过xml配置文件的形式
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("springaop5.xml");
Computer computer = (Computer)ac.getBean("computer");
int add = computer.add(1, 2);
System.out.println(add);
}
myBeforeMethod ….
计算结果:12
myAfterMethod ….
3
3.切点函数
切点函数可以定位到准确的横切逻辑位置,在前面的示例中我们只使用过execution(* com.zhangguo.Spring052.aop02.Math.*(..)),execution就是一个切点函数,但该函数只什么方法一级,如果我们要织入的范围是类或某个注解则execution就不那么好用了,其实一共有9个切点函数,有不同的针对性。
@AspectJ使用AspectJ专门的切点表达式描述切面,Spring所支持的AspectJ表达式可分为四类:
方法切点函数:通过描述目标类方法信息定义连接点。
方法参数切点函数:通过描述目标类方法入参信息定义连接点。
目标类切点函数:通过描述目标类类型信息定义连接点。
代理类切点函数:通过描述代理类信息定义连接点。
常见的AspectJ表达式函数:
execution():满足匹配模式字符串的所有目标类方法的连接点
@annotation():任何标注了指定注解的目标方法链接点
args():目标类方法运行时参数的类型指定连接点
@args():目标类方法参数中是否有指定特定注解的连接点
within():匹配指定的包的所有连接点
target():匹配指定目标类的所有方法
@within():匹配目标对象拥有指定注解的类的所有方法
@target():匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解
this():匹配当前AOP代理对象类型的所有执行方法
最常用的是:execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)切点函数,可以满足多数需求。
1)execution(* com...(..))表达式
表示切点的位置,这里一步一步的演进看到表达式该怎么写:
指定具体的某一个方法切入
@Before("execution(public int com.jd.aop.CalculatorImpl.add(int, int))");
可以将方法名用*代替
@Before("execution(public int com.jd.aop.CalculatorImpl.*(int, int))");
可以用..代替参数
@Before("execution(public int com.jd.aop.CalculatorImpl.*(..))");
可以将public int修饰符和返回值类型用*代替
@Before("execution(* com.jd.aop.CalculatorImpl.*(..))");
可以用*和.代替包名
@Before("execution(* com..*.*(..))")
@Before("execution(* *..*.*(..))")
2)切点函数within
//within切点函数
//com.zhangguo.Spring052.aop03包下所有类的所有方法被切入
@After("within(com.jd.aop3.*)")
public void after(JoinPoint jp){
System.out.println("最后通知");
}
3)this切点函数
//this切点函数
//实现了IAm接口的代理对象的任意连接点
@After("this(com.jd.aop3.IAm)")
public void after(JoinPoint jp){
System.out.println("最终通知");
}
4)args切点函数
//args切点函数
//要求方法有两个int类型的参考才会被织入横切逻辑
@After("args(int,int)")
public void after(JoinPoint jp){
System.out.println("最终通知");
}
5)@annotation切点函数
先自定义一个可以注解在方法上的注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnno {
}
//@annotation切点函数
//要求方法必须被注解com.zhangguo.Spring052.aop03.MyAnno才会被织入横切逻辑
@After("@annotation(com.jd.aop3.MyAnno)")
public void after(JoinPoint jp){
System.out.println("最终通知");
}
@Component("strUtil")
public class StrUtil {
@MyAnno
public void show(){
System.out.println("Hello StrUtil!");
}
}
通过这篇,SpringAOP常用的一些配置方式和参数的配置基本够用了。
推荐一篇文章:https://www.cnblogs.com/best/p/5736422.html