第一种
导入AOP相关坐标(依赖冲突解决办法,将依赖中版本号删除,springboot会自动匹配合适的版本 )
<dependencies>
<!--spring核心依赖,会将spring-aop传递进来-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
<!--aspect: 切面, AOP;aspect-java -> aspectj-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名。类/接口名.方法名(参数)异常名
execution(public User com.itheima.service.UserService.findById(int))
**- 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
- 访问修饰符:public,private等,可以省略
- 返回值
- 包名
- 类/接口名
- 方法名
- 参数
- 异常名:方法定义中抛出指定异常,可以省略**
**- AOP切入点表达式
- 可以使用通配符描述切入点,快速描述
- :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现*
execution(public * com.itheima.*.UserService.find*(*))
匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法。
… : 多个连续的任意符号,可以独立出现,常用语简化包名与参数的书写(参数一般使用…)
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
+:专用于匹配子类类型
execution(* *..*Service+.*(..))
总结:
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
* execution(* com.itheima.service.*Service.*(..))
切入点表达式描述通配符
作用:用于快速描述,范围描述
* : 匹配任意符号(常用)
.. : 匹配多个连续的任意符号(常用)
+ : 匹配子类类型
自定义注解的三个属性
1、@Target ElementType是一个枚举类型,它规范了注解的使用位置;
public enum ElementType {
/** 类, 接口 (包括注解类型), 或 枚举 声明 */
TYPE,
/** 字段声明(包括枚举常量) */
FIELD,
/** 方法声明(Method declaration) */
METHOD,
/** 正式的参数声明 */
PARAMETER,
/** 构造函数声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注解类型声明 */
ANNOTATION_TYPE,
/** 包声明 */
PACKAGE,
/**
* 类型参数声明
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 使用的类型
*
* @since 1.8
*/
TYPE_USE
}
2、@Retention
RetentionPolicy这个枚举类型的常量描述保留注解的各种策略,它确定了注解的存活时间
public enum RetentionPolicy {
/**
* 注解只在源代码级别保留,编译时被忽略
*/
SOURCE,
/**
* 注解将被编译器在类文件中记录
* 但在运行时不需要JVM保留。这是默认的
* 行为.
*/
CLASS,
/**
*注解将被编译器记录在类文件中
*在运行时保留VM,因此可以反读。
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
3、@Documented
这个注解表明将会被javadoc记录,如果类型声明被这个注解了,它将成为公共API的一部分。
第二步:在启动类上开启aop注解开发
第三步: 定义一个切面类
package com.test.easycodeauto.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
//设置切入点,@Pointcut注解要求配置在方法上方 监控业务层所有的方法
@Pointcut("execution(* com.test.easycodeauto.service.*Service.*(..))")
private void pt(){}
@Pointcut("execution(* com.test.easycodeauto.service.StudentService.queryById(..))")
private void pt1(){}
@Pointcut("execution(* com.test.easycodeauto.controller.StudentController.check*(..))")
private void pt2() {
}
@Around("pt2()")
public Object aroundCheck(ProceedingJoinPoint jpj) throws Throwable {
// 去除web层的参数空格
Object[] args = jpj.getArgs();
System.out.println(Arrays.toString(args));
for (int i = 0; i < args.length; i++) {
if(args[i].getClass().equals(String.class)) {
args[i] = args[i].toString().trim();
}
}
Object proceed = jpj.proceed(args);
return proceed;
}
/**
* 通过 JoinPoint 来拿参数,around通知 使用ProceedingJoinPint
*/
// @Before("pt1()")
public void before(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice...");
}
// @After("pt1()")
public void after(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("after advide ...");
}
// @Around("pt1()")
public Object around(ProceedingJoinPoint jpj) throws Throwable {
Object[] args = jpj.getArgs();
System.out.println(Arrays.toString(args));
args[0] = "02";
Object proceed = jpj.proceed(args);
System.out.println("around advide ...");
return proceed;
}
// @AfterReturning("pt1()")
public void afterReturning() {
}
// @AfterThrowing("pt1()")
public void afterThrowing() {
}
//设置在切入点pt()的前面运行当前操作(前置通知)
@Around("pt()")
public void method(ProceedingJoinPoint pjp) throws Throwable {
// 获取执行签名信息
Signature signature = pjp.getSignature();
// 通过签名获取执行操作类型(接口名)
String className = signature.getDeclaringTypeName();
// 通过签名获取执行操作类型(方法名)
String methodName = signature.getName();
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("测试业务侧接口"+className +"."+ methodName +"万次执行效率: "+ (end - start) + "ms");
}
}
第二种:基于自定义注解开发
第一种方式:
自定义注解
package com.test.easycodeauto.customaop.aop;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAop {
String value() default "";
}
定义一个切面类
package com.test.easycodeauto.customaop.aop;
import com.test.easycodeauto.customaop.domain.Dept;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.HashMap;
@Component
@Aspect
@Slf4j
public class TestAopAspect {
@Pointcut("@annotation(com.test.easycodeauto.customaop.aop.TestAop)")
public void annotationPointcut(){}
/**
* 前置增强
* @param jp
*/
// @Before("annotationPointcut()")
public void beforePointCut(JoinPoint jp) {
// 此处进入方法前,可以实现一些业务逻辑
log.info("before...");
}
/**
* 后置增强
* @param jp
*/
// @After("annotationPointcut()")
public void afterPointCut(JoinPoint jp) {
// 此处进入方法,可以实现一些业务逻辑
log.info("after...");
}
@Around("annotationPointcut()")
public Object doAround(ProceedingJoinPoint jp) throws Throwable {
log.info("around...");
// 获取这个方法的注解的方法实例
MethodSignature signature = (MethodSignature) jp.getSignature();
Method method = signature.getMethod();
// 获取这个注解的信息
TestAop testAop = method.getAnnotation(TestAop.class);
// 输出这个方法上这个注解的属性信息
log.info(testAop.toString());
// 获取传入的参数
Object[] args = jp.getArgs();
// 获取传入的参数名称,是有序的数组对象
String[] argNames = signature.getParameterNames();
// 这个把参数名称和参数匹对
HashMap<String,Object> params = new HashMap<>();
for (int i = 0; i < argNames.length; i++) {
params.put(argNames[i],args[i]);
}
//通过使坏来改变参数一的值,即name的值
args[0] = "666";
// 通过反射的方式获取属性信息
Object proceed = jp.proceed(args);
// 通过使坏来改变运行结果
proceed = "Helo Wo";
return proceed;
}
}
使用方式:
package com.test.easycodeauto.customaop.service.impl;
import com.test.easycodeauto.customaop.aop.TestAop;
import com.test.easycodeauto.customaop.domain.Dept;
import com.test.easycodeauto.customaop.service.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class DeptServiceImpl implements DeptService {
@Override
@TestAop(value="TEST_AOP")
public String testAop(String name, Dept dept) {
log.info(name);
log.info(dept.toString());
log.info("testAop");
return "testAop";
}
}
第二种方式:
自定义一个注解:
package com.test.easycodeauto.customaop.aop;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyOperationLog {
// 方法名称
String methodName() default "";
// 当前操作人
String currentUser() default "";
// 操作
String operate() default "";
}
定义一个切面类:
package com.test.easycodeauto.customaop.aop;
import com.test.easycodeauto.customaop.domain.MyOperationLogVo;
import com.test.easycodeauto.customaop.service.LogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Slf4j
@Component
@Aspect
public class MyLogAspect {
@Autowired
private LogService logService;
// 定义一个切入点
@Pointcut("@annotation(com.test.easycodeauto.customaop.aop.MyOperationLog)")
public void pt(){}
// 定义一个通知
@Before("pt()")
public void deBefore(JoinPoint joinPoint) {
log.info("before...");
// 获取自定义注解@MyOperationLog
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (null == method) {
return;
}
MyOperationLog myOperationLog = method.getAnnotation(MyOperationLog.class);
// 获取签名
String signa = joinPoint.getSignature().toString();
// 获取方法名
String methodName = signa.substring(signa.lastIndexOf(".") + 1, signa.indexOf("("));
String methodName1 = joinPoint.getSignature().getName();//获取目标方法的名称;
// 获取方法的execution
String longTemp = joinPoint.getStaticPart().toLongString();
//获取目标类的名称
String classType = joinPoint.getTarget().getClass().getName();
try{
Class<?> clazz = Class.forName(classType);
Method[] methods = clazz.getDeclaredMethods();
for (Method method1 : methods) {
if(method1.isAnnotationPresent(MyOperationLog.class)&& method1.getName().equals(methodName)) {
// 封装对象,可以进行一些业务逻辑
// 解析
MyOperationLogVo myOperationLogVos = parseAnnotation(method1);
// 日志添加
logService.addLog(myOperationLogVos);
}
}
}catch (Exception e) {
e.printStackTrace();
}
}
private MyOperationLogVo parseAnnotation(Method method) {
MyOperationLog myOperationLog = method.getAnnotation(MyOperationLog.class);
if (null == myOperationLog) {
return null;
}
MyOperationLogVo myOperationLogVo = new MyOperationLogVo();
myOperationLogVo.setMethodName(myOperationLog.methodName());
myOperationLogVo.setCurrentUser(myOperationLog.currentUser());
myOperationLogVo.setOperate(myOperationLog.operate());
return myOperationLogVo;
}
}
使用方式:
@RequestMapping("/controller")
@MyOperationLog(methodName = "testLog",currentUser = "admin",operate = "查询")
public String testLog() {
return "log";
}