01-前言:
Java注解是一种特殊标记,其作用包括对属性赋值,方法逻辑扩展,类、属性说明,异常处理等。注解的使用可以减少对代码逻辑部分的入侵,提升开发效率,如Spring中的@Component,@Async,@RestController等常用的注解。但是在实际开发中,也会遇到一些特殊情况无法使用已有注解解决开发需求的情况。本文将描述如何通过Spring AOP实现自定义注解。
02-实现自定义属性打印注解
- 定义一个注解类HunterLogAnno
/**
* 定义一个普通注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface HunterLogAnno {
String value() default "";
}
- 使用AOP对注解进行解析,需要定义一个切面类,包括自定义的切点方法normalPointCut(),以及连接点的处理方法normalPointAround()。连接点中的ProceedingJoinPoint可以获取被代理类的方法属性等。
import com.example.annotations.HunterLogAnno;
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.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 使用 Aspect 实现注解,别忘记添加 @Component 注解让Aspect注解生效
*/
@Component
@Aspect
public class HunterAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 这里定义一个PointCut,用于描述满足条件的方法
*/
@Pointcut("execution(public * com.example.Services..*(*))")
private void normalPointCut() {
System.out.println("pointCut");
}
/** 将Services 文件下,且带有 HunterLogAnno 注解的类进行
*
* @param joinPoint
*/
@Around("normalPointCut() && @annotation(com.example.annotations.HunterLogAnno)")
private void normalPointAround(ProceedingJoinPoint joinPoint) throws Throwable {
logger.info("===========================");
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
// 执行具体的方法,在使用 Around 类的时候,需要对执行方法进行调用,否则只会执行 Around 方法 normalPointBefore 里面的逻辑
joinPoint.proceed();
// 注解解析
HunterLogAnno hunterLogAnno = method.getAnnotation(HunterLogAnno.class);
String value = hunterLogAnno.value();
// 打印注解记录
logger.info(value);
logger.info("===========================");
}
}
上边定义了一个切点和一个环绕处理方法,通过定义 @PointCut 和 @annotation 来对注解解析。这里有个容易忽视的点,使用 @Around 环绕织入时需要JointPoint 的proccess方法执行原方法 (未被代理时) ,否则只会执行上图中的 normalPointAround() 方法的流程,稍后可以利用这个特性实现方法的异步执行。在这里,使用@Around注解,获取被代理方法的属性,并输出log日志,其中 MethodSignature 类可以获取方法的名称,输入参数等,可以通过ProceedingJoinPoint 类获取该类的各种属性。
- 接下来,实现一个普通类,并定义value的属性值。
import com.example.annotations.HunterLogAnno;
import org.springframework.stereotype.Service;
@Service
public class HunterServiceImpl {
@HunterLogAnno(value = "honda")
public void getTheLogs(String name) {
System.out.println(name);
}
}
- 最后,定义Controller和Service类进行模拟调用
import com.example.Services.HunterServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyHtrixController {
@Autowired
private HunterServiceImpl service;
@RequestMapping("/hunterCar")
@ResponseBody
public String reqHunterAnno() {
service.getTheLogs("honda");
return "honda!!";
}
}
成功将 SpringBoot Start 启动后,使用浏览器输入网址:http://localhost:8771/hunterCar
03-自定义注解实现方法的异步执行
- 首先自定义一个异步处理的注解@HunterAsyncAnno
import java.lang.annotation.*;
/**
* 异步执行方法的注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface HunterAsyncAnno {
// 核心线程数
int coreSize() default 1;
// 最大核心线程数
int maxSize() default 1;
}
- 在Aspect中定义一个注解的解析,对标注了@HunterAsyncAnno的方法进行解析,这里使用上边所说的@Around 中JoinPoint.proccess()方法,在执行AOP外部方法时使用Thread方法对其进行包裹并执行。
/**
## * 定义一个注解的切点
* @param joinPoint
*/
@Around("normalPointCut() && @annotation(com.example.annotations.HunterAsyncAnno)")
private void asyncPointAround(ProceedingJoinPoint joinPoint) {
try {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
HunterAsyncAnno hunterAsyncAnno = method.getAnnotation(HunterAsyncAnno.class);
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
joinPoint.proceed();
System.out.println("END TIME:" + System.currentTimeMillis());
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
}).start();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
- 同样,在Service类中添加请求处理的方法:
import com.example.annotations.HunterAsyncAnno;
import com.example.annotations.HunterLogAnno;
import org.springframework.stereotype.Service;
@Service
public class HunterServiceImpl {
@HunterLogAnno(value = "Integra")
public void getTheLogs(String name) {
System.out.println(name);
}
@HunterAsyncAnno
public void asyncSystemOut(String name) {
System.out.println("This text loggout in 5s!!");
}
}
同样使用Controller方法进行测试,可以看到异步执行的结果。
import com.example.Services.HunterServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyHtrixController {
@Autowired
private HunterServiceImpl service;
@RequestMapping("/hunterCar")
@ResponseBody
public String reqHunterAnno() {
service.getTheLogs("honda");
return "honda!!";
}
@RequestMapping("/hunterAsync")
@ResponseBody
public String reqHunterAsyncAnno() {
System.out.println("BEGIN TIME:" + System.currentTimeMillis());
service.asyncSystemOut("Integra!!");
return "Integra!!";
}
}
可以看到使用Thread类start后进行异步处理proccess(),在注解逻辑中使用Thread.sleep(5000)暂停了5s时间,可以看见打印的结果也相差了5s左右。
04-总结
以上,介绍了SpringBoot中结合AOP实现自定义注解的简单实践,依靠Spring中AOP的自定义切点以及通过ProceedingJoinPoint方法属性的获取进行更加多样化的注解定义。但是,这种注解也存在一定的缺点,由于使用的是AspectJ框架,因此在使用带有该注解的方法时,不能由该类的另一个方法调用,否则会另注解无效,这是因为动态代理的一些特点导致的。