Spring3-AOP

面向切面编程

  1. 横切关注点:从每个方法中抽取出来的同一类非核心业务。

  2. ​切面:封装横切关注点信息的类,每个关注点体现为一个通知方法。

  3. 通知:切面必须要完成的各个具体工作

  4. 目标:被通知的对象

  5. 代理:向目标对象应用通知之后创建的代理对象

  6. 连接点:横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方

    法调用前、调用后、方法捕获到异常后等。

    在应用程序中可以使用横纵两个坐标来定位一个具体的连接点:

AspectJ

5种通知:

  1. @Before:前置通知,在目标方法调用前执行
/**
 * 前置通知:在目标方法调用前执行,必须要通过切入点表达式指定目标方法
 * 通用的切入点表达式:
 * 第一个 * :任意修饰符,任意返回值
 * 第二个 * :任意类
 * 第三个 * :任意方法
 * .. :任意的参数列表
 */
//@Before("execution(public int com.yuyi.spring.aop.aspectj.impl.ArithmeticCalculatorImpl.add(int,int))")
@Before("execution(* com.yuyi.spring.aop.aspectj.impl.*.*(..))")
public void beforeMethod(JoinPoint joinPoint) {
    //获取目标方法名字
    String methodName = joinPoint.getSignature().getName();

    //获取目标入参,对象数组
    Object[] methodParams = joinPoint.getArgs();

    System.out.println("------ LoggingAspect -> " + methodName + " 方法 begin . 入参" + Arrays.toString(methodParams) + " ------");
}
  1. @After:后置通知,在方法执行之后执行

    /**
     * 后置通知:获取不到方法的返回值,是在目标放管服执行后执行
     * 就算抛出异常对日志也没有影响
     */
    @After("execution(* com.yuyi.spring.aop.aspectj.impl.*.*(..))")
    public void afterMethod(JoinPoint joinPoint) {
    
        // 获取目标方法名字
        String methodName = joinPoint.getSignature().getName();
    
        System.out.println("------ LoggingAspect -> " + methodName + " 方法 end . ------");
    }
    
  2. @AfterReturning:返回通知,在方法返回结果之后执行

    /**
     * 返回通知:用户获取目标方法的返回值,必须通过returning属性指定一个参数名(必须跟当前通知方法的某个形参名称保持一致)
     */
    @AfterReturning(value = "execution(* com.yuyi.spring.aop.aspectj.impl.*.*(..))", returning = "result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result) {
    
        // 获取目标方法名字
        String methodName = joinPoint.getSignature().getName();
    
        System.out.println("------ LoggingAspect -> " + methodName + " 方法 return: " + result + " . ------");
    
    }
    
  3. @AfterThrowing:异常通知,在方法抛出异常之后执行

    /**
     * 异常抛出通知:在目标方法产生异常会执行,throwing属性指定异常别名,此别名必须跟当前通知方法的形参名保持一致
     */
    @AfterThrowing(value = "execution(* com.yuyi.spring.aop.aspectj.impl.*.*(..))", throwing = "ex")
    public void afterReturningMethod(JoinPoint joinPoint, Exception ex) {
    
        // 获取目标方法名字
        String methodName = joinPoint.getSignature().getName();
    
        System.out.println("------ LoggingAspect -> " + methodName + " 方法 Throw: " + ex + " . ------");
    
    }
    
  4. @Around:环绕通知,围绕着方法执行

    /**
     * 环绕通知:提供了一种方式,让开发者可以在代码中手动控制核心方法调用
     * 可以看做是其它四种通知的结合体,一般请求环绕会影响其他同志,所以二者要避免同时使用
     */
    //@Around("execution(* com.yuyi.spring.aop.aspectj.impl.*.*(..))")
    public Object aroundMethod(ProceedingJoinPoint joinPoint) {
        //获取目标方法名字
        String methodName = joinPoint.getSignature().getName();
    
        //环绕处理方法
        try {
            //前置通知
            System.out.println("------ LoggingAspect -> " + methodName + " 方法 begin . 入参" + Arrays.toString(joinPoint.getArgs()) + " ------");
    
            //手动调用目标方法
            Object result = joinPoint.proceed();
    
            //返回通知
            System.out.println("------ LoggingAspect -> " + methodName + " 方法 return: " + result + " . ------");
    
            // 必须要返回结果,才能保证调用核心方法能获取到值,否则就是null
            return result;
    
        } catch (Throwable ex) {
            //异常抛出通知
            System.out.println("------ LoggingAspect -> " + methodName + " 方法 Throw: " + ex + " . ------");
        } finally {
            //后置通知
            System.out.println("------ LoggingAspect -> " + methodName + " 方法 end . ------");
        }
        return null;
    }
    

重用切入点表达式

@Pointcut("execution(* com.yuyi.spring.aop.aspectj.impl.*.*(..))")
public void reusablePointcut() {}

调用时:@Before("reusablePointcut()")

优先级

order(1):数字越小优先级越高

定时任务:

bean中任务实体

/**
 * 定时获取用户手机,发送短信
 */
@Scheduled(cron = "0/2 * * * *")
public void SendMsgJobAnnotation() {
    System.out.println("------ 执行发送短信");

    for (int i = 0; i < 2; i++) {
        System.out.println("发送短信信息:" + UUID.randomUUID().toString());
        //模拟耗时
        try {
            Thread.sleep(10);

        } catch (Exception e) {

        }
    }
}

容器配置

<!--组件扫描 -->
<context:component-scan base-package="com.yuyi.spring.schedule"/>

<!-- 手动配置定时任务 -->
<task:scheduler id="schedule" pool-size="10"/>
<task:scheduled-tasks scheduler="schedule">
    <!-- ref指向的是容器中的定时任务类的id值-->
    <task:scheduled ref="sendMsgSchedule" method="sendMsgJob" cron="0/2 * * * * ?"/>
</task:scheduled-tasks>

任务类需要被组件扫描进入容器,之后再被装载入定时任务中

cron计划任务:

M H D m d cmd.

M: 分钟(0-59)。

H:小时(0-23)。

D:天(1-31)。

m: 月(1-12)。

d: 一星期内的天(0~7,0,7为星期天,6为星期六)。

cmd: 要执行的命令。

(1):表示匹配该域的任意值,假如在Minutes域使用, 即表示每分钟都会触发事件。

(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用,如果使用表示不管星期几都会触发,实际上并不是这样。

(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次

(4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.

(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在第5和第20分钟分别触发一次。

(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

(7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份

(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个非周六周末的日期。

(9)#:用于确定每个月第几个星期几,只能出现在DayofWeek域。例如在4#2,表示某月的第二个星期三。
使用注解创建定时任务

bean中任务实体

   /**
     * 定时获取用户手机,发送短信
     */
    @Scheduled(cron = "0/1 * * * * ?")
    public void SendMsgJobAnnotation() {
        System.out.println("------ 执行发送短信");

        for (int i = 0; i < 2; i++) {
            System.out.println("发送短信信息:" + UUID.randomUUID().toString());
            //模拟耗时
            try {
                Thread.sleep(10);
            } catch (Exception e) {
            }
        }
    }

xml中配置:

<!-- 开启任务注解扫描 -->
<task:annotation-driven/>

异步处理:

xml中:

    <!--组件扫描 -->
    <context:component-scan base-package="com.yuyi.spring.async"/>

    <task:executor id="0-postChargeProcessor" pool-size="10-50" queue-capacity="100" rejection-policy="CALLER_RUNS"/>

    <!-- 开启任务注解扫描 -->
    <task:annotation-driven/>

注解实现:

package com.yuyi.spring.async;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

/**
 * Created on 2021/6/19.
 * <p>
 * Author: Lcywings
 * <p>
 * Description:异步发送充值处理线程池
 */
@Component
public class PostChargeProcessor {
    @Autowired
    private ChargService chargService;
    /**
     * @return :
     * @author : zhukang
     * @date : 2021-06-19
     * @description : 发送充值
     * 别名就是配置文件中task:executor 定义的id别名,要跟类名首字母小写不同(类也是组件,会放入容器)
     */
    @Async("0-postChargeProcessor")
    public void postCharge(String phoneNo) {
        System.out.println("提交充值手机号:" + phoneNo);
        // 调用业务接口进行充值
        chargService.doCharg(phoneNo);
    }

}

Q.E.D.