AOP详细讲解和实例代码演示

一.什么是AOP,由浅极深的感受AOP的好处

AOP英文全称:Aspect Oriented Programming(面向切面编程、面向方面编程),其实说白了,面向切面编程就是面向特定方法编程。

举例:讲解AOP的用处

        比如我们要我们计算每一个业务的耗时时间,我们是不是会在每一个业务之前加上业务开始时间,和业务结束时间。这样如果我们要计算新增,删除,修改的业务时间,是不是得每个方法加上开始时间和结束时间代码,这样是不是显得很繁琐。

所以AOP的作用是增强方法。

二:AOP核心概念

1. 连接点:JoinPoint,可以被AOP控制的方法 例如:入门程序当中所有的业务方法都是可以被aop控制的方法。
2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
5. 目标对象:Target,目标对象指的就是通知所应用的对象,我们就称之为目标对象。

三:AOP的进阶

1.通知类型(重要的@Around环绕通知的使用)
  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行

  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行

  • @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行

  • @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行

  • @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行 

 2. 通知顺序

通知的执行顺序为:前置通知 -> 环绕通知 -> 目标方法执行 -> 后置返回通知或后置异常通知 -> 最终通知。在环绕通知中,可以控制目标方法的执行,并在执行前后添加额外逻辑,因此它的执行顺序包含了整个通知流的生命周期。

3.切入点表达式

从AOP的入门程序到现在,我们一直都在使用切入点表达式来描述切入点。

第一种表达式 execution(……):根据方法的签名来匹配
  • 省略方法的修饰符号

    execution(void com.gyb.service.impl.DeptServiceImpl.delete(java.lang.Integer))
  • 使用*代替返回值类型

    execution(* com.gyb.service.impl.DeptServiceImpl.delete(java.lang.Integer))
  • 使用*代替包名(一层包使用一个*

    execution(* com.gyb.*.*.DeptServiceImpl.delete(java.lang.Integer))
  • 使用..省略包名

    execution(* com..DeptServiceImpl.delete(java.lang.Integer))    
  • 使用*代替类名

    execution(* com..*.delete(java.lang.Integer))   
  • 使用*代替方法名

    execution(* com..*.*(java.lang.Integer))   
  • 使用 * 代替参数

    execution(* com.gyb.service.impl.DeptServiceImpl.delete(*))
  • 使用..省略参数

    execution(* com..*.*(..))
第二种@annotation使用例子(推荐这一种)

自定义注解:MyLog

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyLog {
}

业务类:DeptServiceImpl

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {
    @Autowired
    private DeptMapper deptMapper;

    @Override
    @MyLog //自定义注解(表示:当前方法属于目标方法)
    public List<Dept> list() {
        List<Dept> deptList = deptMapper.list();
        //模拟异常
        //int num = 10/0;
        return deptList;
    }

    @Override
    @MyLog  //自定义注解(表示:当前方法属于目标方法)
    public void delete(Integer id) {
        //1. 删除部门
        deptMapper.delete(id);
    }

切面类

@Slf4j
@Component
@Aspect
public class MyAspect6 {
    //针对list方法、delete方法进行前置通知和后置通知

    //前置通知
    @Around("@annotation(com.itheima.anno.MyLog)")
    public void log(ProceedingJoinPoint pjp){
        Object result = pjp.proceed();
        return result;
    }
4.连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型

  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

四:AOP案例 写一个操作日志的案例

1.AOP起步依赖

<!--AOP起步依赖-->

<!--AOP起步依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.第二部创建表

-- 操作日志表
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
    operate_user int unsigned comment '操作人',
    operate_time datetime comment '操作时间',
    class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
    cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';

3.定义数据库实体类

//操作日志实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
    private Integer id; //主键ID
    private Integer operateUser; //操作人ID
    private LocalDateTime operateTime; //操作时间
    private String className; //操作类名
    private String methodName; //操作方法名
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
    private Long costTime; //操作耗时
}

4.自定义注解

/**
 * 自定义Log注解
 */
@Target({ElementType.METHOD})
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
}

5.对需要增强的代码进行增强

@Slf4j
@Service
public class EmpServiceImpl implements EmpService {
    @Autowired
    private EmpMapper empMapper;

    @Override
    @Log
    public void update(Emp emp) {
        emp.setUpdateTime(LocalDateTime.now()); //更新修改时间为当前时间

        empMapper.update(emp);
    }

    @Override
    @Log
    public void save(Emp emp) {
        //补全数据
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        //调用添加方法
        empMapper.insert(emp);
    }
}

6.定义切面类,完成操作日志代码的逻辑

@Slf4j
@Component
@Aspect //切面类
public class LogAspect {

    @Autowired
    private HttpServletRequest request;

    @Autowired
    private OperateLogMapper operateLogMapper;

    @Around("@annotation(com.itheima.anno.Log)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
        //操作人ID - 当前登录员工ID
        //获取请求头中的jwt令牌, 解析令牌
        String jwt = request.getHeader("token");
        Claims claims = JwtUtils.parseJWT(jwt);
        Integer operateUser = (Integer) claims.get("id");

        //操作时间
        LocalDateTime operateTime = LocalDateTime.now();

        //操作类名
        String className = joinPoint.getTarget().getClass().getName();

        //操作方法名
        String methodName = joinPoint.getSignature().getName();

        //操作方法参数
        Object[] args = joinPoint.getArgs();
        String methodParams = Arrays.toString(args);

        long begin = System.currentTimeMillis();
        //调用原始目标方法运行
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();

        //方法返回值
        String returnValue = JSONObject.toJSONString(result);

        //操作耗时
        Long costTime = end - begin;


        //记录操作日志
        OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
        operateLogMapper.insert(operateLog);

        log.info("AOP记录操作日志: {}" , operateLog);

        return result;
    }

}

7.mapper层相关代码

@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
    public void insert(OperateLog log);

}

  • 26
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
自定义注解是一种在Java中实现AOP(面向切面编程)的方式。通过定义注解并在代码中使用该注解,可以实现对特定方法或类的增强操作。下面是一个详细的自定义注解实现AOP的示例: 首先,定义一个自定义注解,例如@LogExecution: ```java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface LogExecution { String value() default ""; } ``` 然后,在需要增强的方法上使用该注解: ```java public class MyClass { @LogExecution("执行MyMethod方法") public void myMethod() { // 方法逻辑 } } ``` 接下来,创建一个切面类,用于在方法执行前后进行增强操作: ```java import java.lang.reflect.Method; public class LogExecutionAspect { public void logExecution(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); LogExecution annotation = method.getAnnotation(LogExecution.class); String value = annotation.value(); System.out.println("开始执行方法:" + method.getName()); System.out.println("注解值:" + value); // 执行原始方法 joinPoint.proceed(); System.out.println("方法执行结束:" + method.getName()); } } ``` 最后,在应用程序中配置切面和目标对象的关系,并启动应用程序: ```java import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class Application { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(LogExecutionAspect.class); context.register(MyClass.class); context.refresh(); MyClass myClass = context.getBean(MyClass.class); myClass.myMethod(); context.close(); } } ``` 以上示例中,自定义注解@LogExecution用于标记需要增强的方法,切面类LogExecutionAspect中的logExecution方法会在方法执行前后进行增强操作。在应用程序中,通过配置切面和目标对象的关系,实现了自定义注解的AOP功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值