首先再说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: sunzhinan
* @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: sunzhinan
* @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,"sunzhinan");
System.out.println("----调用add方法结束----");
return "hello world!";
}
@RequestMapping(value = "/test2",method = RequestMethod.GET)
public String test2(){
System.out.println("----开始调用query方法----");
try {
testService.query("sunzhinan");
} 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: sunzhinan
* @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: sunzhinan
* @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: sunzhinan
* @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: sunzhinan
* @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: sunzhinan
* @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: sunzhinan
* @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进行详细的介绍,先挖个坑。