Spring3-AOP
面向切面编程
-
横切关注点:从每个方法中抽取出来的同一类非核心业务。
-
切面:封装横切关注点信息的类,每个关注点体现为一个通知方法。
-
通知:切面必须要完成的各个具体工作
-
目标:被通知的对象
-
代理:向目标对象应用通知之后创建的代理对象
-
连接点:横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例如:类某个方
法调用前、调用后、方法捕获到异常后等。
在应用程序中可以使用横纵两个坐标来定位一个具体的连接点:
AspectJ
5种通知:
- @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) + " ------");
}
-
@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 . ------"); }
-
@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 + " . ------"); }
-
@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 + " . ------"); }
-
@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.