用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。Annotations仅仅是元数据,和业务逻辑无关。
元注解
说自定义注解之前,肯定要说元注解,因为自定义注解是由元注解来定义的。
@Documented :注解是否将包含在JavaDoc中
@Inherited :是否允许子类继承该注解
@Retention :什么时候使用该注解,即注解的生命周期
RetentionPolicy.SOURCE
:在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override
,@SuppressWarnings
都属于这类注解。RetentionPolicy.CLASS
:在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式。RetentionPolicy.RUNTIME
:始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
@Target :注解用于什么地方
ElementType.TYPE
:用于描述类、接口或enum声明ElementType.FIELD
:用于描述实例变量ElementType.METHOD
:用于方法- ElementType.PARAMETER
- ElementType.CONSTRUCTOR
- ElementType.LOCAL_VARIABLE
- ElementType.ANNOTATION_TYPE :另一个注释
- ElementType.PACKAGE :用于记录java文件的package信息
自定义注解
用例:
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)
@interface Todo {
public enum Priority {LOW, MEDIUM, HIGH}
public enum Status {STARTED, NOT_STARTED}
String author() default "Yash";
Priority priority() default Priority.LOW;
Status status() default Status.NOT_STARTED;
}
自定义注解类编写的一些规则:
1. Annotation
型定义为@interface
所有的Annotation
会自动继承java.lang.Annotation
这一接口,并且不能再去继承别的类或是接口.
2. 参数成员只能用public
或默认(default)
这两个访问权修饰
3. 参数成员只能用基本类型byte,short,char,int,long,float,double,boolean
八种基本数据类型和String、Enum、Class、annotations
等数据类型,以及这一些类型的数组。
4. 如果只有一个成员,成员名称最好用value
,因为默认就是它。
4. 一般定义参数成员后,都会给一个默认值,注意String
一般不用null
做默认值,可以用字符串或空串(”“)
5. 要获取类方法和字段的注解信息,必须通过Java
的反射技术来获取 Annotation
对象,因为你除此之外没有别的获取注解对象的方法
6. 注解也可以没有定义成员, 不过这样注解就没啥用了
使用:
public class BusinessLogic {
public BusinessLogic() {
super();
}
public void compltedMethod() {
System.out.println("This method is complete");
}
@Todo(priority = Todo.Priority.HIGH)
public void notYetStartedMethod() {
// No Code Written yet
}
@Todo(priority = Todo.Priority.MEDIUM, author = "Uday", status = Todo.Status.STARTED)
public void incompleteMethod1() {
//Some business logic is written
//But its not complete yet
}
@Todo(priority = Todo.Priority.LOW, status = Todo.Status.STARTED )
public void incompleteMethod2() {
//Some business logic is written
//But its not complete yet
}
}
Annotations
仅仅是元数据,和业务逻辑无关,并没有做逻辑处理。那么接下来问题来了,怎么做这个逻辑处理?
AOP+反射+自定义注解
自定义注解可以用来做权限控制、字段校验,最常见的就是Log记录。下面我们就来看看AOP+反射+自定义注解做的Log记录吧
1、新建Log记录实体类
public class LogDO {
private Long id;
private Long userId;
private String username;
private String operation;
private Integer time;
private String method;
private String params;
private String ip;
@JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
private Date gmtCreate;
//省去getter、sertter方法
}
这里持久层dao就不写了。不是重点。
2.、新建一个注解Log
import java.lang.annotation.Retention;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
String value() default "";
}
3、新建一个LogAspect来监听Log对象
@Aspect
@Component
public class LogAspect {
@Autowired
LogDao logMapper; //由于篇幅原因,持久层dao省略了
@Pointcut("@annotation(com.bootdo.common.annotation.Log)")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
long beginTime = System.currentTimeMillis();
// 执行方法
Object result = point.proceed();
// 执行时长(毫秒)
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();
LogDO sysLog = new LogDO();
//通过注解,获取注解对象
Log syslog = method.getAnnotation(Log.class);
if (syslog != null) {
// 注解上的描述
sysLog.setOperation(syslog.value());
}
// 请求的方法名
String className = joinPoint.getTarget().getClass().getName();
String methodName = signature.getName();
sysLog.setMethod(className + "." + methodName + "()");
// 请求的参数
Object[] args = joinPoint.getArgs();
try {
String params = JSONUtils.beanToJson(args[0]).substring(0, 4999);
sysLog.setParams(params);
} catch (Exception e) {
}
// 获取request
HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
// 设置IP地址
sysLog.setIp(IPUtils.getIpAddr(request));
// 用户名 这里用的是shiro管理用户信息,要是其他的框架,也可以
UserDO currUser = ShiroUtils.getUser();
//UserDO currUser = null; //如果不会shiro,用这个,同样不影响日志的打印
if (null == currUser) {
if (null != sysLog.getParams()) {
sysLog.setUserId(-1L);
sysLog.setUsername(sysLog.getParams());
} else {
sysLog.setUserId(-1L);
sysLog.setUsername("获取用户信息为空");
}
} else {
sysLog.setUserId(ShiroUtils.getUserId());
sysLog.setUsername(ShiroUtils.getUser().getUsername());
}
sysLog.setTime((int) time);
// 系统当前时间
Date date = new Date();
sysLog.setGmtCreate(date);
// 保存系统日志
logMapper.save(sysLog);
}
}
这里面有些方法HttpServletRequest
、ShiroUtils
等,都是为了得到LogDO
对象字段值的一种手段,这里就不贴代码了。总之,如上面所示,可以通过AOP
来监听注解Log
这个切面,然后,再利用反射来处理方法上的注解。
4、使用
@Log("请求访问主页")
@GetMapping({ "/index" })
String index(Model model) {
List<Tree<MenuDO>> menus = menuService.listMenuTree(getUserId());
model.addAttribute("menus", menus);
model.addAttribute("name", getUser().getName());
model.addAttribute("username", getUser().getUsername());
return "index_v1";
}
这样,通过一个注解,就可以记录一条用户使用的日志到数据库中去了。