什么是注解
Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。
Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。。
注解用处
我们最常见的就是继承时自动出现的@Override,以及使用spring时配置使用的@Conteoller、@Service、@Autowired...
这些都属于注解。而作用大体归为:
- 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等(我们注释里面用到的)
- 跟踪代码依赖性,实现替代配置文件功能。(spring注解的大量使用,可以简化配置文件)
- 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。
注解的原理
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
所有 annotation 类型都要扩展的公共接口。注意,手动扩展该公共接口的接口不 定义 annotation 类型。还要注意此接口本身不定义 annotation 类型。
讲了这么多,现在说说系统给我们带来了哪些注解:
第一种:就像老祖宗一样的注解,像Object是所有类的父类一样,没错就是一种叫做元注解的东西,用来专门注解其他注解的注解。
注释类型摘要
Documented
指示某一类型的注释将通过 javadoc 和类似的默认工具进行文档化。
Inherited
指示注释类型被自动继承。
Retention
指示注释类型的注释要保留多久。
Target
指示注释类型所适用的程序元素的种类。
没错上面这四种就是官方API中给出的四种元注解的解释。
翻译成我们自己的话就是:
Documented:这个注解要不要包含在javaDoc里面,展示不展示啊!
Inherited:子类继承这个类,那修饰父类的注解子类需要继承不
Retention:什么时候使用该注解,编译时还是类加载时还是啥时候啊,这个自己指定
Target:注解用在什么地方
具体点就是:
1.@Retention– 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
2.Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType参数包括
● ElementType.CONSTRUCTOR:用于描述构造器
● ElementType.FIELD:成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE:用于描述局部变量
● ElementType.METHOD:用于描述方法
● ElementType.PACKAGE:用于描述包
● ElementType.PARAMETER:用于描述参数
● ElementType.TYPE:用于描述类、接口(包括注解类型) 或enum声明
3.@Documented–一个简单的Annotations标记注解,表示是否将注解信息添加在java文档中。
4.@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。
常见的系统注解:
1.Override
java.lang.Override是一个标记类型注解,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种注解在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。
2.Deprecated
Deprecated也是一种标记类型注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。所以使用这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。
3.SuppressWarnings
SuppressWarning不是一个标记类型注解。它有一个类型为String[]的成员,这个成员的值为被禁止的警告名。对于javac编译器来讲,被-Xlint选项有效的警告名也同样对@SuppressWarings有效,同时编译器忽略掉无法识别的警告名。
@SuppressWarnings("unchecked")
接下来搞自定义注解了
自定义注解类编写的一些规则:
1. Annotation型定义为@interface,所有的Annotation会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
2.参数成员只能用public或默认(default)这两个访问权修饰
3.参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation对象,因为你除此之外没有别的获取注解对象的方法
5. 注解也可以没有定义成员, 不过这样注解就没啥用了
PS:自定义注解需要使用到元注解
首先定义三个,分别为地址、性格以及证件
import static java.lang.annotation.ElementType.FIELD;
import java.lang.annotation.Target;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
/**
* 地址注解
* */
@Target(FIELD)//定义使用在哪里(当前为可用于成员变量、枚举常量)
@Retention(RUNTIME)//始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。
@Documented
public @interface Address {
String value() default "";
}
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
/**
* 性格注解
* */
@Target(FIELD)
@Documented
@Retention(RUNTIME)
public @interface Character {
/**
* 性格枚举
* */
public enum Chart{HAPPY,SAD,CRAZY};
/**
* 性格属性
* */
Chart personChart() default Chart.HAPPY;
}
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* 证件信息注解
* */
@Target(FIELD)
@Documented
@Retention(RUNTIME)
public @interface Card {
public int id() default -1;
String name() default "";
String num() default "";
}
下面是引用了自定义注解的类
public class Person {
@Address("黑龙江省哈尔滨市")
String myAddress;
@Character(personChart = Chart.CRAZY)
String myCharactor;
@Card(id=25,name="Wizard",num="4428")
String myCard;
}
下面通过反射获取注解的值
/**
* 测试类
* */
public class AnnotationDemo{
public static void main(String[] args) {
getPersonInfo(Person.class);
}
public static void getPersonInfo(Class<?> clazz){
String address = "地址";
String chart = "性格";
String card = "证件信息";
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//判断该属性注解种类名称
if(field.isAnnotationPresent(Address.class)){
Address addre = field.getAnnotation(Address.class);
address = address+":"+addre.value();
System.out.println(address);
}else if(field.isAnnotationPresent(Character.class)){
Character chara = field.getAnnotation(Character.class);
chart = chart+":"+chara.personChart();
System.out.println(chart);
}else if(field.isAnnotationPresent(Card.class)){
Card cad = field.getAnnotation(Card.class);
card = card+":id"+cad.id()+";name"+cad.name()+";num:"+cad.num();
System.out.println(card);
}
}
}
}
这里多说一句我在实际项目中用到的自定义注解
在使用spring的AOP进行系统日志记录时,使用自定义注解将用户操作行为记录下来
首先定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface ControlLog {
//记录操作描述
String description() default "";
//模块名称
String module() default "";
//日志级别
String grade() default "";
//日志类型
String type() default "";
}
然后使用AOP定义切面,在指定位置添加注解(下面是springMVC控制层,看出注解含义为客户正在删除会议,且该级别和模块都能自己定义)
@RequestMapping("/deleteMeet.action")
@ResponseBody
@ControlLog(description = "会议删除",module = MEET,grade = GRADEIMPORT , type = RETYPEOPERATION)
public String meetDelete(String[] params) {
log.info("删除会议");
String code = meetService.deleteMeet(params);
return code;
}
然后再AOP切面的后置通知中,将使用我们自定义注解的方法的相关信息写入系统日志
@After(value = "execution(* com.youo.scan.controller.*.*(..))&& @annotation(controlLog)",argNames = "controlLog")
public void after(ControlLog controlLog){
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
User_Info user = (User_Info)request.getSession().getAttribute("user");
String remoteAddr = request.getHeader("X-FORWARDED-FOR"); //获取真实IP地址
if (remoteAddr == null || "".equals(remoteAddr)) {
remoteAddr = request.getRemoteAddr();
}
log.info("记录系统日志");
String module = controlLog.module();//获取模块名称
String inputdate = DateUtil.getDateTime();//录入日期
String recontent = controlLog.description();//操作内容
String type = controlLog.type();//日志类型
String grade = controlLog.grade();//日志界别
recordInfo = new Record_Info();
recordInfo.setReType(type);
recordInfo.setGrade(grade);
recordInfo.setReContent(recontent);
recordInfo.setInputdate(inputdate);
String uName = "";
if(user != null)uName = user.getUName();
recordInfo.setInputUser(uName);
recordInfo.setModule(module);
recordInfo.setIP(remoteAddr);
int num = systemDataService.saveLog(recordInfo);
if(num>0)log.info("日志纪录成功");
}
好了,注解的自定义以及使用场景就列出来了。像spring的以来注入注解,也是简化配置文件大量配置bean的有效方法,因此注解的使用可以大大简化一些场景的开发。