元注解参数解释
注解 | 说明 |
---|---|
@Target | 定义注解的作用目标,也就是可以定义注解具体作用在类上,方法上,还是变量上 |
@Retention | 定义注解的保留策略, :注解仅存在于源码中在class字节码文件中不包含 :默认的保留策略注解会在class字节码文件中存在但运行时无法获得; :注解会在class字节码文件中存在,在运行时可以通过反射获取到。 |
@Document | 说明该注解将被包含在javadoc中 |
@Inherited | 说明子类可以继承父类中的该注解 |
@Target类型 | 说明 |
---|---|
接口、类、枚举、注解 | |
字段、枚举的常量 | |
方法 | |
方法参数 | |
构造函数 | |
ElementType.LOCAL_VARIABLE | 局部变量 |
ElementType.ANNOTATION_TYPE | 注解 |
包 |
自己实现注解
目标:在controller的方法里实现自己的参数校验功能,比如我搭建了一个博客系统,现在想做一个演示的功能,用户只能操作自己的数据和信息,别人的他只能看,但是我现在有没有足够的时间去做(或者不想搞太高的复杂度)RABC,把权限传给前端让前端验证,那么简单处理就是在每个需要修改操作的接口加上验证,是否是自己的数据,每个接口写一次,是不是很烦。
那么咱们就使用注解简单搞一下。
1.定义注解
接收参数,方便校验使用.x新建注解类DomainOwner
package com.warmer.web.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DomainOwner {
String domainIdPattern() default "";//传入的参数可能是id,也可能是编码,所以定义了两个参数
String domainPattern() default "";
}
2.定义切面
新建切面类DomainValidAspect,告诉spring切入点
package com.warmer.web.aspect;
import com.warmer.base.enums.ReturnStatus;
import com.warmer.base.util.R;
import com.warmer.base.util.SpringUtils;
import com.warmer.base.util.StringUtil;
import com.warmer.web.annotation.AnnotationResolver;
import com.warmer.web.annotation.DomainOwner;
import com.warmer.web.entity.KgDomain;
import com.warmer.web.security.TokenService;
import com.warmer.web.service.KnowledgeGraphService;
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.springframework.beans.factory.annotation.Autowired;
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.util.List;
@Aspect
@Component
public class DomainValidAspect {
@Autowired
TokenService tokenService;
@Pointcut("@annotation()")
public void annotationPointcut() {
}
@Around("annotationPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
DomainOwner domainOwner = methodSignature.getMethod().getAnnotation(DomainOwner.class);
//获取domainId表达式,(类似形参),可以是正则,可以是具体参数,下面使用的时候讲
String domainIdPattern = domainOwner.domainIdPattern();
String domainPattern = domainOwner.domainPattern();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//获取当前用户的数据列表,这两行是根据具体的业务,获取数据库中的数据,可以使用SpringUtils获取对象实例,也可以是@Autowried自动注入
KnowledgeGraphService kgService = SpringUtils.getBean(KnowledgeGraphService.class);
List<KgDomain> myDomains = kgService.getDomainByUuid(tokenService.getLoginUserUuid(request));
//下面就是解析参数了,具体实现靠AnnotationResolver类,下面会贴出代码
AnnotationResolver annotationResolver = AnnotationResolver.newInstance();
String domain = "";
int domainId = 0;
if(StringUtil.isNotBlank(domainIdPattern)&&StringUtil.isNotBlank(domainPattern)){
return R.create(ReturnStatus.NoRight, "没有权限");
}
if(StringUtil.isNotBlank(domainIdPattern)){
domainId = (int) annotationResolver.resolver(joinPoint, domainIdPattern);
int finalDomainId = domainId;
if (myDomains.stream().filter(n -> n.getId()== finalDomainId).count() == 0) {
return R.create(ReturnStatus.DemoOperate, "不能修改别人的数据");
}
}
if(StringUtil.isNotBlank(domainPattern)){
domain = (String) annotationResolver.resolver(joinPoint, domainPattern);
String finalDomain = domain;
if (myDomains.stream().filter(n -> n.getName().equalsIgnoreCase(finalDomain)).count() == 0) {
return R.create(ReturnStatus.DemoOperate, "不能修改别人的数据");
}
}
return joinPoint.proceed();
}
}
3.具体使用
我们定义注解的时候指定@Target({}),作用在方法上,所以注解使用范围也只是方法
@Controller
@RequestMapping(value = "/")
public class UserManagerController {
//1.传入的是一个具体的值
@DomainOwner(domainPattern = "#{userCode}")
@ResponseBody
@RequestMapping(value = "/getUserDetail")
public R<String> getUserDetail(String userCode) {
try {
int domainId = (int) params.get("domainId");
//处理自己的业务
} catch (Exception e) {
e.printStackTrace();
return R.error(e.getMessage());
}
return R.error("操作失败");
}
//2.传入的是一个对象,这里User类就不贴了,属性userCode,userId之类的
@DomainOwner(domainPattern = "#{}")
@ResponseBody
@RequestMapping(value = "/saveUser")
public R<String> saveUser(@RequestBody User userItem) {
try {
//处理自己的业务
} catch (Exception e) {
e.printStackTrace();
return R.error(e.getMessage());
}
return R.error("操作失败");
}
//3.传入的可能是一个map,同样指定一个表达式
@DomainOwner(domainIdPattern = "#{}")
@ResponseBody
@RequestMapping(value = "/saveNodeImage")
public R<String> saveNodeImage(@RequestBody Map<String, Object> params) {
try {
int domainId = (int) params.get("domainId");
//处理自己的业务
} catch (Exception e) {
e.printStackTrace();
return R.error(e.getMessage());
}
return R.error("操作失败");
}
}
AnnotationResolver类实现
package com.warmer.web.annotation;
import java.lang.reflect.Method;
import java.util.Map;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
/**
* 该类的作用可以把方法上的参数绑定到注解的变量中,注解的语法#{变量名}
* 能解析类似#{userCode}或者#{}或者{}
*/
public class AnnotationResolver {
private static AnnotationResolver resolver ;
public static AnnotationResolver newInstance(){
if (resolver == null) {
return resolver = new AnnotationResolver();
}else{
return resolver;
}
}
/**
* 解析注解上的值
* @param joinPoint
* @param str 需要解析的字符串
* @return
*/
public Object resolver(JoinPoint joinPoint, String str) {
if (str == null) return null ;
Object value = null;
if (str.matches("#\\{\\D*\\}")) {// 如果name匹配上了#{},则把内容当作变量
String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
if (newStr.contains(".")) { // 复杂类型
try {
value = complexResolver(joinPoint, newStr);
} catch (Exception e) {
e.printStackTrace();
}
} else {
value = simpleResolver(joinPoint, newStr);
}
} else { //非变量
value = str;
}
return value;
}
private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
String[] strs = str.split("\\.");
for (int i = 0; i < names.length; i++) {
if (strs[0].equals(names[i])) {
Object obj = args[i];
//这里处理出入参数为Map的逻辑
if(obj instanceof Map){
Map item=(Map) obj;
return item.get(strs[1]);
}
Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
Object value = dmethod.invoke(args[i]);
return getValue(value, 1, strs);
}
}
return null;
}
private Object getValue(Object obj, int index, String[] strs) {
try {
if (obj != null && index < strs.length - 1) {
Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
obj = method.invoke(obj);
getValue(obj, index + 1, strs);
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String getMethodName(String name) {
return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
}
private Object simpleResolver(JoinPoint joinPoint, String str) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < names.length; i++) {
if (str.equals(names[i])) {
return args[i];
}
}
return null;
}
}
工具类SpringUtils
获取spring对象实例
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringUtils implements ApplicationContextAware{
private static ApplicationContext applicationContext = null;
@Override
public void setApplicationContext(ApplicationContext arg0) throws BeansException {
if (SpringUtils.applicationContext == null) {
SpringUtils.applicationContext = arg0;
}
}
// 获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
// 通过name获取 Bean.
public static Object getBean(String name) {
return getApplicationContext().getBean(name);
}
// 通过class获取Bean.
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);
}
// 通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
补充
还有一点想说的,就是@Pointcut的用法,刚才我们写的是 @annotation:用于匹配当前执行方法持有指定注解的方法
/**
* 切入点
* 设置切入点为标记为DomainOwner的地点
* AspectJ支持命名切入点,方法必须是返回void类型
*/
@Pointcut("@annotation()")
public void annotationPointcut() {
}
@Pointcut有12中用法,常用的还有@execution用于匹配方法执行的连接点,具体的参考下方博客
/**
* 切入点
* 设置切入点为web层
* AspectJ支持命名切入点,方法必须是返回void类型
*/
@Pointcut("execution(public * xxx.*.controller.*.*(..))")
public void aopMethod() {
}
参考博客
@Pointcut 的 12 种用法,你知道几种
java中注解的使用