在业务中有这样的场景:我需要在调某些方法的时候,需要提前拿出它的参数作业务判断,然后判断通过的代码再进行业务操作,此处我们使用自定义注解来解决问题。
自定义注解
import org.springframework.stereotype.Component;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Component
public @interface Note {
String type() default "";
}
定义注解时,会需要一些元注解(meta-annotation),如@Target和@Retention。
@Target 用来定义你的注解将应用于什么地方(例如是一个方法或者一个域)。
@Retention 用来定义该注解在哪一个级别可用,在源代码中(SOURCE)、类文件中(CLASS)或者运行时(RUNTIME)。
Java除了内置了三种标准注解,还有四种元注解。此处借鉴(Java中三种标准注解和四种元注解_良月柒-优快云博客_java元注解)
@Target 表示该注解用于什么地方,可能的值在枚举类 ElemenetType 中,包括:
ElemenetType.CONSTRUCTOR-----------------------------构造器声明
ElemenetType.FIELD ----------------------------------域声明(包括 enum 实例)
ElemenetType.LOCAL_VARIABLE------------------------- 局部变量声明
ElemenetType.METHOD ---------------------------------方法声明
ElemenetType.PACKAGE --------------------------------包声明
ElemenetType.PARAMETER ------------------------------参数声明
ElemenetType.TYPE----------------------------------- 类,接口(包括注解类型)或enum声明
@Retention 表示在什么级别保存该注解信息。可选的参数值在枚举类型 RetentionPolicy 中,包括:
RetentionPolicy.SOURCE-------------注解将被编译器丢弃
RetentionPolicy.CLASS -------------注解在class文件中可用,但会被VM丢弃
RetentionPolicy.RUNTIME ---------VM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。
@Documented 将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同。相当与@see,@param 等。
@Inherited 允许子类继承父类中的注解。
Spring在扫描类信息的使用只会判断被@Component注解的类,所以任何自定义的注解只要带上@Component(当然还要有String value() default "";的方法,因为Spring的Bean都是有beanName唯一标示的),都可以被Spring扫描到,并注入容器内。
业务类
我们编写一个简单的业务代码,如下controller
import com.example.demo.base.sp.dto.ResponseDto;
import com.example.demo.base.sp.dto.UserDto;
import com.example.demo.base.sp.dto.UserEntity;
import com.example.demo.base.sp.service.IUserService;
import com.example.demo.base.utils.aop.Note;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @author sun.ll
* @date 2020/12/1
*/
@Controller
@RequestMapping("/v1/user")
public class UserController {
@Autowired
private IUserService userService;
@GetMapping("/login")
@ResponseBody
public ResponseDto<String> loginIn(@RequestParam("username") final String username, @RequestParam("password") final String password) {
final String token = userService.selectByNameAndPwd(username,password);
return ResponseDto.wrapSuccess(token);
}
/**
* 此Get方法加上了自定义注解
* @param userId
* @return
*/
@GetMapping("/query")
@Note(type = "LOGIN")
@ResponseBody
public ResponseDto<UserEntity> query(@RequestParam("userId") final Long userId) {
final UserEntity userEntity = userService.selectById(userId);
return ResponseDto.wrapSuccess(userEntity);
}
/**
* 此Post方法加上了自定义注解
* @param entity
* @return
*/
@PostMapping("/query/post")
@Note(type = "LOGIN")
@ResponseBody
public ResponseDto<UserEntity> query(final UserDto entity ) {
final UserEntity userEntity = userService.getUserEntity(entity);
return ResponseDto.wrapSuccess(userEntity);
}
}
枚举类
接下来我们需要在调用这个方法的时候先进行一系列的操作,比如权限鉴定等等。我们定义一个Enum,来作参数与注解值的映射:
import java.util.HashMap;
import java.util.Map;
/**
* @author sun,linglei
* @date 2020/12/1
*/
public enum BusinessTypeMappingEnum {
/**
* GET测试注解
*/
GET_PARAM("LOGIN","userId"),
/**
* POST测试注解
*/
POST_BEAN("LOGIN","UserDto");
private String businessType;
private String paramName;
BusinessTypeMappingEnum(String businessType, String paramName) {
this.businessType = businessType;
this.paramName = paramName;
}
private String getBusinessType(){
return businessType;
}
private String getParamName(){
return paramName;
}
//初始化数据
private static final Map<String,BusinessTypeMappingEnum> BUSINESS_MAP = new HashMap<>();
static {
for (BusinessTypeMappingEnum mappingEnum:values()
) {
BUSINESS_MAP.put(mappingEnum.getBusinessType(),mappingEnum);
}
}
//根据businessType 查找参数名
public static String getValuesByName(final String businessType){
final BusinessTypeMappingEnum businessTypeMappingEnum = BUSINESS_MAP.get(businessType);
return businessTypeMappingEnum == null ? null : businessTypeMappingEnum.getParamName();
}
}
(这个enum当中的 根据type查找参数名的方法也可以替换很多 简单的if-else匹配)
关于Spring aop(细说Spring——AOP详解(AOP概览)_啦啦啦的博客-优快云博客_aop)
具体实现方法
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.example.demo.base.enums.BusinessTypeMappingEnum;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* @author sun,linglei
* @date 2020/12/1
*/
@Slf4j
@Aspect
@Component
public class AspectsClass
{
@Pointcut("@annotation(com.example.demo.base.utils.aop.Note)")
public void points(){
}
@Before("points()")
public void doBefore(final JoinPoint joinPoint){
//获取注解信息
final MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
final Method method = methodSignature.getMethod();
if (Objects.isNull(method)){
return;
}
final Note annotation = method.getAnnotation(Note.class);
//获取调用的方法
final HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
if ("POST".equals(request.getMethod().toUpperCase())) {
//获取post参数
String str = "";
final Object[] args = joinPoint.getArgs();
if (args.length!=0){
str = JSONArray.toJSON(args[0]).toString();
}
//获取JsonObject
final JSONObject jsonObject = JSONObject.parseObject(str);
//获取object参数的类
final Class object = getObject(BusinessTypeMappingEnum.getValuesByName(annotation.type()));
final Object javaMap = JSON.toJavaObject(jsonObject,object);
//可以再根据type写个策略具体业务具体解决
System.out.println(javaMap);
}
else if ("GET".equals(request.getMethod().toUpperCase())) {
//根据名字获取参数
final String param = request.getParameter(BusinessTypeMappingEnum.getValuesByName(annotation.type()));
System.out.println(param);
//do something
return;
}
}
/**
* 根据类型名字获取bean参数
* @param name
* @return
*/
private Class getObject(final String name){
try {
final Class clazz = Class.forName("com.example.demo.base.sp.dto."+name);
return clazz;
} catch (ClassNotFoundException e) {
log.info("class not found ");
}
return null;
}
}
几个注意点:(一)Class.forName后面类名前需要拼上包名,此处也可以全数定义在枚举类中。否则会报ClassNotFoundException;
(二) post或者get拿到具体的参数后可以接上 策略模式等,再次根据从注解中获取的type走不同的业务分支(SpringBoot中使用策略模式_吃鱼的宗介的博客-优快云博客)