AOP(Aspect Oriented Programming):即我们常说的面向切面编程。
什么是AOP?AOP是在我们原来写的代码的基础上,进行一定的包装,比如在方法执行前、方法返回后、方法抛出异常后等地方进行一定的拦截处理或者增强处理。我们需要实现一个代理来创建实例,实际运行的实例其实是生成的代理类的实例。
Spring的AOP和AspectJ?
springaop的底层实现有两种,一种是jdk的动态代理,另一种是cglib,springaop没有用到aspectj,只是借鉴了它并添加了aspectj风格的注解,使用aspectj必须用到它自己特殊的编译器和运行环境的插件。
spring aop只是用到了aspectj的配置而已,没有用它的编译器、也没有用它的类加载器。spring aop对于没有实现接口的类,会用cglib来动态生成子类,否则使用jdk自带的动态代理。
Spring AOP:首先要说明的是,这里的Spring AOP 是纯的 Spring 代码,和 AspectJ 没什么关系,但是 Spring 延用了 AspectJ 中的概念,包括使用了 AspectJ 提供的 jar 包中的注解,但是不依赖于其实现功能。@Aspect、@Pointcut、@Before、@After 等注解都是来自于 AspectJ,但是功能的实现是纯 Spring AOP 自己实现的。
基于@AspectJ注解的AOP配置
本文只介绍关于注解的方式,配置文件的方式来实现AOP不作分析
1. 首先引入AOP所依赖的jar包
<!-- AOP --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.8.RELEASE</version> </dependency>
其中包含2个必须的包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.8.RELEASE</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
如果使用了SpringBoot的话,直接添加如下依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2. 在 @AspectJ 的配置方式中,之所以要引入 aspectjweaver 并不是因为需要使用 AspectJ 的处理功能,而是因为 Spring 使用了 AspectJ 提供的一些注解,实际上还是纯的 Spring AOP 代码。明确一点,@AspectJ 采用注解的方式来配置使用 Spring AOP。
开启 @AspectJ 的注解配置方式,有两种方式:
一、使用xml方式:
<aop:aspectj-autoproxy/>
二、使用@EnableAspectJAutoProxy
/** * @author 70KG * @Title: Config * @Description: 配置类 * @date 2018/7/29下午2:01 * @From www.nmyswls.com */ @Configuration // 手动开启Aspect注解 @EnableAspectJAutoProxy public class Config { }
3. 采用@Configuration配置文件的方式来模拟spring上下文环境
/** * @author 70KG * @Title: MainTest * @Description: * @date 2018/7/29下午2:01 * @From www.nmyswls.com */ public class MainTest { public static void main(String[] args) { // 加载配置文件 AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class); BusinessCalculate businessCalculate = ac.getBean(BusinessCalculate.class); int result1 = businessCalculate.calculate(10, 5); System.out.println("==================================================="); RandomCalculate randomCalculate = ac.getBean(RandomCalculate.class); int result2 = randomCalculate.calculate(10, 5); } }
4.一旦开启了上面的配置,那么所有使用 @Aspect 注解的 bean 都会被 Spring 当做用来实现 AOP 的配置类,称之为一个 Aspect。
/** * description * * @author 70KG * @date 2018/7/30 */ @Aspect public class BeforeAdvice { }
Demo介绍:
1. 编写两个业务类(目的是为了测试不同的切入点)
/** * @author 70KG * @Title: BusinessCalculate * @Description: 业务计算类 * @date 2018/7/29下午2:01 * @From www.nmyswls.com */ public class BusinessCalculate { public int calculate(int i, int j) { int result = i / j; System.out.println("BusinessCalculate-业务方法执行。。。。。。"); return result; } }
/** * description * * @author 70KG * @date 2018/7/30 */ public class RandomCalculate { public int calculate(int i, int j) { int result = i / j; System.out.println("RandomCalculate-业务方法执行。。。。。。"); return result; } }
2. 编写切入点SystemArchitecture类(Spring建议是这个名字) (此类上面不需要加@Aspect)
/** * description 用来定义切入点的类 * * @author 70KG * @date 2018/7/30 */ public class SystemArchitecture { @Pointcut("bean(*domCalculate)") public void definitionPointCut(){} // execution(* *(..)) 所有方法 // 抽取公共表达式 // @Pointcut("execution(public int com.nmys.story.springCore.aopdemo.BusinessCalculate.*(..))") @Pointcut("execution(* com.nmys.story.springCore.aopdemo.BusinessCalculate.*(..))") // @Pointcut("bean(*Calculate)") public void pointCut() {} }
介绍一下切入点的表达式:
- within:指定所在类或所在包下面的方法(Spring AOP 独有)如 @Pointcut("within(com.nmys.story.service..*)")。
- @annotation:方法上具有特定的注解,如 @Subscribe 用于订阅特定的事件。如 @Pointcut("execution( .*(..)) && @annotation(com.nmys.story.Subscribe)")。
- bean(idOrNameOfBean):匹配 bean 的名字(Spring AOP 独有)如 @Pointcut("bean(*Service)")。
- execution 来正则匹配方法签名(根据业务需求来查阅一下表达式的具体写法)。
上面匹配中,通常 "." 代表一个包名,".." 代表包及其子包,方法参数任意匹配使用两个点 ".."。
3. 定义切面(切面最好不要都揉在一个类中,显得杂乱无章,建议分开写,如下)
/** * description * * @author 70KG * @date 2018/7/30 */ @Aspect public class BeforeAdvice { // 前置通知 @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()") public void logBefore(JoinPoint joinPoint) { // 获取传入的参数 Object[] args = joinPoint.getArgs(); for (int i = 0; i < args.length; i++) { System.out.println(args[i]); } System.out.println("调用方法之前执行logBefore。。。。。。"); } // 前置通知 @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()") public void randomBefore() { System.out.println("调用方法之前执行randomBefore。。。。。。"); } }
其中如果需要拿到方法的入参,则用JoinPoint类来获得
/** * description * * @author 70KG * @date 2018/7/30 */ @Aspect public class AfterAdvice { // 后置通知 @After("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()") public void logAfter() { System.out.println("调用方法之后执行logAfter。。。。。。"); } // 后置通知 @After("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()") public void randomAfter() { System.out.println("调用方法之后执行randomAfter。。。。。。"); } }
上面加上了@Aspect注解表明spring会将此bean当作切面来管理
介绍一下其他通知方法的写法:
/** * @author 70KG * @Title: LogAspect * @Description: 定义切面 * @date 2018/7/29下午2:00 * @From www.nmyswls.com */ // 声明是一个切面类 @Aspect public class LogAspect { // // 前置通知 // @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.pointCut()") // public void logBefore() { // System.out.println("调用方法之前执行logBefore。。。。。。"); // } // // // 前置通知 // @Before("com.nmys.story.springCore.aopdemo.SystemArchitecture.definitionPointCut()") // public void randomBefore() { // System.out.println("调用方法之前执行randomBefore。。。。。。"); // } // // 后置通知 // @After("pointCut()") // public void logAfter() { // System.out.println("调用方法之后执行logAfter。。。。。。"); // } // // // 正常返回通知(抛出异常则不会执行) // @AfterReturning("pointCut()") // public void logReturn() { // System.out.println("方法正常返回结果后执行logReturn。。。。。。"); // } // // // 异常通知 // @AfterThrowing("pointCut()") // public void logException() { // System.out.println("logException。。。。。。"); // } // // @Around("pointCut()") // public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { // // 比前置通知提前执行 // System.out.println("环绕通知 - around 执行目标方法之前。。。。。。"); // // 利用反射机制来调用目标方法 // Object proceed = proceedingJoinPoint.proceed(); // System.out.println("环绕通知 - around 执行目标方法之后。。。。。。"); // return proceed; // } }
@Around():环绕通知在实际中不常用,proceed()方法实际是用了反射来调用目标方法,上面的具体注释很清楚。
4. 编写config类
/** * @author 70KG * @Title: Config * @Description: 配置类 * @date 2018/7/29下午2:01 * @From www.nmyswls.com */ @Configuration // 手动开启Aspect注解 @EnableAspectJAutoProxy public class Config { // 将@Aspect修饰的类和业务类都交给spring来管理 @Bean public BeforeAdvice beforeAdvice() { return new BeforeAdvice(); } @Bean public AfterAdvice afterAdvice() { return new AfterAdvice(); } @Bean public BusinessCalculate businessCalculate() { return new BusinessCalculate(); } @Bean public RandomCalculate randomCalculate() { return new RandomCalculate(); } }
需要将业务类和切面类都交给spring来管理,同时需要开启Aspect(@EnableAspectJAutoProxy)
5. 编写测试类
/** * @author 70KG * @Title: MainTest * @Description: * @date 2018/7/29下午2:01 * @From www.nmyswls.com */ public class MainTest { public static void main(String[] args) { // 加载配置文件 AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
// 从容器中取出bean实例 BusinessCalculate businessCalculate = ac.getBean(BusinessCalculate.class); int result1 = businessCalculate.calculate(10, 5); System.out.println("==================================================="); RandomCalculate randomCalculate = ac.getBean(RandomCalculate.class); int result2 = randomCalculate.calculate(10, 5); } }
6. 运行结果:
10 5 调用方法之前执行logBefore。。。。。。 BusinessCalculate-业务方法执行。。。。。。 调用方法之后执行logAfter。。。。。。 =================================================== 调用方法之前执行randomBefore。。。。。。 RandomCalculate-业务方法执行。。。。。。 调用方法之后执行randomAfter。。。。。。
以上结果表明对目标方法进行了增强。
最后再说一下,以上使用的是Spring的AOP,和AspectJ基本没什么关系。
用惯了markdown不知道博客园如何排版了,可以访问下面的网址看排好版的。