Spring AOP面向切面编程介绍及实例
首先再说AOP之前,可以先去了解一些代理模式,Spring的AOP功能就是基于JDK动态代理和Cglib代理实现的,关于代理模式可以去看我的这篇文章——代理模式
简介
AOP,简单的来说就是面向切面编程,它的全称是Aspect Oriented Programming,它能够将我们的业务逻辑和横切的问题进行分离(横切问题和我们业务逻辑关系不大),达到解耦的目的,使代码的重用性和开发效率提高。
通过上面这张图,AOP是环绕在我们业务层代码外的一个模块,AOP中也细分了很多部件,我们常说的切点,切面等都是它内部的组成部分。
AOP组成部分
- 切面( Aspect):它其实就是一个类,也就是我们切面需要完成的功能,我们可以通过一个注解声明一个切面类。
- 通知(Advice):通知有五种:前置通知、后置通知、环绕通知、异常抛出通知和返回通知,这里就将它们理解为切面里的方法,也就是说我们定义的这个代理类需要做什么,什么时候去做。
- 切入点(Pointcut):对连接点进行过滤,匹配出需要执行的连接点(Joint point),在这些连接点上织入通知(Adice),通俗的讲就是对符合条件的连接点进行代理。
- 连接点(Joint point):可以理解为我们需要代理的目标类中所有可以能的方法
- 目标对象(Target):也就是需要被代理的对象
通知(Advice) 的类型
前置通知(before)
:在 join point 前被执行的 advice后置通知(after)
:在一个 join point 正常返回后执行的 advice环绕通知(around)
:在 join point 前和 joint point 后都执行的 advice异常抛出通知(after throwing advice)
:当一个 join point 抛出异常后执行的 advice返回通知(after(final) advice)
:无论一个 join point 是正常退出还是发生了异常, 都会被执行的 advice
AOP一般应用于:日志,事务,权限,缓存(session)等
AOP使用示例
对于AOP的使用方法,我这边详细介绍下通过注解的方式,它还有通过实现官方接口或者自定义切面类的方式来实现,但是在实际开发过程中,推荐使用注解方式。
简单示例
切面类:
package com.example.demo.spring.aop.aspect; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; /**
* @author: sun*n
* @create: 2020-08-09 00:57
* @description: 通过注解方式实现AOP--这里主要讲述他们的简单应用
*/ //标记切面类的处理优先级,值越小,优先级别越高; //可以注解类,也能注解到方法上 @Order(1) @Aspect @Slf4j //这里有个坑,spring boot扫描不会自动注入@Aspect这个注解的类,所有需要手动加上, //如果不加就不会进行代理,因为@Aspect只会代理IOC容器内的对象 @Component public class TestLogAspect { //通过切入点:可以通过|| && 方式进行组合 // @Pointcut("execution(* com.example.demo.spring.aop.service..*.*(..)) " + " || execution(* com.example.demo.spring.aop.service.TestService.query())") @Pointcut("execution(* com.example.demo.spring.aop.controller.TestController.*(..))") private void beforePointCut(){} @Pointcut("execution(* com.example.demo.spring.aop.service..*.*(..))") private void afterPointCut(){} @Pointcut("execution(* com.example.demo.spring.aop.service.TestService.*(..))") private void aroundPointCut(){} /**
* 对于通知Advice也可以自己定义切入点规则,不需要通过自定义切入点
*/ // @Before("execution(* com.example.demo.spring.aop.controller.TestController.*(..))") // @Before("execution(* com.example.demo.spring.aop.service..*.*(..))") @Before(value = "beforePointCut()") public void logBefore(){ log.info("方法调用前打印日志"); } /**
* 最终通知
*/ @After(value = "afterPointCut()") public void logAfter(){ log.info("方法调用后打印日志"); } /**
* aroud和其他的有点区别,他需要在内部调用被代理类的方法
* @param joinPoint
* @return
*/ // @Around(value = "aroundPointCut()") @Around(value = "execution(* com.example.demo.spring.aop.controller.TestController.*(..))") public Object logAround(ProceedingJoinPoint joinPoint) throws NoSuchMethodException { System.out.println("----Around----"); //获得目标对象的class Class<?> targetClass = joinPoint.getTarget().getClass(); //获得方法参数类型 Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getParameterTypes(); //获得目标方法的入参 Object[] args = joinPoint.getArgs(); //获得目标对象的方法名字 String methodName = joinPoint.getSignature().getName(); Method method = targetClass.getMethod(methodName, parameterTypes); //获取目标方法 log.info("我调用的方法名字是 : "+ methodName); log.info("method is : " + method); for (int i = 0; i < args.length; i++) { log.info("方法的参数是 : "+args[i]); } try { //执行目标类方法:这个是核心的 Object result = joinPoint.proceed(); //获得方法的执行结果 log.info("result: " + result); } catch (Throwable throwable) { throwable.printStackTrace(); } return "Around"; } /**
* AfterReturning(后置通知) 会在 After之后执行
*/ @AfterReturning(pointcut = "afterPointCut() || beforePointCut()") public void afterRun(){ log.info("执行 AfterReturning"); } /**
* 异常通知
* 注意: 使用@AfterThrowing与@Around时,这两个advice的切入点不能重合,如何这里@Around的切入点也是afterPointCut(),那么@AfterThrowing不会生效
*/ @AfterThrowing(throwing="throwable" , value="afterPointCut()") public void afterThrow(Throwable throwable){ log.info("执行 ---- AfterThrowing"); } }
控制器:
package com.example.demo.spring.aop.controller; import com.example.demo.spring.aop.service.TestService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /**
* @author: sun*n
* @create: 2020-08-09 00:56
* @description: 控制器
*/ @RestController @Slf4j public class TestController { @Autowired private TestService testService; @RequestMapping(value = "/test1",method = RequestMethod.GET) public String test1(){ System.out.println("test"); System.out.println("----开始调用add方法----"); testService.add(27,"sun*n"); System.out.println("----调用add方法结束----"); return "hello world!"; } @RequestMapping(value = "/test2",method = RequestMethod.GET) public String test2(){ System.out.println("----开始调用query方法----"); try { testService.query("sun*n"); } catch (Exception e) { log.info("Controller 捕获异常"); } System.out.println("----调用query方法结束----"); return "hello world!"; } }
接口:
package com.example.demo.spring.aop.service; import java.util.Map; /**
* @author: sun*n
* @create: 2020-08-09 00:59
* @description: 业务层
*/ public interface TestService { public void add(int i,String name); public String query(String id); }
实现类:
package com.example.demo.spring.aop.service.impl; import com.example.demo.spring.aop.service.TestService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /**
* @author: sun*n
* @create: 2020-08-09 00:59
* @description: 业务实现类
*/ @Service @Slf4j public class TestServiceImpl implements TestService { @Override public void add(int i,String name) { log.info("----------------------------"); log.info("新增方法实现功能 " + i + " " + name); log.info("----------------------------"); } @Override public String query(String id){ log.info("----------------------------"); log.info("查询方法实现功能 " + id ); log.info("----------------------------"); //测试异常通知 //if(true){ // log.info("-----异常----"); // throw new ArrayIndexOutOfBoundsException("数组越界"); //} return "21545"; } }
结合注解示例
接下来展示通过结合注解来实现切面
切面类:
package com.example.demo.spring.aop.aspect; import com.alibaba.fastjson.JSONObject; import com.example.demo.spring.aop.annotation.Authority; import com.example.demo.spring.aop.annotation.Roles; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import java.lang.annotation.Annotation; import java.lang.reflect.Method; /**
* @author: sun*n
* @create: 2020-08-09 13:24
* @description: 角色注解切面处理类
*/ @Aspect @Slf4j @Component public class TestRoleAspect { @Pointcut("@annotation(com.example.demo.spring.aop.annotation.Authority)") private void authority(){} @Around(value = "authority()") public String advice(ProceedingJoinPoint joinPoint) throws Throwable{ String response = null; // 获得切入的 Method MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature(); // 获得方法 Method method = joinPointObject.getMethod(); RequestMapping requestMapping = method.getAnnotation(RequestMapping.class); RequestMethod[] methods =requestMapping.method(); //我这里为了方便,直接从入参中取出 Object[] args = joinPoint.getArgs(); //根据入参形式不同进行取参 // String params = null; // // GET请求 // if(methods.length > 0 && requestMapping.method()[0] == RequestMethod.GET){ // params = getRequestParams(method,args); // }else{ // if(args != null && args.length > 0){ // params = (String) args[0]; // } // } // 通过session的方式 // HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); // String role = (String) request.getSession().getAttribute("role"); Authority annotation = method.getAnnotation(Authority.class); Roles[] roles = annotation.role(); System.out.println("----------------------"); if ("".equals(args[0])) { return "没有权限"; } if (checkRole(roles, (String) args[0])){ return "没有权限"; } System.out.println("----------------------"); // 执行切面方法 try{ response = (String) joinPoint.proceed(); }catch (Throwable throwable){ } return response; } private String getRequestParams(Method requestMethod,Object[] requestArgs){ String str = ""; if (requestArgs == null || requestArgs.length == 0){ return null; } Annotation[][] paramsAns = requestMethod.getParameterAnnotations(); if (paramsAns.length > 0){ for (int i = 0; i < paramsAns.length; i++) { Annotation[] paramAns = paramsAns[i]; if (paramAns != null && paramAns.length > 0){ for (int j = 0; j < paramAns.length; j++) { if(paramAns[j] instanceof RequestParam){ str = (String) requestArgs[i]; break; } } } } } return str; } private static boolean checkRole(Roles[] roles,String role){ boolean flag = true; for (int i = 0; i < roles.length; i++) { if (role.equals(roles[i].getRoleName())){ System.out.println(roles[i]); flag = false; break; } } return flag; } }
注解类:
package com.example.demo.spring.aop.annotation; import java.lang.annotation.*; /**
* @author: sun*n
* @create: 2020-08-09 11:28
* @description: 注解类--权限
*/ @Inherited @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Authority { /**
* 角色
* @return
*/ Roles[] role(); }
角色枚举:
package com.example.demo.spring.aop.annotation; /**
* @author: sun*n
* @create: 2020-08-09 13:22
* @description: 角色
*/ public enum Roles { MANAGER("manager"), ROOT("root"), TOURIST("Tourist"), NORMAL("normal"); private String roleName; Roles(String roleName) { this.roleName = roleName; } public String getRoleName() { return roleName; } }
控制器:
package com.example.demo.spring.aop.controller; import com.example.demo.spring.aop.annotation.Authority; import com.example.demo.spring.aop.annotation.Roles; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /**
* @author: sun*n
* @create: 2020-08-09 11:26
* @description: 测试通过注解来实现面向切面编程
*/ @RestController @Slf4j public class TestAnnotationController { @Authority(role= Roles.ROOT) @RequestMapping(value = "/annotation1",method = RequestMethod.GET) public String test1(@RequestParam("role") String role){ log.info("----我来啦 1----"); return "hello Annotation1!"; } @Authority(role= Roles.TOURIST) @RequestMapping(value = "/annotation2",method = RequestMethod.GET) public String test2(@RequestParam("role") String role){ log.info("----我来啦 2----"); return "hello Annotation2!"; } @Authority(role= {Roles.TOURIST,Roles.MANAGER}) @RequestMapping(value = "/annotation3",method = RequestMethod.GET) public String test3(@RequestParam("role") String role){ log.info("----我来啦 3----"); return "hello Annotation3!"; } }
示例:
请求地址:http://localhost:8080/annotation3?role=manager 权限校验通过
请求地址:http://localhost:8080/annotation3?role=normal 权限校验不通过
其实,写了两个示例总结起来,只要能在切面获得请求参数,对请求参数进行处理,就能完成我们需要的功能,所有以后再面向切面编程的时候,我们需要注意如何取到入参,就基本能完成功能了。
注解说明
AOP常用注解
注解 | 说明 |
---|---|
@Aspect | 把当前类声明为切面类 |
@Before | 表示在切入点执行前需要进行的操作或者需要执行的方法 |
@After | 表示在切入点执行后,进行哪些操作 |
@AfterReturning | 表示在切入点方法处理成功后才会进行操作 |
@AfterThrowing | 表示在切入点出现异常后,进行哪些操作;参数:throwing与pointcut/value |
@Around | 既可以在切入目标方法之前进行操作,也可以在切入目标方法之后织入进行操作;参数:ProceedingJoinPoint |
@Pointcut | 切入点,对规则内的类进行代理 |
@EnableAspectJAutoProxy | 开启注解切面;参数:proxyTargetClass 默认false采用JDK动态代理,true采用Cglib代理 |
@ControllerAdvice | 全局异常处理、全局数据绑定、全局数据预处理 |
对于@Pointcut 规则表达式说明:
表达式关键词 | 说明 | 示例 |
---|---|---|
execution | 最常用!用来匹配方法,方法使用全限定名,包括访问修饰符(public/private/protected)、返回类型,包名、类名、方法名、参数,其中返回类型,包名,类名,方法,参数是必须的 | @Pointcut(“execution(* com.example.demo.spring.aop.service…* . * (…))”):第一个通配符匹配所有返回值类型,第二个匹配这个类里的所有类,第三个匹配这个类里的所有方法,()括号表示参数列表,括号里的用两个点号表示匹配任意个参数,包括0个 |
within | 和execution差不多,可以用来匹配某个包下面所有类的方法(包括子包下面的所有类方法) | @Pointcut(“within(com.example.demo.spring.aop.service…*)”):织入这个包下面的所以方法 |
this | 如果我们需要代理的类没有实现任何接口,或者:proxyTargetClass设为true时,这时应该使用this | @Pointcut(“this(com.example.demo.spring.aop.controller.TestController)”) |
target | 与this的区别就是,targer是使用JDK动态代理时用的,而this是使用Cglib时使用的,两者用法一样 | @Pointcut(“target(com.example.demo.spring.aop.controller.TestController)”) |
@annotation | 常用!这个指示器匹配那些有指定注解的连接点 | @Pointcut("@annotation(com.example.demo.spring.aop.annotation.LogAnnotation)") |
args | 该函数接收一个类名,表示目标类方法入参对象是指定类(包含子类)时,切点匹配。(可以去了解以下@args用法) | @Pointcut(“args(com.example.demo.spring.Person)”) |
自定义注解
@Inherited | 当@InheritedAnno注解加在某个类A上时,假如类B继承了A,则B也会带上该注解 |
---|---|
@Target |
注解的作用目标: @Target(ElementType.TYPE)——接口、类、枚举、注解 @Target(ElementType.FIELD)——字段、枚举的常量 @Target(ElementType.METHOD)——方法 `@Target(ElementType.PARAMETER)——方法参数 @Target(ElementType.CONSTRUCTOR) ——构造函数 @Target(ElementType.LOCAL_VARIABLE)——局部变量 @Target(ElementType.ANNOTATION_TYPE)——注解 @Target(ElementType.PACKAGE)——包 |
@Retention |
注解的保留位置 RetentionPolicy.SOURCE:这种类型的Annotations只在源代码级别保留,编译时就会被忽略,在class字节码文件中不包含。 RetentionPolicy.CLASS:这种类型的Annotations编译时被保留,默认的保留策略,在class文件中存在,但JVM将会忽略,运行时无法获得。 RetentionPolicy.RUNTIME:这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用。 |
@Document | 说明该注解将被包含在javadoc中 |
以上就是Spring AOP的一些简单应用,这章就介绍到这,后面有时间,会对AOP进行详细的介绍,先挖个坑。
本文地址:https://blog.csdn.net/qq_34754162/article/details/107897623
上一篇: 【Data structures】283题_移动零
下一篇: Java 常见的异常总结