今天有幸阅读了秒懂,Java 注解 (Annotation)你可以这样学,对注解的应用理解加深了不少,特在此总结下。
1:Java中常见注解
详细介绍:基本内置注解
- @Override
用在方法上,表示这个方法重写了父类的方法,如toString()。 - @Deprecated
表示这个方法已经过期,不建议开发者使用。(暗示在将来某个不确定的版本,就有可能会取消掉) - @SuppressWarnings
这个注解的用处是忽略警告信息 - @SafeVarargs
参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告 - @FunctionalInterface
用于约定函数式接口。 如果接口中只有一个抽象方法(可以包含多个默认方法或多个static方法),该接口称为函数式接口。函数式接口其存在的意义,主要是配合Lambda 表达式 来使用。
2: 注解分类
按运行机制分:
- 源码注解
注解只在源码中存在,编译成.class文件就不存在了 - 编译时注解
注解在源码和.class文件中都会存在。比如说@Override - 运行时注解
在运行阶段还会起作用,甚至会影响运行逻辑的注解。比如说@Autowired
下图是类加载的过程,更好的理解三种注解(图片来源)
按来源分:
- JDK内置注解
- java第三方注解
- 自定义注解
- 元注解
3:自定义注解
1:自定义注解的语法要求
在这里不详细记录,仅展示一个 @Description
注解示例,关于元注解详见元注解详细内容
package grammar;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description 自定义注解的语法要求
* @since 2018年10月29日 下午4:47:07
* @author LiuLiBin
*/
/***************** 元注解 **********************************/
/*@Target 表示这个注解能放在什么位置上,是只能放在类上?还是即可以放在方法上,又可以放在属性上
*
* ElementType.TYPE:能修饰类、接口或枚举类型
ElementType.FIELD:能修饰成员变量
ElementType.METHOD:能修饰方法
ElementType.PARAMETER:能修饰参数
ElementType.CONSTRUCTOR:能修饰构造器
ElementType.LOCAL_VARIABLE:能修饰局部变量
ElementType.ANNOTATION_TYPE:能修饰注解
ElementType.PACKAGE:能修饰包
* */
@Target({ElementType.METHOD , ElementType.TYPE})
/*@Retention
* 表示生命周期,
*
* RetentionPolicy.SOURCE: 注解只在源代码中存在,编译成class之后,就没了。@Override 就是这种注解
*
* RetentionPolicy.CLASS: 注解在java文件编程成.class文件后,依然存在,但是运行起来后就没了
* @Retention的默认值,即当没有显式指定@Retention的时候,就会是这种类型。
*
* RetentionPolicy.RUNTIME: 注解在运行起来之后依然存在,程序可以通过反射获取这些信息
*
* */
@Retention( RetentionPolicy.RUNTIME)
/*@Inherited 表示该注解具有继承性
如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。*/
@Inherited
//元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去
@Documented
//使用 @interface 关键字定义注解
public @interface Description {
/* 成员变量
* 1. 注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型
*
* 2. 注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组
*
* 3. 如果注解只有一个成员,则成员名必须取名为 value() ,在使用时可以忽略成员名和赋值号(=)
*
* 4. 注解类可以没有成员,没有成员的注解称为标识注解
* */
String desc();
String author();
//可以用 default 为成员指定一个默认的值
int age() default 18;
}
2:使用自定义注解
@<注解名>(<成员名1>=<成员值1>,<成员名2>=<成员值2>,..)
package grammar;
@Description(desc="I am" ,author="liu",age=22)
public class UseAnnotation {
public String Test() {
return "red";
}
}
3:解析注解
概念: 通过反射获取类、函数或成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。注意,解析注解时, @Retention(RetentionPolicy.RUNTIME)
是必须的。
- 先获取类对象
- 类对象调用
isAnnotationPresent(Class<? extends Annotation> annotationClass)
判断是否应用了某个注解- 通过 getAnnotation() 方法来获取 Annotation 对象,或者getAnnotations() 方法获取所有应用在该类上的注解
示例:
注解类:
package grammar;
@Target({ElementType.METHOD , ElementType.TYPE})
@Retention( RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DesSimple {
String value();
}
应用注解类:
package grammar;
@DesSimple("this is a type ano")
public class Child {
@DesSimple("this is a method ano")
public String name() {
return "name";
}
}
解析注解类:
package grammar;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
public class ParseAnnotation {
public static void main(String[] args) {
try {
//1.使用类加载器加载类对象
Class clazz = Class.forName("grammar.Child");
//2. 判断是否应用了这个注解
boolean hasAnno = clazz.isAnnotationPresent(DesSimple.class);
//3. 获取Annotation对象
if(hasAnno) {
DesSimple ds = (DesSimple)clazz.getAnnotation(DesSimple.class);
System.out.println(ds.value());
}
//4. 找到方法上的注解
Method[] ms = clazz.getMethods();
/******** 第一种解析方式 ************/
for(Method m:ms) {
boolean isMExist = m.isAnnotationPresent(DesSimple.class);
if(isMExist ) {
DesSimple dsm = (DesSimple)m.getAnnotation(DesSimple.class);
System.out.println(dsm.value());
}
}
/************ 另一种解析方式 ******************/
for(Method m: ms) {
Annotation[] ans = m.getAnnotations();//获取所有注解在该方法上的注解
for(Annotation an:ans) {
if(an instanceof DesSimple) {
DesSimple desSimple = (DesSimple)an;
System.out.println(desSimple.value());
}
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
4:自定义注解的应用-仿Hibernate注解
本部分知识点大部分引用How2j.cn
- hibernate两种配置方式
hibernate有两种配置方式,分别是*.hbm.xml 配置方式 和注解方式。 虽然方式不一样,但是都是用于解决如下问题:
- 当前类是否实体类
- 对应的表名称
- 主键对应哪个属性, 自增长策略是什么,对应字段名称是什么
- 非主键属性对应字段名称是什么
接下来,我会做一套仿hibernate的注解,并且在一个实体类Hero上运用这些注解,并通过反射解析这些注解信息,来解决上述的问题
- 自定义hibernate注解 1
参考hibernate的 注解配置方式 ,自定义5个注解,分别对应hibernate中用到的注解:
hibernate_annotation.MyEntity 对应 javax.persistence.Entity
hibernate_annotation.MyTable 对应 javax.persistence.Table
hibernate_annotation.MyId 对应 javax.persistence.Id
hibernate_annotation.MyGeneratedValue 对应 javax.persistence.GeneratedValue
hibernate_annotation.MyColumn 对应 javax.persistence.Column
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyEntity {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTable {
//对应表名
String name();
}
@Target({METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyId {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyGeneratedValue {
//增长策略
String strategy();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyColumn {
//对应列名
String value();
}
- 运用在Hero对象上
package hibernate;
@MyEntity
@MyTable(name="hero_")
public class Hero {
private int id;
private String name;
private int damage;
private int armor;
@MyId
@MyGeneratedValue(strategy = "identity")
@MyColumn("id_")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@MyColumn("name_")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@MyColumn("damage_")
public int getDamage() {
return damage;
}
public void setDamage(int damage) {
this.damage = damage;
}
@MyColumn("armor_")
public int getArmor() {
return armor;
}
public void setArmor(int armor) {
this.armor = armor;
}
}
- 创建一个解析类ParseHibernateAnnotation ,获取Hero类上配置的注解信息
思路如下:
- 首先获取Hero.class类对象
- 判断本类是否进行了MyEntity 注解
- 获取注解 MyTable
- 遍历所有的方法,如果某个方法有MyId注解,那么就记录为主键方法primaryKeyMethod
- 把主键方法的自增长策略注解MyGeneratedValue和对应的字段注解MyColumn 取出来,并打印
- 遍历所有非主键方法,并且有MyColumn注解的方法,打印属性名称和字段名称的对应关系。
示例:
package hibernate;
import java.lang.reflect.Method;
public class ParseHibernateAnnotation {
public static void main(String[] args) {
try {
//1. 获取class类对象
Class hclass = Class.forName("hibernate.Hero");
//判断本类是否进行了MyEntity 注解
boolean isEntity = hclass.isAnnotationPresent(MyEntity.class);
if(!isEntity) {
System.out.println("Hero 不是实体类");
}
else {
System.out.println("Hero 是实体类");//获取注解MyTable
boolean isTable = hclass.isAnnotationPresent(MyTable.class);
if(isTable) {
MyTable table = (MyTable)hclass.getAnnotation(MyTable.class);
System.out.println("其对应的表名为:"+table.name());
}
// 遍历所有的方法,如果某个方法有MyId注解,那么就记录为主键方法primaryKeyMethod
Method[] ms = hclass.getMethods();
Method primaryKeyMethod = null;
for(Method m:ms) {
MyId myId = m.getAnnotation(MyId.class);
if(null!=myId){
primaryKeyMethod = m;
break;
}
}
//存在主键方法
if(null!=primaryKeyMethod){
System.out.println("找到主键:" + method2attribute( primaryKeyMethod.getName() ));
MyGeneratedValue myGeneratedValue =
primaryKeyMethod.getAnnotation(MyGeneratedValue.class);
System.out.println("其自增长策略是:" +myGeneratedValue.strategy());
MyColumn myColumn = primaryKeyMethod.getAnnotation(MyColumn.class);
System.out.println("对应数据库中的字段是:" +myColumn.value());
}
System.out.println("其他非主键属性分别对应的数据库字段如下:");
for (Method m: ms) {
//判断是否非主键方法
if(m==primaryKeyMethod){
continue;
}
MyColumn myColumn = m.getAnnotation(MyColumn.class);
//那些setter方法上是没有MyColumn注解的
if(null==myColumn)
continue;
System.out.format("属性: %s\t对应的数据库字段是:%s%n",method2attribute(m.getName()),myColumn.value());
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static String method2attribute(String methodName) {
String result = methodName; ;
result = result.replaceFirst("get", "");
result = result.replaceFirst("is", "");
if(result.length()<=1){
return result.toLowerCase();
}
else{
return result.substring(0,1).toLowerCase() + result.substring(1,result.length());
}
}
}
注解的内容就暂时写到这,从最后一个例子很明显看出了注解和反射对于加深框架的理解很有帮助,接下来计划去学习了解Spring框架源码
源码:注解教程