AOP 面向切面编程
- 新建项目Spring Starter Project。
- 自定义注解,用于标记需要 动态插入切面 的 位置,在这里我定义了3个。
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,标记需要检查健康码的位置
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HealthCode {
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 自定义注解,标记需要进行性能分析的位置
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Timing {
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,标记需要用户认证的位置
* 其中包含一个属性:用户名,默认为User
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
String value() default "user";
}
- 为每一个注解增加一个类,标记为组件 @Component 和切面 @Aspect,还可以标记其优先级 @Order,当一个方法被多个注解标记时,可以根据优先级判断切入顺序。
@Pointcut() 标记切入点 :这里是被注解标记的方法
@Before() 指定前置通知的切入点
@After() 指定后置通知的切入点
JoinPoint 连接点:指切面切入的具体方法,通过连接点可以获取:
- 切入点 方法的名字 getSignature().getName()
-切入点方法的参数 getArgs()
- 切入点方法标注的注解对象,进而可以获取注解的信息
- …
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component // 标记为组件
@Aspect // 标记为切面
@Order(2) // 优先级
public class HealthCodeAspect {
// 通知(特定位置):织入目标方法的位置
// @Before 前置
// @After 后置
// @Around 环绕
// @AfterReturning 方法返回后
// @AfterThrowing 异常抛出后
// 把重复的部分提取出来,用注解的方式标记接入点 @Pointcut,不必每次都重复写
@Pointcut("@annotation(com.newer.aop.HealthCode)")
public void x() {
}
// 指定前置通知的切入点,这里的 切入点 是被 注解标记的方法
// 传入 注解的 包命+类名
//@Before("@annotation(com.newer.aop.HealthCode)")
@Before("x()")
public void checkCode(JoinPoint joinpoint) {
// 获得连接点,即切面切入的具体方法
String name = joinpoint.getSignature().getName();
System.out.printf("%s ::前置通知:检查健康码\n",name);
}
//@After("@annotation(com.newer.aop.HealthCode)")
@After("x()")
public void after(JoinPoint joinPoint) {
System.out.printf("%s ::后置通知\n",joinPoint.getSignature().getName());
}
}
@Around() :指定环绕通知的切入点
环绕通知 = 前置 + 切面切入方法的执行 + 后置
proceed 方法就是用于启动目标方法的执行,通过Proceedingjoinpoint 对象调用
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
// 标记为 组件,切面
@Component
@Order(1)
@Aspect
public class TimingAspect {
@Pointcut("@annotation(com.newer.aop.Timing)")
public void y() {
}
// 环绕通知,切入点
@Around("y()")
public void aroung(ProceedingJoinPoint point) {
System.out.printf("-----------------------%s性能分析开始\n",point.getSignature().getName());
long time = System.currentTimeMillis();
// 被切入方法的执行
Object result = null;
try {
result = point.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
time = System.currentTimeMillis() - time;
System.out.printf("=========%s 性能分析,耗时 %d\n",
point.getSignature().getName(),
time);
}
}
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
// 组件、切面
@Component
@Aspect
@Order(0)
public class AuthAspect {
@Before("@annotation(com.newer.aop.Auth)")
public void beforeAuth(JoinPoint joinPoint) {
// 切入点的方法名
String name = joinPoint.getSignature().getName();
System.out.println("方法名:"+name);
// 切入点方法中的参数
Object[] args = joinPoint.getArgs();
if(args!=null) {
for(Object obj:args) {
System.out.println("参数:"+obj);
}
}
// 获得注解的值,进一步拓展可做权限认证等...
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method targetMethod = methodSignature.getMethod();
Auth ano = targetMethod.getAnnotation(Auth.class);
System.out.println("注解标记的权限值为:"+ano.value());
System.out.println("*********************************************************");
}
}
- 给需要动态加入公共需求的方法标记上相对应的注解。
import org.springframework.stereotype.Component;
@Component
public class Life {
// 织入(一些公共的需求,可动态加入)
// 织入多个切面,可以定义切面的优先级 @order()
@HealthCode
@Timing
public void shopping() {
// 用户认证
// 权限的鉴定
// 日志
// 开启事务
// ...
System.out.println("购物...");
int t = (int) (Math.random()*1000+500);
try {
Thread.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 提交事务
// 性能分析
}
@HealthCode
@Timing
public void subway() {
// 日志
// 开启事务
System.out.println("乘地铁...");
int t = (int) (Math.random()*1000+500);
try {
Thread.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 提交事务
// 性能分析
}
@Timing
public void eat() {
System.out.println("吃饭...");
int t = (int) (Math.random()*1000+500);
try {
Thread.sleep(t);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 权限验证
@Auth(value = "admin")
public void checkIn(String name) {
//System.out.println("checkIn:"+name);
}
@Auth(value = "member")
public void addOrder(int a,int b) {
}
}
- Spring Main 调用测试
import org.springframework.beans.factory.BeanFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class Aop0810Application {
public static void main(String[] args) {
SpringApplication.run(Aop0810Application.class, args);
}
@Bean
public CommandLineRunner operation(BeanFactory factory) {
return new CommandLineRunner() {
@Override
public void run(String... args) throws Exception {
// 从组件工厂获得实例
Life life = factory.getBean(Life.class);
life.subway();
System.out.println();
life.shopping();
System.out.println();
life.eat();
life.checkIn("jack");
life.addOrder(7, 9);
}
};
}
}
- 查看控制台输出