Java 注解是从 Java5 开始添加到 Java 的。Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。
注解
定义
日常开发中新建Java类,我们使用class、interface比较多,而注解和它们一样,也是一种类的类型,他是用的修饰符为 @interface
分类
大致分为三类:自定义注解、JDK内置注解、还有第三方框架提供的注解。
- 自定义注解就是我们自己写的注解。
- JDK内置注解,比如@Override检验方法重载,@Deprecated标识方法过期等
- 第三方框架定义的注解比如SpringMVC的@Controller等
使用位置
实际开发中,注解常常出现在类、方法、成员变量、形参位置。当然还有其他位置
作用
如果说注释是写给人看的,那么注解就是写给程序看的。它更像一个标签,贴在一个类、一个方法或者字段上。它的目的是为当前读取该注解的程序提供判断依据。比如程序只要读到加了@Test的方法,就知道该方法是待测试方法,又比如@Before注解,程序看到这个注解,就知道该方法要放在@Test方法之前执行。也就是为当前的代码增加了额外的代码,可以在编译时或者运行时。
注解的本质
注解的本质是一个接口。
元注解
元注解顾名思义我们可以理解为注解的注解,它是作用在注解中,方便我们使用注解实现想要的功能。元注解分别有@Retention、 @Target、 @Document、 @Inherited和@Repeatable(JDK1.8加入)五种。
@Retention
Retention英文意思有保留、保持的意思,它表示注解存在阶段是保留在源码(编译期),字节码(类加载)或者运行期(JVM中运行)。在@Retention注解中使用枚举RetentionPolicy来表示注解保留时期。查看JDK文档
如果我们是自定义注解,则通过前面分析,我们自定义注解如果只存着源码中或者字节码文件中就无法发挥作用,而在运行期间能获取到注解才能实现我们目的,所以自定义注解中肯定是使用 @Retention(RetentionPolicy.RUNTIME)
@Target
Target的英文意思是目标,这也很容易理解,使用@Target元注解表示我们的注解作用的范围就比较具体了,可以是类,方法,方法参数变量等,同样也是通过枚举类ElementType表达作用类型。查看JDK文档
@Documented
Document的英文意思是文档。它的作用是能够将注解中的元素包含到 Javadoc 中去。查看JDk文档
@Inherited
Inherited的英文意思是继承,但是这个继承和我们平时理解的继承大同小异,一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修饰,则它的子类也继承了父类的注解。查看JDk文档
总结:
- 类继承关系中@Inherited的作用:类继承关系中,子类会继承父类使用的注解中被@Inherited修饰的注解
- 接口继承关系中@Inherited的作用:接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰
- **类实现接口关系中@Inherited的作用:**类实现接口时不会继承任何接口中定义的注解
@Repeatable
Repeatable的英文意思是可重复的。顾名思义说明被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代表不同的含义。查看JDk文档
注解的属性
注解的属性其实和类中定义的变量有异曲同工之处,只是注解中的变量都是成员变量(属性),并且注解中是没有方法的,只有成员变量,变量名就是使用注解括号中对应的参数名,变量返回值注解括号中对应参数类型。
属性类型
- 基本数据类型
- String
- 枚举类型
- 注解类型
- Class类型
- 以上类型的一维数组类型
注解属性(成员变量)赋值
如果注解又多个属性,则可以在注解括号中用“,”号隔开分别给对应的属性赋值。
@MyTestAnnotation(name = "father",age = 50)
public class Father {
}
获取注解属性
使用注解的关键是获取属性的值。如果获取注解属性,当然是反射。
自定义注解的使用案例
这里简单实现一个自定义注解实现日志记录,主要用到的技术是Java注解、Java反射、spring的aop。
第一步
提供一个自定义注解的类
/**
*自定义注解 拦截Controller
【需要设置启动aspectj注解,通知spring使用cglib而不是jdk代理】
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SystemControllerLog {
// descption 描述方法的实际作用
String description() default "";
}
第二步
实现切点类,思路是:首先见自定义注解弄为一个切入点,然后根据反射获取使用该注解的类、方法信息,同时也获取当前登录人的信息记录到数据库中。
总结:开发中使用自定义注解大多数都需要用到spring的aop编程和Java反射来实现注解的作用。
/**
* 切点类
*/
@Aspect //表示该类为一个切面类
@Component //@component组件扫描,让其 logService能注入进来
public class SystemLogAspect {
//注入Service用于把日志保存数据库
@Resource
private LogService logService;
//本地异常日志记录对象
private static final Logger logger = LoggerFactory.getLogger(SystemLogAspect. class);
//Controller层切点
@Pointcut("@annotation(com.bjsxt.util.SystemControllerLog)")
public void controllerAspect() {
}
/**
* 前置通知 用于拦截Controller层记录用户的操作
*
* @param joinPoint 切点
*/
@Before("controllerAspect()")
public void doBefore(JoinPoint joinPoint) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
//读取session中的用户
Users user = (Users) session.getAttribute("user");
//获取用户名
String username = user==null?"null":user.getFullname();
//请求的IP
String ip = request.getRemoteAddr();
try {
//*========数据库日志=========*//
Logs log = new Logs();
log.setAction(getControllerMethodDescription(joinPoint));
log.setActiontime(new Date(System.currentTimeMillis()));
log.setUsername(username);
log.setIp(ip);
logService.saveLog(log);
//System.out.println("=====前置通知结束=====");
} catch (Exception e) {
//记录本地异常日志
logger.error("====系统抛出前置通知异常====");
e.printStackTrace();
logger.error("异常信息:{}", e.getMessage());
}
}
/**
* 获取注解中对方法的描述信息 用于Controller层注解
*
* @param joinPoint 切点
* @return 方法描述
* @throws Exception
*/
public static String getControllerMethodDescription(JoinPoint joinPoint) throws Exception {
//获取类的权限定名
String targetName = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
Object[] arguments = joinPoint.getArgs();
Class targetClass = Class.forName(targetName);
Method[] methods = targetClass.getMethods();
String description = "";
for (Method method : methods) {
if (method.getName().equals(methodName)) {
Class[] clazzs = method.getParameterTypes();
if (clazzs.length == arguments.length) {
description = method.getAnnotation(SystemControllerLog. class).description();
break;
}
}
}
return description;
}
}