概述
学习Spring无非学习Ioc(控制反转)和AOP(面向切面)两大核心功能,因为Ioc是Spring的精华,大家接触的多了也就对Ioc很熟悉了(主要也是因为Ioc比较好理解和容易使用)。不知道别人怎么样,我是每次学习AOP时候总是跳着看,觉得总是看不明白,可学习Spring永远躲不过AOP的掌握,前面章节刚把Java的动态代理理解透(传送门:Java动态代理的彻底理解(Java高级特性-动态代理)),建议理解了动态代理再来学习AOP,因为Spring AOP是通过动态代理来实现的,趁热把铁打,今天就把学习Spring怎么也躲不过去的AOP来彻底攻破吧。
一、AOP
1.1 AOP概述
AOP它是软件开发思想发展到一定高度的产物,是对OOP(面向对象)的有益补充,来处理面向对象无法实现的功能。它是有特定的应用场合的,它适合具有横切逻辑的应用场合,如性能检测、访问控制、事务管理及日志记录。
首先,在面向切面编程的思想里面,把功能分为核心业务功能和通用功能。
- 核心业务:比如登陆,增加数据,删除数据都叫核心业务
- 通用功能:比如性能统计,日志,事务管理等等
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发,然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP。
一句话来了解AOP:AOP是通过将业务逻辑抽取,在业务逻辑的前后不同位置插入通用逻辑功能,以实现业务和通用功能松耦合,达到减少开发、提高效率的一种软件开发思想。
1.2 AOP术语
AOP的几个重要术语,现在了解下,我会用自己所理解的语音解释清楚。
1.2.1 切面(Aspect)
切面就是在一个怎样的环境中工作,在动态代理中可以把它理解成一个拦截器,说白了就是一个类,在里面定义所要拦截的一些对象的条件、处理逻辑的方法等(其它术语都是定义在切面里的)。
1.2.2 通知(Adice)
通知(也有叫增强的)是定义在切面中的方法,就是将代理对象的真实方法调用前、后的顺序和逻辑区分开,白话就是定义几个方法,将这几个通知方法放在真实调用方法不同位置(前、后)和不同时机来调用。
- 前置通知( before ):在动态代理反射原有对象方法或者执行环绕通知前执行的通知功能。
- 后置通知( after ):在动态代理反射原有对象方法或者执行环绕通知后执行的通知功能。无论是否抛出异常,它都会被执行。
- 返回通知( afterReturning ):在动态代理反射原有对象方法或者执行环绕通知后执行的通知功能。
- 异常通知( afterThrowing ): 在动态代理反射原有对象方法或者执行环绕通知产生异常后执行的通知功能。
- 环绕通知( aroundThrowing ):在动态代理中,它可以取代当前被拦截对象的方法,通过参数或反射调用被拦截对象的方法。
1.2.3 引入(Introduction )
引入允许我们在现有的类里添加自定义的类和方法,他可以为类添加一个属性和方法,即使一个业务类原本没有实现某个接口,通过AOP的引入功能,也可以动态的的为该业务类添加接口的实现逻辑,让业务流成为这个接口的实现类。
1.2.4 切点(Pointcut)
在动态代理中,被切面拦截的方法就是一个切点,切面将可以将其切点和被拦截的方法按照一定的逻辑织入到约定流程当中。其实就是定义好哪些业务方法可以拦截,记住是业务的方法。
1.2.5 连接点(join point)
连接点是一个判断条件,由它可以指定哪些是切点。对于指定的切点,Spring 会生成代理对象去使用对应的切面对其拦截,否则就不会拦截它,记住是判断切点的条件。
1.2.6 织入(Weaving)
织入是将通知添加到目标方法的具体过程,也就是一个生成代理对象的过程。根据不同的技术AOP有3中织入方式:
- 静态代理,编译class文件时生成代码(编译期时产生),要求使用特殊的Java编译器;
- 通过ClassLoader(类加载器)在类加载时生成代码,要求使用特殊的类加载器;
- 动态代理,在运行期为目标类添加增强的方式,Spring AOP就是采用这种,并已JDK和CGLIB来生成的代理对象。
1.3 AOP的实现者
AOP因为是一种编程思想,所以不是Spring的特有的,它有很多实现
- AspectJ:语言级别AOP实现,定义了AOP语法,能再编译期提供横切代码的织入;
- AspectWerkz:是基于Java的简单、动态、轻量级的AOP框架,现已和AspectJ项目合并,扩展了AspectJ语言;
- JBoss AOP:作为JBoss应用程序服务器框架的扩展功能发布;
- Spring AOP:Spring AOP使用纯Java实现,不需要编译过程,也不需要类装载器,它再运行期通过代理方法想目标类织入增强代码。在Spring中可以无缝将Spring AOP、Ioc和AspectJ整合在一起。
二、Spring AOP的实现方法
Spring AOP是一种基于方法拦截的AOP,换句话说Spring只能支持方法拦截的AOP。在Spring中有4种方式可以实现AOP的拦截功能。
- 使用ProxyFactoryBean 和对应的接口实现AOP 。
- 使用XML配置AOP 。
- 使用@AspectJ注解驱动切面。
- 使用AspectJ 注入切面。
在Spring AOP拦截的4中方式中,其实真正常用的是@AspectJ注解的方式实现的切面,因为简单直观,所以本篇已注解的方式来做说明。
三、Spring AOP开发
说明:测试项目采用Spring Boot和Maven开发,引入AOP启动的依赖<artifactId>spring-boot-starter-aop</artifactId>
需求:我们有一个自己的“角色”业务,现在业务可以打印角色的信息。
3.1 定义Role实体类(id, name)(代码略)
3.2 定义角色接口RoleService,定义“打印角色”的方法,此方法就是切点,关键代码如下:
public interface RoleService {
public void printRole(Role role);
}
实现类RoleServiceImpl,关键代码如下:
package com.eyaoshun.aop.service;
@Service
public class RoleServiceImpl implements RoleService {
@Override
public void printRole(Role role) {
System.out.println("id" + role.getId() +
"name:"+role.getName() );
}
}
3.3 创建切面
选择好了切点就可以创建切面了,对于动态代理的概念而言, 它就如同一个拦截器,在Spring 中只要使用@Aspect 注解一个类,那么Spring IoC 容器就会认为这是一个切面了,代码如下:
@Aspect
public class RoleAspect {
@Pointcut("execution(* com.eyaoshun.aop.service.RoleServiceImpl.printRole(..))")
public void print(){}
@Before("print()")
public void before(){
System.out.println("before...");
}
@After("print()")
public void after() {
System.out.println("after...");
}
@AfterReturning("execution(* com.eyaoshun.aop.service.RoleServiceImpl.printRole(..))")
public void afterReturning(){
System.out.println("afterReturning...");
}
@AfterThrowing("execution(* com.eyaoshun.aop.service.RoleServiceImpl.printRole(..))")
public void afterThrowing() {
System.out.println("afterThrowing...");
}
}
解释:
@Pointcut--切点:其实就是定义条件,将哪些方法拦截,代码中使用的是表达式(execution)方式作为条件,内容是正则。因为写法问题连接点要注解在方法上,后面的所有“通知”方法可以复用这个pring()方法的形式来设置连接点,如下面的before、after通知方法的@Before("pring()")。当然你可以不定义@Pointcut注解,那么每个“通知”方法上都需要设置连接条件,如afterReturning、afterThrowing方法上都需要写全连接条件。
execution (* com.eyaoshun.aop.service.RoleServiceImpl.printRole( .. ) )
- execution : 代表执行方法的时候会触发。
- *: 代表任意返回类型的方法。
- com.eyaoshun.aop.service.RoleServiceImpl:代表类的全限定名。
- printRole : 被拦截方法名称。
- (..): 任意的参数。
@Before、@After、@AfterReturning、@AfterThrowing是“通知”的注解
注解 | 通知 | 备注 |
---|---|---|
@Before | 在被代理对象的方法前先调用 | 前宣通知 |
@Arond | 将被代理对象的方法封装起来,并用环绕通知取代它 | 环绕通知取代它原有方法,后续会讨论 |
@After | 在被代理对象的方法后调用 | 后置通知 |
@AfterReturning | 在被代理对象的方法正常返回后调用 | 返回通知,要求被代理对象的方法执行过程中没有发生异常 |
@AfterThrowing | 在被代理对象的方法抛出异常后调用 | 异常通知,要求被代理对象的方法执行过程中产生异常 |
3.4 测试AOP
先创建一个切面的Bean,通过注解的方式,代码如下
@Configuration
public class AopConfig {
@Bean
public RoleAspect getRoleAspect() {
return new RoleAspect();
}
}
测试类代码:
@RunWith(SpringRunner.class)
@SpringBootTest
public class AopApplicationTests {
@Autowired
private RoleService roleService;
@Test
public void contextLoads() {
Role role = new Role();
role.setId(1L);
role.setName("wtao");
roleService.printRole(role);
}
}
运行后,控制台输出结果,AOP生效了:
3.5 测试完上面了4个简单的“通知”,再回来看看“环绕通知”
环绕通知是SpringAOP 中最强大的通知, 它可以同时实现前置通知和后置通知。它保留了调度被代理对象原有方法的功能,所以它既强大, 又灵活。但是由于强大,它的可控制性不那么强,如果不需要大量改变业务逻辑, 一般而言并不需要使用它。
你注意到的是它接受ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的proceed()方法。
让我们在切面上添加环绕通知的方法,代码如下所示。
@Around("print()")
public void around(ProceedingJoinPoint jp) {
System.out.println("around before...");
try {
jp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("around after");
}
这样在一个切面里通过@Around 注解加入了切面的环绕通知,这个通知里有一个ProceedingJoinPoint 参数。这个参数是Spring 提供的,使用它可以反射切点方法,在加入反射切点方法后再次进行测试,可以得到下面的日志:
运行后,控制台输出结果,around通知生效了,注意执行的顺序:
3.6 给通知传递参数
在SpringAOP 各类通知中,除了环绕通知外,并没有讨论参数的传递,有时候我们还是希望能够传递参数的,为此本节介绍如何传递参数给AOP 的各类通知。用法很简单就是在通知方法的切点注解后面加上args(参数)即可。
如在之前的@Before改为如下因为@Before追加了args(role)的指示器,before就可以获取参数了。
@Before("execution(* com.eyaoshun.aop.service.RoleServiceImpl.printRole(..)) && args(role)")
public void before(Role role) {
System.out.println("before..." + role.getName());
}
3.7 引入
其实引入的概念是将原有的业务类动态的增强,可以增加属性,增加方法,不过使用场景不多,本篇不做介绍,想了解可以自行查找资料。
四、多个切面
上面的例子讨论了一个方法只有一个切面的问题,而事实是Spring 也能支持多个切面。当有多个切面时,在测试过程中发现它不会存在任何顺序,这些顺序代码会随机生成,但是有时候我们希望它按照指定的顺序运行。
实现它只需要在切面的类上加一个注解@Order(“顺序值”)即可,如@Order(1),@Order(2),这样在多个切面的情况下,就可以按顺序。
五、总结
AOP 是Spring 两大核心内容之一,通过AOP 可以将一些比较公用的代码抽取出来,进而减少开发者的工作量。理解AOP 有一定的困难,但是只要通过动态代理模式一步步进行测试和调试,就能掌握它。本篇介绍SpringAOP 的开发和原理,通过注解的方式演示了关于AOP 的切点、切面、连接点、通知等功能,希望大家重在理解AOP的原理和概念,才能在实际的项目中写出自己的AOP程序来。
(完)