Java的AOP(三大组件之一)入门到+案例详解

本文详细介绍了AOP(面向切面编程)在SpringBoot中的应用,包括AOP的基本概念、通知类型(如环绕、前置、后置等)、切入点表达式,以及如何通过实际案例实现CRUD操作的日志记录。
摘要由CSDN通过智能技术生成
前言

作为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;
    }
6.实现效果

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值