Annotation学习
About 6 minjavajava
一、基本概述
- Annontation是Java5开始引入的新特征。中文名称一般叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联测试。
- 更通俗的意思是为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且是供指定的工具或框架使用的。
- Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
二、原理
- Annotation其实是一种接口。通过Java的反射机制相关的API来访问Annotation信息。相关类(框架或工具中的类即使用注解的类)根据这些信息来决定如何使用该程序元素或改变它们的行为。
- Annoation和程序代码的隔离性:Annotation是不会影响程序代码的执行,无论Annotation怎么变化,代码都始终如一地执行。
// @Before 切入点开始处切入内容
// @After 切入点结尾处切入内容
// @AfterReturning 切入点return内容之后切入内容
// @Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
// @AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
try{
try{
// 对应@Before注解的方法切面逻辑
doBefore();
method.invoke();
}finally{
// 对应@After注解的方法切面逻辑
doAfter();
}
// 对应@AfterReturning注解的方法切面逻辑
doAfterReturning();
}catch(Exception e){
// 对应@AfterThrowing注解的方法切面逻辑
doAfterThrowing();
}
三、元注解
3.1 @Target:注解的作用目标
- ElementType.TYPE:允许被修饰的注解作用在类、接口、注解和枚举上
- ElementType.FIELD:允许作用在属性字段上
- ElementType.METHOD:允许作用在方法上
- ElementType.PARAMETER:允许作用在方法参数上
- ElementType.CONSTRUCTOR:允许作用在构造器上
- ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
- ElementType.ANNOTATION_TYPE:允许作用在注解上
- ElementType.PACKAGE:允许作用在包上
3.2 @Retention:注解的生命周期
- RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
- RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
- RetentionPolicy.RUNTIME:永久保存,可以反射获取
3.3 @Documented:
注解是否应当被包含在 JavaDoc 文档中
3.4 @Inherited
允许子类继承该注解,但不能从接口继承注解
3.5 @Repeatable
该注解可以在同一个地方被重复使用多次
四、实践
4.1 Log注解
4.1.1 目标
为了简化代码只需要添加一个注解就可以记录关键接口的日志,便于后期排查问题
4.1.2 定义注解
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;
/**
* 自定义日志注解
* @author gaoqisen
*
*/
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
4.1.3 Aspect实现逻辑
import java.lang.reflect.Method;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
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.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
@Aspect
@Component
public class LogAspect {
@Autowired
private CommonService commonServcie;
/**
* 切入点
*/
@Pointcut("@annotation(cn.annotation.Log)")
public void pointcut() {
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint point) {
Object result = null;
long beginTime = System.currentTimeMillis();
try {
// 执行方法
result = point.proceed();
} catch (Throwable e) {
e.printStackTrace();
throw new AppException(e.getMessage());
}
// 执行时长(毫秒)
long time = System.currentTimeMillis() - beginTime;
// 保存日志
saveLog(point, time);
return result;
}
private void saveLog(ProceedingJoinPoint joinPoint, long time) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
SysLog sysLog = new SysLog();
Log logAnnotation = method.getAnnotation(Log.class);
if (logAnnotation != null) {
// 注解上的描述
sysLog.setOperation(logAnnotation.value());
}
// 请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethods(className + "." + methodName + "方法");
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
String params = "";
for (int i = 0; i < args.length; i++) {
params += " " + paramNames[i] + ": " + args[i];
}
sysLog.setParms(params);
}
// 获取request
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Object obj = request.getSession().getAttribute("user");
// 设置IP地址
String ip = RequestUtil.getIpAddress(request);
sysLog.setIp(ip);
sysLog.setAddress(RequestUtil.getAddressByIp(ip));
// 模拟一个用户名
if(obj != null) {
User user = (User) obj;
sysLog.setUser_name(user.getUsername());
sysLog.setUser_id(user.getId());
} else {
sysLog.setUser_name("未知");
}
sysLog.setBaseData();
sysLog.setTime((int) time);
// 保存系统日志
this.commonServcie.inster("sys_log", sysLog);
}
private String replaceBlank(String s) {
String result= null;
if (s == null) {
return result;
} else {
Pattern p = Pattern.compile("\\s*|\t|\r|\n");
Matcher m = p.matcher(s);
result= m.replaceAll("");
return result;
}
}
}
4.2 Validation注解校验
4.2.1 目标
Validation用于校验是特别方便的,但是官方提供的注解可能不满足自己的需要,因此自定义注解用于数据校验
4.2.1 定义注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = LengthInvalidatorImpl.class)
@Documented
public @interface MaxLength {
// 字段支持的最大长度(字符数)
int maxLength() default 5;
// 校验失败后返回的错误信息
String message() default "超过最大长度";
// 分组
Class<?>[] groups() default {};
// 负载
Class<? extends Payload>[] payload() default {};
}
4.2.3 Aspect实现逻辑
/**
* 最大长度校验实现类
*/
public class LengthInvalidatorImpl implements ConstraintValidator<MaxLength, String> {
private MaxLength maxLength;
/**
* 初始化数据
* @param constraintAnnotation 注解类
*/
@Override
public void initialize(MaxLength constraintAnnotation) {
maxLength = constraintAnnotation;
}
/**
* 校验是否有效
*
* @param fieldValue 字段值
* @param context 限制校验上下文
* @return 是否有效
*/
@Override
public boolean isValid(String fieldValue, ConstraintValidatorContext context) {
if (fieldValue.length() > maxLength.maxLength()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(maxLength.message()).addConstraintViolation();
// 校验失败返回false。返回true上游收集不到错误信息。
return false;
}
return false;
}
}
4.2.4 手动校验工具
/**
* validation校验工具
*/
public class ValidationUtils {
private static Validator validation = Validation.buildDefaultValidatorFactory().getValidator();
/**
* 校验实体
* @param obj 需要校验的对象
*/
public static void validationEntity(Object obj) {
Set<ConstraintViolation<Object>> constraintViolations = validation.validate(obj);
for (ConstraintViolation<Object> constraintViolation : constraintViolations) {
String message = constraintViolation.getMessage();
throw new AppException(message);
}
}
}
4.3 Redis缓存
4.3.1 目标
很多地方都需要进行redis缓存,为了简化代码用注解实现redis缓存,方便后期使用
4.3.2 定义注解
/**
* redis缓存
*
* @author gaoqisen
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCache {
/**
* 缓存key
*/
String key() default "";
/**
* 缓存时间(单位秒)
* 默认缓存30分钟
*/
int time() default 60 * 30;
/**
* 业务key
* 例子: #obj.attribute
*/
String bizKey() default "";
}
4.3.3 Aspect实现逻辑
@Slf4j
@Aspect
@Component
@Order(10)
public class RedisCacheAspect {
private final StringRedisTemplate stringRedisTemplate;
public RedisCacheAspect(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
@Pointcut(value = "@annotation(com.gaoqisen.RedisCache)")
public void redisCacheAspectPointcut() {}
@Around("redisCacheAspectPointcut()")
public Object redisCache(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
RedisCache annotation = method.getAnnotation(RedisCache.class);
String key = annotation.key();
int time = annotation.time();
String bizKey = annotation.bizKey();
String redisKey;
if(UT.Str.isNullOrEmpty(bizKey)) {
redisKey = RedisKeySupport.generateCommonKey(key);
} else {
ExpressionParser parser=new SpelExpressionParser();
EvaluationContext ctx = new StandardEvaluationContext();
//获取方法的参数名和参数值
List<String> paramNameList = Arrays.asList(signature.getParameterNames());
List<Object> paramList = Arrays.asList(joinPoint.getArgs());
//将方法的参数名和参数值一一对应的放入上下文中
for (int i = 0; i < paramNameList.size(); i++) {
ctx.setVariable(paramNameList.get(i), paramList.get(i));
}
//解析SpEL表达式获取值
String value = parser.parseExpression(bizKey).getValue(ctx).toString();
//解析后的业务代码
redisKey = RedisKeySupport.generateCommonKey(key, value);
}
String dataStr = stringRedisTemplate.opsForValue().get(redisKey);
if(UT.Str.isNotBlank(dataStr)) {
Object obj = SerializeUtils.str2Object(dataStr);
log.info("Redis切面缓存-缓存命中: {}", UT.Json.toJSONString(obj));
return obj;
}
log.info("Redis切面未命中缓存-请求接口获取数据 redisKey: {}", redisKey);
Object obj = joinPoint.proceed();
stringRedisTemplate.opsForValue().set(redisKey, SerializeUtils.object2Str(obj), time, TimeUnit.SECONDS);
return obj;
}
}
4.3.4 缓存序列化工具
public class SerializeUtils {
public static final String CHARSET_NAME = "ISO-8859-1";
/**
* 对象转字符串
*/
public static String object2Str(Object obj) throws Exception {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(obj);
String objectStr = byteArrayOutputStream.toString(CHARSET_NAME);
byteArrayOutputStream.close();
objectOutputStream.close();
return objectStr;
}
/**
* 字符串转对象
*/
public static Object str2Object(String str) throws Exception {
ByteArrayInputStream byteArrayOutputStream = new ByteArrayInputStream(str.getBytes(CHARSET_NAME));
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayOutputStream);
Object object = objectInputStream.readObject();
byteArrayOutputStream.close();
objectInputStream.close();
return object;
}
}