在Java功法大全之中,有一种神秘的功法叫做注解。
那么到底什么是注解呢?
先别急,我们先来看一个经典的例子:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Setter
@Getter
public class UserInfo {
private String userId;
private String userName;
private Integer userAge;
}
上面这个就是使用Lombok 注解实现省略Getter 和 Setter 以及ToString方法的经典用法。
上述代码编译后就会变成这样
public class UserInfo {
private String userId;
private String userName;
private Integer userAge;
public UserInfo() {
}
public String toString() {
return "UserInfo(userId=" + this.getUserId() + ", userName=" + this.getUserName() + ", userAge=" + this.getUserAge() + ")";
}
public void setUserId(String userId) {
this.userId = userId;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setUserAge(Integer userAge) {
this.userAge = userAge;
}
public String getUserId() {
return this.userId;
}
public String getUserName() {
return this.userName;
}
public Integer getUserAge() {
return this.userAge;
}
}
看到没,我们通过以下简单的三个注解,替我们生成了成员属性的Getter 和Setter 以及ToString()方法,大大简化了我们的开发。
好了现在,我们应该也许能回答这个问题了.
- 所谓注解在我看来无非是简化代码开发或实现特定功能的一种手段
- 通过使用注解可以代替我们写一大堆低技术含量、具有重复性的代码
- 注解 != 注释 ,注解一般都是具有一定特定功能的,注释只具有描述信息的功能。
- 注解处理器通过解析注解将代码和注解合成完整的代码以供JVM加载调用。
总结起来就是, 注解+ 代码= 完整代码- 当然注解不仅仅只是可以替我们写代码,注解还可以实现更多其他特定的功能。
比如Spring 扫描到类上如果有@Component注解则自动为其注册为Spring Bean.
完整的注解= 注解类+ 注解处理器
在JavaSE 5中内置了三种注解
- @Override
- 这种注解的功能是比如你的一个类实现了一个接口,但是没有重写这个方法,编译器就会报错,甚至如果你重写接口的方法名称不匹配,也会报错。
- 提醒级别是Error
- @Deprecated
- 这个注解的功能是当使用了已经过时的一些API 或者方法的时候,编译器就会产生警告信息。
- 提醒级别是Warn
- @SuppressWarnings
- 这个注解的功能是忽略一些警告,关闭不当的编译器警告信息
除此之外,Java 还允许我们自定义一些注解。
自定义注解需要一些元注解来解释自定义的注解
以下是Java中内置的几种元注解,元注解专职负责注解其他的注解
元注解名称 | 元注解功能描述 |
---|---|
@Target | 表示该注解可以用于什么地方?是作用于类上?方法上?还是字段上?CONSTRUCTOR:构造器的声明;FIELD:域声明;LOCAL_VARIABLE:局部变量声明; METHOD: 方法声明;PACKAGE:包声明;PARAMETER:参数声明;TYPE:类,接口或enum声明 |
@Retention | 表示需要在什么级别保存该注解信息。SOURCE :注解将被编译器丢弃,CLASS 注解在class中可用,但是会被JVM 丢弃。RUNTIME级别JVM运行时也保留注解,因此可以通过反射机制读取注解的信息。 |
@Documented | 将此注解包含在javadoc 中 |
@Inherited | 允许子类继承父类中的注解 |
- 比如上面的@Getter 和@Setter 以及@ToString都是SOURCE级别的
- 因此当编译后,在UserInfo.class文件中将看不到这些注解。
- 不信的话,我们可以打开lombok 源码可以看到
@Setter注解类定义如下import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.SOURCE) public @interface Setter { AccessLevel value() default AccessLevel.PUBLIC; Setter.AnyAnnotation[] onMethod() default {}; Setter.AnyAnnotation[] onParam() default {}; /** @deprecated */ @Deprecated @Retention(RetentionPolicy.SOURCE) @Target({}) public @interface AnyAnnotation { } }
- @Setter 的定义作用域是成员变量级别 ElementType.FIELD
- @Setter 的级别是源码级别 Retention(RetentionPolicy.SOURCE)
也许看后可能还是不太明白,没关系,接下来看我如何自定义一个注解@Column
新建一个类Column.java.内容如下所示:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String value() ;
int length() default 255;
}
- 纳尼?怎么看起来好像一个接口哈,是的看起来和接口确实挺像的,但是可以有默认值。
- 通过上面操作,我们定义了一个@Column注解,该注解作用在字段上,在运行时级别生效。
也许你会好奇,上面的length()方法为啥我不用Integer类型?
这是因为注解元素仅支持的如下类型,如果使用其他类型就会报错
- 所有基本类型 int,float,boolean 等
- String
- Class
- enum
- Annotation
- 所有的注解元素默认值不能有不确定的值 也就是说注解元素要么使用默认值,要么在使用注解时提供元素的值
- 对于非基本类型的注解元素,默认值不能设置null 也就是说注解类中不能对于非基本类型的默认值不能使用null,最多只能定义一些特俗的值,比如空字符串
“”
或者负数
定义完刚才的注解后我们就可以这么使用了
import custom.annotion.Column;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Setter
@Getter
public class UserInfo {
@Column(value = "USER_ID",length = 1024)
private String userId;
@Column(value = "USER_NAME",length = 1024)
private String userName;
@Column(value = "USER_AGE",length = 1024)
private Integer userAge;
}
值得注意的是,这里的@Column 就是我们刚才自定义的注解
上面我们已经自定义了一个注解类,也学会如何使用注解了,接下来我们就学习如何让自定义注解生效,毕竟没有生效的类就没有意义。
import custom.annotion.Column;
import model.UserInfo;
import java.lang.reflect.Field;
public class ScanTable {
public static void main(String[] args) {
//主方法中调用
dealWithScan(UserInfo.class);
}
private static void dealWithScan(Class<?> mClass){
//获取该类声明的所有成员变量
Field[] fieldArray= mClass.getDeclaredFields();
//遍历所有的成员变量
for (Field filed:fieldArray
) {
//获取该字段中的自定义注解
Column column=filed.getAnnotation(Column.class);
//不为空说明该字段曾经存在
if(null!=column){
System.out.println(filed.getName()+"使用了@Column注解,value="+column.value()+",length="+column.length());
}else{
System.out.println("没有使用@Column注解");
}
}
}
}
上面代码运行后打印结果如下所示:
userId使用了@Column注解,value=USER_ID,length=1024
userName使用了@Column注解,value=USER_ID,length=1024
userAge使用了@Column注解,value=USER_ID,length=1024
看到没,我们到这里已经成功自定义一个注解并获取到这些注解的值了,
然后只需要在合适的地方加载调用这个方法,就可以获取并根据这些注解信息做一番操作了。
我们已经学会了注解的基础用法,现在就来实战做点有意义的事情。
大家好,我是星云, 相信大家应该都知道的是MyBatis3 框架是一款优秀的持久层框架,但是我觉得它并不完美。
为什么这么说呢? 请大家跟我一起来看MyBatis3 现存的几个痛点
使用过MyBatis3 的应该都知道,像下面这样,在实际开发中,实体类和数据库表字段一般并不相同,需要一种映射。
为了实现这种映射,我们经常需要定义这样一个resultMap 标签
<mapper namespace="com.xingyun.sample.model.UserInfoMapper">
<resultMap type="com.xingyun.sample.model.UserInfo" id="UserInfoResultMap">
<id column="USER_ID" property="userId"/>
<result column="USER_NAME" property="userName"/>
<result column="USER_PASSWORD" property="userPassword"/>
<result column="USER_AGE" property="userAge"/>
resultMap>
<select id="findAllUserInfo" resultMap="UserInfoResultMap">
select>
mapper>
一般来说没什么问题,但是如果实体类字段特别多的话自己写起来就很麻烦。
那么有没有更简便的方法呢?
如果你愿意使用当前类库,那么你只需要添加如下代码
//打印MyBatis3 XML实体类映射Map
IDBColumnXMLHandler idbColumnXMLHandler= SmartMyBatis3App.getDBColumnXMLHandler();
System.out.println("----打印MyBatis3 XML实体类映射Map开始----");
idbColumnXMLHandler.printDocument(UserInfo.class);
System.out.println("----打印MyBatis3 XML实体类映射Map结束----");
然后就可以输出我们想要的结果:
<mapper namespace="com.xingyun.sample.model.UserInfoMapper">
<resultMap type="com.xingyun.sample.model.UserInfo" id="UserInfoResultMap">
<id column="USER_ID" property="userId"/>
<result column="USER_NAME" property="userName"/>
<result column="USER_PASSWORD" property="userPassword"/>
<result column="USER_AGE" property="userAge"/>
resultMap>
mapper>
如果你使用的不是XML配置方式,而是通过注解方式的的话,那么我们可能需要这么做。
@Select(value = "SELECT * FROM t_user_info")
@Results({
@Result(property ="userId",column ="USER_ID",javaType=Integer.class),
@Result(property ="userName",column ="USER_NAME",javaType=String.class),
@Result(property ="userPassword",column ="USER_PASSWORD",javaType=String.class),
@Result(property ="userAge",column ="USER_AGE",javaType=Integer.class),
})
JobInfo findUserInfo();
如果字段特别多,那么手写将会很累。
但是如果使用当前的类库那么只需要编写如下代码即可自动生成:
//打印MyBatis3 注解实体类映射Map
iDBColumnAnnotationHandler= SmartMyBatis3App.getDBColumnAnnotationHandler();
String result=iDBColumnAnnotationHandler.autoCreateMappingModel(UserInfo.class);
System.out.println("----打印MyBatis3 注解实体类映射Map开始----");
System.out.println(result);
System.out.println("----打印MyBatis3 注解实体类映射Map结束---");
上述操作的前提需要在实体类中添加一些注解
import com.xingyun.annotations.model.DBColumn;
import com.xingyun.annotations.model.DBConstraints;
import com.xingyun.annotations.model.DBTable;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.stereotype.Component;
@Getter
@Setter
@ToString
@DBTable(name = "t_user_info")
@Component
public class UserInfo {
//数据库字段是主键,必须唯一,不可为空
//列的名称,类型,长度
@DBConstraints(primaryKey = true,unique = true,allowNull = false)
@DBColumn(name = "USER_ID",type = "bigint",length = 20,description="用户主键")
private Integer userId;
//用户名称
@DBConstraints(unique = true,allowNull = false)
@DBColumn(name = "USER_NAME",description="用户名称")
private String userName;
//用户密码
//默认约束 等价于
// @DBConstraints(primaryKey = false,unique = false,allowNull = true)
@DBConstraints
@DBColumn(name = "USER_PASSWORD",description="用户密码")
private String userPassword;
//默认约束 等价于
// @DBConstraints(primaryKey = false,unique = false,allowNull = true)
@DBConstraints
@DBColumn(name = "USER_AGE",type = "int",length = 20)
private Integer userAge;
}
下载地址:https://github.com/geekxingyun/SmartMybatis3/tree/master/lib
mvn install:install-file -DgroupId=com.xingyun -DartifactId=smart-mybatis3 -Dversion=1.0-RELEASE -Dpackaging=jar -Dfile=smart-mybatis3-1.0-RELEASE.jar
pom.xml中添加依赖
<dependency>
<groupId>com.xingyungroupId>
<artifactId>smart-mybatis3artifactId>
<version>1.0-RELEASEversion>
dependency>
主要有四大接口
public interface ISmartObjectHandler {
/**
* 获取注解映射的数据库表列名
* @param mClass
* @return
*/
List<String> getDBColumnList(Class<?> mClass);
/**
* 获取字段名称列表
* @param mClass
* @return
*/
List<String> getFieldNameList(Class<?> mClass);
/**
* 获取字段类型带包名列表
* @param mClass
* @return
*/
List<String> getFieldTypeList(Class<?> mClass);
/**
* 获取字段类型不带包名列表
* @param mClass
* @return
*/
List<String> getFieldSimpleTypeList(Class<?> mClass);
}
import com.xingyun.annotations.model.DBColumn;
import com.xingyun.annotations.model.DBConstraints;
import com.xingyun.annotations.model.DBTable;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.stereotype.Component;
@Getter
@Setter
@ToString
@DBTable(name = "t_user_info")
@Component
public class UserInfo {
//数据库字段是主键,必须唯一,不可为空
//列的名称,类型,长度
@DBConstraints(primaryKey = true,unique = true,allowNull = false)
@DBColumn(name = "USER_ID",type = "bigint",length = 20,description="用户主键")
private Integer userId;
//用户名称
@DBConstraints(unique = true,allowNull = false)
@DBColumn(name = "USER_NAME",description="用户名称")
private String userName;
//用户密码
//默认约束 等价于
// @DBConstraints(primaryKey = false,unique = false,allowNull = true)
@DBConstraints
@DBColumn(name = "USER_PASSWORD",description="用户密码")
private String userPassword;
//默认约束 等价于
// @DBConstraints(primaryKey = false,unique = false,allowNull = true)
@DBConstraints
@DBColumn(name = "USER_AGE",type = "int",length = 20)
private Integer userAge;
}
如果想获取这个注解的值
@DBTable(name = "t_user_info")
public class UserInfo {
}
调用方法如下:
//打印MyBatis3 映射表名
IDBTableXMLHandler iDBTableXMLHandler= SmartMyBatis3App.getDBTableXMLHandler();
String dbTableName=iDBTableXMLHandler.getDBTableName(UserInfo.class);
System.out.println("----打印MyBatis3 映射表名开始----");
System.out.println(dbTableName);
System.out.println("----打印MyBatis3 映射表名结束----");
如果想获取类似这样的内容
<mapper namespace="com.xingyun.sample.model.UserInfoMapper">
<resultMap type="com.xingyun.sample.model.UserInfo" id="UserInfoResultMap">
<id column="USER_ID" property="userId"/>
<result column="USER_NAME" property="userName"/>
<result column="USER_PASSWORD" property="userPassword"/>
<result column="USER_AGE" property="userAge"/>
resultMap>
mapper>
那么调用方法如下:
//打印MyBatis3 XML实体类映射Map
IDBColumnXMLHandler idbColumnXMLHandler= SmartMyBatis3App.getDBColumnXMLHandler();
System.out.println("----打印MyBatis3 XML实体类映射Map开始----");
idbColumnXMLHandler.printDocument(UserInfo.class);
System.out.println("----打印MyBatis3 XML实体类映射Map结束----");
如果想获取类似这样的内容
@Results({
@Result(property ="userId",column ="USER_ID",javaType=Integer.class),
@Result(property ="userName",column ="USER_NAME",javaType=String.class),
@Result(property ="userPassword",column ="USER_PASSWORD",javaType=String.class),
@Result(property ="userAge",column ="USER_AGE",javaType=Integer.class),
})
那么调用方法如下:
//打印MyBatis3 注解实体类映射Map
IDBColumnAnnotationHandler iDBColumnAnnotationHandler= SmartMyBatis3App.getDBColumnAnnotationHandler();
String result=iDBColumnAnnotationHandler.autoCreateMappingModel(UserInfo.class);
System.out.println("----打印MyBatis3 注解实体类映射Map开始----");
System.out.println(result);
System.out.println("----打印MyBatis3 注解实体类映射Map结束---");
如果想获取类似下面这样的成员属性名称列表
userId
userName
userPassword
userAge
调用方法如下:
//打印UserInfo 对象字段集合
ISmartObjectHandler iSmartObjectHandler=SmartMyBatis3App.getSmartObjectHandler();
List<String> fieldNameList=iSmartObjectHandler.getFieldNameList(UserInfo.class);
System.out.println("----打印UserInfo 对象字段集合开始---");
for (String fieldName:fieldNameList
) {
System.out.println(fieldName);
}
System.out.println("----打印UserInfo 对象字段集合结束---");
输出结果:
----打印UserInfo 对象字段集合开始---
userId
userName
userPassword
userAge
----打印UserInfo 对象字段集合结束---
//打印 注解映射表名的字段类型集合
List<String> dbColumnNameList=iSmartObjectHandler.getDBColumnList(UserInfo.class);
System.out.println("----打印 注解映射表名的字段类型集合开始---");
for (String dbColumnName:dbColumnNameList
) {
System.out.println(dbColumnName);
}
System.out.println("----打印 注解映射表名的字段类型集合结束---");
输出结果:
----打印 注解映射表名的字段类型集合开始---
USER_ID
USER_NAME
USER_PASSWORD
USER_AGE
----打印 注解映射表名的字段类型集合结束---
//打印 带包名的字段类型集合
List<String> fieldTypeList=iSmartObjectHandler.getFieldTypeList(UserInfo.class);
System.out.println("----打印 带包名的字段类型集合开始---");
for (String fieldType:fieldTypeList
) {
System.out.println(fieldType);
}
System.out.println("----打印 带包名的字段类型集合结束---");
输出结果
----打印 带包名的字段类型集合开始---
java.lang.Integer
java.lang.String
java.lang.String
java.lang.Integer
----打印 带包名的字段类型集合结束---
//打印 不带包名的字段类型集合
List<String> fieldSimpleTypeList=iSmartObjectHandler.getFieldSimpleTypeList(UserInfo.class);
System.out.println("----打印 不带包名的字段类型集合开始---");
for (String fieldSimpleType:fieldSimpleTypeList
) {
System.out.println(fieldSimpleType);
}
System.out.println("----打印 不带包名的字段类型集合结束---");
输出结果:
----打印 不带包名的字段类型集合开始---
Integer
String
String
Integer
----打印 不带包名的字段类型集合结束---
要想实体类和数据库字段之间建立映射,那么我们首先需要针对表名做一个注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义表注解
*/
//作用于类级别
@Target({ElementType.TYPE})
//运行时保留该注解
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
String name() default "";
}
用法的话就是:
@DBTable(name = "t_user_info")
public class UserInfo {
}
其次最重要的是实体类名称和数据库字段名称做一个映射
因此我们创建注解类DBColumn.java 内容如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DBColumn {
//数据库字段的名称
String name() default "";
//数据库字段的长度
long length() default 255;
//数据库字段的类型
String type() default "varchar";
//数据库字段说明
String description() default "";
}
用法就像这样:
public class UserInfo {
//列的名称,类型,长度
@DBColumn(name = "USER_ID",type = "bigint",length = 20,description="用户主键")
private Integer userId;
}
我们还需要对字段的一些基本操作制定一些约束,比如是否可以为空,是否是主键之类。
注解类DBConstraints.java内容如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义字段约束注解
*/
//作用于成员变量字段
@Target(ElementType.FIELD)
//运行时保留该注解
@Retention(RetentionPolicy.RUNTIME)
public @interface DBConstraints {
//是否是主键
boolean primaryKey() default false;
//是否唯一
boolean unique() default false;
//是否允许为空
boolean allowNull() default true;
}
用法的话就像这样
public class UserInfo {
//数据库字段是主键,必须唯一,不可为空
@DBConstraints(primaryKey = true,unique = true,allowNull = false)
private Integer userId;
}
完整的一个测试实体类如下:
import com.xingyun.annotations.model.DBColumn;
import com.xingyun.annotations.model.DBConstraints;
import com.xingyun.annotations.model.DBTable;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.stereotype.Component;
@Getter
@Setter
@ToString
@DBTable(name = "t_user_info")
@Component
public class UserInfo {
//数据库字段是主键,必须唯一,不可为空
//列的名称,类型,长度
@DBConstraints(primaryKey = true,unique = true,allowNull = false)
@DBColumn(name = "USER_ID",type = "bigint",length = 20,description="用户主键")
private Integer userId;
//用户名称
@DBConstraints(unique = true,allowNull = false)
@DBColumn(name = "USER_NAME",description="用户名称")
private String userName;
//用户密码
//默认约束 等价于
// @DBConstraints(primaryKey = false,unique = false,allowNull = true)
@DBConstraints
@DBColumn(name = "USER_PASSWORD",description="用户密码")
private String userPassword;
//默认约束 等价于
// @DBConstraints(primaryKey = false,unique = false,allowNull = true)
@DBConstraints
@DBColumn(name = "USER_AGE",type = "int",length = 20)
private Integer userAge;
}
上面的注解已经写好了,也学会使用了,但是我们怎么解析我们自定义的注解呢?
还记得我们之前是这个注解的使用用法吧
@DBTable(name = "t_user_info")
public class UserInfo {
}
想要获取 t_user_info这个值,其实很简单,
我们只需要通过反射如下编码即可:
import com.xingyun.annotations.model.DBTable;
import com.xingyun.annotations.processor.interfaces.IDBTableXMLHandler;
@SuppressWarnings("unused")
public class DBTableXMLHandler implements IDBTableXMLHandler {
/**
* 获取注解的数据库表名
* @param mClass
* @return
*/
@Override
public String getDBTableName(Class<?> mClass){
DBTable dbTable=mClass.getAnnotation(DBTable.class);
if(null!=dbTable){
return dbTable.name();
}
return null;
}
}
在需要的地方我们可以通过这样这个名字:
DBTableXMLHandler dbTableXMLHandler=new DBTableXMLHandler();
String tableName=dbTableXMLHandler.getDBTableName(UserInfo.class);
这样就可以获取
- 值得注意的是,为了让项目更清晰,我这里分层写了一个接口类
- 让当前类实现IDBTableXMLHandler 接口,
- 这里你可以暂时忽略implements IDBTableXMLHandler
- 这里的调用方法可能和Github 上描述的虽然不太一样,但是这样是最直接的用法。
- 关键点在于我们首先需要反射获取类上的@DBTable 注解对象,然后就很简单了
还记得刚才我们的注解使用时这样的
public class UserInfo {
//列的名称,类型,长度
@DBColumn(name = "USER_ID",type = "bigint",length = 20,description="用户主键")
private Integer userId;
我们想要获取name的值,还是需要通过反射,不过这次不太一样:
public List<String> getDBColumnList(Class<?> mClass){
List<String> dbColumnList=new ArrayList<>();
//遍历字段集合
Field[] fieldArray=mClass.getDeclaredFields();
for (Field field:fieldArray
) {
DBColumn dbColumn=field.getAnnotation(DBColumn.class);
String dbColumnName;
if(null!=dbColumn){
dbColumnName=dbColumn.name();
dbColumnList.add(dbColumnName);
}
}
return dbColumnList;
}
这次不同的是这个注解是成员变量上的,因此我们需要通过mClass.getDeclaredFields();获取成员变量数组,然后判断成员变量上面有没有@DBColumn这个注解对象,如果有,那么获取到这个对象,然后赋值即可,
剩下的相信对于聪明的你来说就比较简单了,
我们只需要使用Dom4j API 生成XML 代码片段并格式化输出即可
或者使用StringBuild 拼接内容即可
其他几种解析方式和这个类似就不过做过多重复介绍了。
对了,成员变量字段列表获取的方式也比较简单,这里简单贴一下
public List<String> getFieldNameList(Class<?> mClass){
List<String> fieldNameList=new ArrayList<>();
//遍历字段集合
Field[] fieldArray=mClass.getDeclaredFields();
for (Field field:fieldArray
) {
fieldNameList.add(field.getName());
}
return fieldNameList;
}
Github 源码地址:SmartMyBatis3
本篇完~
交流及分享,分享才能进步,如果喜欢我的博文,欢迎,点赞、评论或转发。
我是星云,我们下篇再见~