约定编程 - Spring AOP
AOP概念
AOP其实是一种约定流程的编程,约定何时执行怎样的程序代码块
AOP最典型的应用可以是事务,也可以应用于日志,因为代码与业务实现无关
AOP可以减少大量重复代码,保持程序的整洁,但是缺点是可读性降低,不宜与理解
AOP术语
连接点:具体的被拦截对象,因Spring只支持方法,所以被拦截的对象往往是指特定方法
切点:有时我们的切面不单单应用于单个方法,亦可是多个类的不同方法,此时可通过正则表达式和指示器的规则去定义,从而适配连接点,切点则是这样的一个功能概念
通知:主要是用来查看执行流程和运行条件,分为前置通知、后置通知、环绕通知、异常通知、最终通知
目标对象:即被代理的对象
引入:引入新的类和方法,增强现有Bean的功能
织入:通过动态代理,为原有服务对象生成代理对象,拦截与切点定义匹配的连接点,按照约定将各类通知织入约定流程的过程
切面:一个可以定义切点、各类通知和引入的内容,Spring AOP利用其增强Bean功能或将对应方法织入流程
AOP开发
确定连接点
public interface UserService {
public void printUser(User user);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public void printUser(User user) {
if (null == user){
throw new RuntimeException("请检查参数是否为空。。。。");
}
System.out.println("userName:" + user.getUserName());
System.out.println("sex:" + user.getSex());
System.out.println("age:" + user.getAge());
}
}
开发切面
@Aspect
public class MyAspect {
@Before("execution(* com.szx.demo.aop.UserServiceImpl.printUser(..))")
public void before(){
System.out.println("MyAspect.before。。。。。");
}
@After("execution(* com.szx.demo.aop.UserServiceImpl.printUser(..))")
public void after(){
System.out.println("MyAspect.after。。。。。");
}
@AfterReturning("execution(* com.szx.demo.aop.UserServiceImpl.printUser(..))")
public void afterReturning(){
System.out.println("MyAspect.afterReturning。。。。。");
}
@AfterThrowing("execution(* com.szx.demo.aop.UserServiceImpl.printUser(..))")
public void afterThrowing(){
System.out.println("MyAspect.afterThrowing。。。。。");
}
}
@Aspect:声明切面注解
@Before:前置通知注解
@After:后置通知注解
@AfterReturning:最终知注解
@AfterThrowing:异常通知注解
切点定义
如上代码所示,每次通知定义,都需要写麻烦的链接点,代码有些冗余,使用切点可以很好的解决代码冗余
@Aspect
public class MyAspect {
@Pointcut("execution(* com.szx.demo.aop.UserServiceImpl.printUser(..))")
public void pointCut(){}
@Before("pointCut()")
public void before(){
System.out.println("MyAspect.before。。。。。");
}
@After("pointCut()")
public void after(){
System.out.println("MyAspect.after。。。。。");
}
@AfterReturning("pointCut()")
public void afterReturning(){
System.out.println("MyAspect.afterReturning。。。。。");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
System.out.println("MyAspect.afterThrowing。。。。。");
}
}
@Pointcut:用来定义切点
execution(* com.szx.demo.aop.UserServiceImpl.printUser(…)) 含义解释
execution 表示在执行的时候,拦截里面的正则表达式
*表示任意返回类型的方法
com.szx.demo.aop.UserServiceImpl 目标对象的全路径名称
printUser 指定目标对象的方法
(…) 对任意参数匹配
AspectJ关于Spring AOP切点指示器
项目类型 | 描述 |
---|---|
arg() | 限定连接点方法参数 |
@args() | 通过连接点方法参数上的注解进行限定 |
execution() | 用于匹配是连接点的执行方法 |
this() | 限制链接点匹配AOP代理Bean引用为指定的类型 |
target() | 目标对象(及被代理对象) |
@target() | 限制目标对象的配置了指定的注解 |
within | 限制连接点匹配指定的类型 |
@within() | 限定连接点带有匹配注解类型 |
@annotation() | 限定带有指定注解的连接点 |
execution(* com.szx.*.*.UserServiceImpl.printUser(..) && bean('userServiceImpl'))
&& :表示并且的意思
bean :对SpeingBean名称的限定,下文此处 @Service 注入到Bean,名称没有规定,默认为首字母小写(userServiceImpl)
@Service
public class UserServiceImpl implements UserService {...}
测试AOP
@SpringBootApplication
public class DemoApplication {
@Bean(name = "myAspect")
public MyAspect initMyAspect(){
return new MyAspect();
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
无论是否发生异常 after(后置通知)都会执行
环绕通知
@Around("pointCut()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("MyAspect.around.before。。。。。。。。");
proceedingJoinPoint.proceed();
System.out.println("MyAspect.around.after。。。。。。。。");
}
没啥特殊要求,不要使用环绕通知,有时会因版本问题出现通知位置异常
引入
在书写程序过程中,可能会抛出异常,如果我们在抛出之前检测,可以成功规避异常,程序可以顺利执行,比如当如果要输出的对象为空时,则会报异常,我们可以在输出之前判定是否为空,为空则不执行,所以引入则是为了增强接口功能
定义检测是否为空的接口类
public interface UserValidator {
public boolean validate(User user);
}
实现类
public class UserValidatorImpl implements UserValidator {
@Override
public boolean validate(User user) {
System.out.println("新的引入接口:" + UserValidator.class.getSimpleName());
return user != null;
}
}
AOP引入定义
@DeclareParents(value = "com.szx.demo.aop.UserServiceImpl",defaultImpl = UserValidatorImpl.class)
public UserValidator userValidator;
@DeclareParents:引入新的类增强服务
value:指向增强功能的对象
defaultImpl:引入增强功能的类
测试
@RequestMapping("/zengqiang")
@ResponseBody
public User validateAndPrintUser(String userName, String sex, String age){
User user = new User();
user.setUserName(userName);
user.setSex(sex);
user.setAge(age);
UserValidator userValidator = (UserValidator)userService;
if (userValidator.validate(user)){
userService.printUser(user);
}
return user; //加入断点
}
通知获取参数
给通知传递参数,只需要给切入点加入对应的正则表达式即可,对于非环绕通知还可以使用一个连接点类型的参数
前置通知增加参数传递
@Before("pointCut() && args(user)")
public void before(JoinPoint point, User user){
System.out.println("MyAspect.before。。。。。" + user.toString());
}
“pointCut() && args(user)”:pointCut()表示启用原来切点定义规则,并约定将连接点(目标对象方法)名称为 user 的参数传递进来
注:JoinPoint 类型的参数对非环绕通知而言,SpringAOP会自动将他传入通知中;对环绕通知而言,可使用 ProceedingJoinPoint 类型参数
织入
织入是一个生成动态代理对象并且将切面和目标对象方法编织成为约定流程的过程
上述所有即为织入过程
多个切面
public interface UserService {
public void printUser(User user);
public void manyAspects();
}
public class UserServiceImpl implements UserService {
@Override
public void printUser(User user) {
if (user == null){
throw new RuntimeException("请检查参数是否为空。。。。");
}
System.out.println("userName:" + user.getUserName());
System.out.println("sex:" + user.getSex());
System.out.println("age:" + user.getAge());
}
@Override
public void manyAspects() {
System.out.println("多个切面顺序。。。。。");
}
}
多个切面
@Aspect
public class MyAspect1 {
@DeclareParents(value = "com.szx.demo.aop.UserServiceImpl",defaultImpl = UserValidatorImpl.class)
public UserValidator userValidator;
@Pointcut("execution(* com.szx.demo.aop.UserServiceImpl.manyAspects(..))")
public void pointCut(){}
@Before("pointCut() && args(user)")
public void before(JoinPoint point, User user){
System.out.println("MyAspect1.before。。。。。" + user.toString());
}
@After("pointCut()")
public void after(){
System.out.println("MyAspect1.after。。。。。");
}
@AfterReturning("pointCut()")
public void afterReturning(){
System.out.println("MyAspect1.afterReturning。。。。。");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
System.out.println("MyAspect1.afterThrowing。。。。。");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("MyAspect1.around.before。。。。。。。。");
proceedingJoinPoint.proceed(); //回调目标对象原有方法
System.out.println("MyAspect1.around.after。。。。。。。。");
}
}
@Aspect
public class MyAspect2 {
@DeclareParents(value = "com.szx.demo.aop.UserServiceImpl",defaultImpl = UserValidatorImpl.class)
public UserValidator userValidator;
@Pointcut("execution(* com.szx.demo.aop.UserServiceImpl.manyAspects(..))")
public void pointCut(){}
@Before("pointCut() && args(user)")
public void before(JoinPoint point, User user){
System.out.println("MyAspect2.before。。。。。" + user.toString());
}
@After("pointCut()")
public void after(){
System.out.println("MyAspect2.after。。。。。");
}
@AfterReturning("pointCut()")
public void afterReturning(){
System.out.println("MyAspect2.afterReturning。。。。。");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
System.out.println("MyAspect2.afterThrowing。。。。。");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("MyAspect2.around.before。。。。。。。。");
proceedingJoinPoint.proceed(); //回调目标对象原有方法
System.out.println("MyAspect2.around.after。。。。。。。。");
}
}
@Aspect
public class MyAspect3 {
@DeclareParents(value = "com.szx.demo.aop.UserServiceImpl",defaultImpl = UserValidatorImpl.class)
public UserValidator userValidator;
@Pointcut("execution(* com.szx.demo.aop.UserServiceImpl.manyAspects(..))")
public void pointCut(){}
@Before("pointCut() && args(user)")
public void before(JoinPoint point, User user){
System.out.println("MyAspect3.before。。。。。" + user.toString());
}
@After("pointCut()")
public void after(){
System.out.println("MyAspect3.after。。。。。");
}
@AfterReturning("pointCut()")
public void afterReturning(){
System.out.println("MyAspect3.afterReturning。。。。。");
}
@AfterThrowing("pointCut()")
public void afterThrowing(){
System.out.println("MyAspect3.afterThrowing。。。。。");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("MyAspect3.around.before。。。。。。。。");
proceedingJoinPoint.proceed(); //回调目标对象原有方法
System.out.println("MyAspect3.around.after。。。。。。。。");
}
}
启动类配置
@Bean(name = "myAspect1")
public MyAspect1 initMyAspect1(){
return new MyAspect1();
}
@Bean(name = "myAspect2")
public MyAspect2 initMyAspect2(){
return new MyAspect2();
}
@Bean(name = "myAspect3")
public MyAspect3 initMyAspect3(){
return new MyAspect3();
}
以此看来,顺序并非如我们所想,不过可以使用注解来配置其执行顺序
@Order(1):表示第一个执行,可根据自己需要配置,好像最新版的执行顺序是按照 SpringBoot 启动类注入Bean时的顺序决定的,若不使用注解,可以按照自己的需要,于启动类中顺序注入Bean
还有另一种实现接口方法,感觉太麻烦,就不过多陈述,掌握注解即可