前言
作为Java的三大组件之一的AOP,我们可以把某一些方法共性的一些功能统一进行抽取,定义在AOP程序中,程序运行时,动态的为原始业务方法进行增强 ,更加灵活、高效、简洁
1.AOP的认识
AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。
优点:1.减少重复代码2.代码无侵入3.提高开发效率4.维护方便
1.例如下面的例子.
当一个程序有冗余时,并且结构相同 我们便可以抽取出共同部分.
2.我们导入配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3. 我们定义一个Aspect类
@Component
@Aspect
@Slf4j
public class TimeAspect {
@Around("execution(* com.**************.impl.********Impl.*(..))")
public Object countTime(ProceedingJoinPoint pjp) throws Throwable {
long beginTime = System.currentTimeMillis();
Object result = pjp.proceed();
long endTime = System.currentTimeMillis();
System.out.println(endTime-beginTime);
return result;
}
}
通过一个案例简单了解AOP的用法.
2.AOP的概念及特点
1.AOP的概念
1. 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
5. 目标对象:Target,通知所应用的对象
2.AOP的通知类型
-
@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行
-
@Before:前置通知,此注解标注的通知方法在目标方法前被执行
-
@After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行
-
@AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行
-
@AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行
public class AspectAop { @Pointcut("execution(* com.*****.service.impl*******Impl.*(..))") public void pt(){ } @Before("pt()") public void before(){ System.out.println("之前aop1"); } @AfterReturning("pt()") public void afterReturing(){ System.out.println("执行之后,抛出异常不会执行aop1"); } @After("pt()") public void after(){ System.out.println("最后执行aop1"); } @AfterThrowing(value = "pt()",throwing = "e") public void afterTh(Throwable e){ e.printStackTrace(); System.out.println("异常通知增强aop1"); } }
3.AOP通知执行的顺序
在AOP中,通知执行的顺序和类名的顺序相同
AOP1
public class AspectAop1 {
@Pointcut("execution(* com.********.************Impl.*(..))")
public void pt(){
}
@Before("pt()")
public void before(){
System.out.println("之前aop1");
}
@AfterReturning("pt()")
public void afterReturing(){
System.out.println("执行之后,抛出异常不会执行aop1");
}
@After("pt()")
public void after(){
System.out.println("最后执行aop1");
}
@AfterThrowing(value = "pt()",throwing = "e")
public void afterTh(Throwable e){
e.printStackTrace();
System.out.println("异常通知增强aop1");
}
}
AOP2:
public class AspectAop2 {
@Pointcut("execution(* com.****************Impl.*(..))")
public void pt(){
}
@Before("pt()")
public void before(){
System.out.println("之前aop2");
}
@AfterReturning("pt()")
public void afterReturing(){
System.out.println("执行之后,抛出异常不会执行aop2");
}
@After("pt()")
public void after(){
System.out.println("最后执行aop2");
}
@AfterThrowing(value = "pt()",throwing = "e")
public void afterTh(Throwable e){
e.printStackTrace();
System.out.println("异常通知增强aop2");
}
}
执行顺序图
3.AOP切入点表达式
1.execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配
其语法为:execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
2.annotation
1.自定义切面类
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}
2.在业务中添加@annotation
@Override
@MyLog //自定义注解(表示:当前方法属于目标方法)
public void delete(Integer id) {
//1. 删除部门
deptMapper.delete(id);
}
3.添加通知
@Component
@Aspect
public class MyAspect {
//针对list方法、delete方法进行前置通知和后置通知
//前置通知
@Before("@annotation(com.itheima.anno.MyLog)")
public void before(){
log.info("MyAspect6 -> before ...");
}
4.实际案例(例子)
1.(实例)
当我们在实际工作中,需要对CRUD操作进行各项记录
(比如,当我们进行删除时,会记录操作人,操作时间.结束时间.等)方面管理员后续查看,在这样的场景下 就需要使用AOP切面)
下面一个实际案例
2.准备实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; //ID
private Integer operateEmpId; //操作人ID
private LocalDateTime operateTime; //操作时间
private String className; //操作类名
private String methodName; //操作方法名
private String methodParams; //操作方法参数
private String returnValue; //操作方法返回值
//页面展示字段
private String operateEmpName; //操作人姓名
}
2.Mapper层的查询数据库的sql
@Mapper
public interface OperateLogMapper {
//插入日志数据
@Insert("insert into operate_log (operate_emp_id, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
"values (#{operateEmpId}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
public void insert(OperateLog log);
}
3.@annotation
@Target(ElementType.METHOD)//当前注解的作用目标-指当前注解只能作用到方法上
@Retention(RetentionPolicy.RUNTIME)//设置该注解的生命周期,RUNTIME整个生命周期有效
public @interface Log {
}
4.切入
@Log
//修改数据
@PutMapping("/depts")
public Result save (@RequestBody Dept dept){
deptService.save(dept);
return Result.success();
}
5.Aspect类
@Component
@Aspect
@Slf4j
public class LogAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("execution(* com.*****.tliaswebmanagement.controller.*.*(..))&&" +
"@annotation(com.*****.tliaswebmanagement.annotation.Log)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.currentTimeMillis();
//创建一个log容器对象
OperateLog operateLog = new OperateLog();
//获取操作人id 从bean中获取
String token = request.getHeader("token");
//解析token
// String body = Jwts.parser()
// .setSigningKey(signKey)
// .parsePlaintextJws(token)
// .getBody();
Claims claims = JwtUtils.parseJwt(token);
//获取操作人id
Integer operateEmpId = (Integer) claims.get("id");
//操作时间
LocalDateTime operateTime = LocalDateTime.now();
//操作类名
String className = pjp.getTarget().getClass().getName();
//操作方法名
String methodName = pjp.getSignature().getName();
//操作方法参数
Object[] args = pjp.getArgs();
//操作方法返回值
Object result = pjp.proceed();
//结束时间
long end = System.currentTimeMillis();
//操作耗时
Long costTime=end-begin;
//存入数据
operateLog.setOperateEmpId(operateEmpId);
operateLog.setOperateTime(operateTime);
operateLog.setClassName(className);
operateLog.setMethodName(methodName);
operateLog.setMethodParams(Arrays.toString(args));
operateLog.setReturnValue(result.toString());
operateLog.setCostTime(costTime);
operateLogMapper.insert(operateLog);
return result;
}