Java之注解的魔法

Java中注解的魔法

    • 0x01 什么是注解?
      • 1.1 关于注解的一些理解
    • 0x02 Java 注解知多少?
      • 2.1 内置注解
      • 2.2 自定义注解
        • 2.2.1 了解元注解
        • 2.2.2 自定义注解类
          • 2.2.2.1 注解元素类型限制
          • 2.2.2.2 注解元素默认值限制
        • 2.2.3 使用自定义注解
        • 2.2.4 编写注解处理器
    • 0x03 注解实战开发开源项目SmartMybatis3
      • 3.1.为什么创建SmartMyBatis3?
        • 3.1.1 痛点一:MyBatis3 使用XML实体类和数据库表字段映射
        • 3.1.2 痛点二:注解方式返回实体类与数据库字段映射
      • 3.2 如何集成SmartMyBatis3?
        • 3.2.1 下载依赖
        • 3.2.2 添加依赖
          • 方式一:安装到本地仓库(推荐)
          • 方式二:直接将jar 添加到项目中
        • 3.2.3 开始使用
          • 3.2.3.1 在实体类上添加注解
          • 3.2.3.2 主方法中调用
            • 3.2.3.2.1 获取注解的表名
            • 3.2.3.2.2 获取XML 中的ResultMap
            • 3.2.3.2.3 获取@Results({}) 注解返回实体类映射
            • 3.2.3.2.4 获取实体类成员属性名称集合
            • 3.2.3.2.5 获取解映射表名的字段类型集合
            • 3.2.3.2.6 获取带包名的成员变量类型集合
            • 3.2.3.2.7 获取不带包名的成员变量类型集合
    • 0x04 关于SmartMybatis3项目实现思路
      • 4.1 定义注解实体类
        • 4.1.1 @DBTable注解实体类
        • 4.1.2 @DBColumn
        • 4.1.3 @DBConstraints 注解
      • 4.2 编写注解处理器
        • 4.2.1 表名@DBTable注解的解析
        • 4.2.2列名@DBColumn注解的解析
      • 仓库开源源码

0x01 什么是注解?

在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()方法,大大简化了我们的开发。

  1. @ToString
  2. @Setter
  3. @Getter

好了现在,我们应该也许能回答这个问题了.

1.1 关于注解的一些理解

  1. 所谓注解在我看来无非是简化代码开发或实现特定功能的一种手段
  2. 通过使用注解可以代替我们写一大堆低技术含量、具有重复性的代码
  3. 注解 != 注释 ,注解一般都是具有一定特定功能的,注释只具有描述信息的功能。
  4. 注解处理器通过解析注解将代码和注解合成完整的代码以供JVM加载调用。
    总结起来就是, 注解+ 代码= 完整代码
  5. 当然注解不仅仅只是可以替我们写代码,注解还可以实现更多其他特定的功能。
    比如Spring 扫描到类上如果有@Component注解则自动为其注册为Spring Bean.

    完整的注解= 注解类+ 注解处理器

Java之注解的魔法_第1张图片

0x02 Java 注解知多少?

2.1 内置注解

在JavaSE 5中内置了三种注解

  • @Override
    • 这种注解的功能是比如你的一个类实现了一个接口,但是没有重写这个方法,编译器就会报错,甚至如果你重写接口的方法名称不匹配,也会报错。
    • 提醒级别是Error
  • @Deprecated
    • 这个注解的功能是当使用了已经过时的一些API 或者方法的时候,编译器就会产生警告信息。
    • 提醒级别是Warn
  • @SuppressWarnings
  • 这个注解的功能是忽略一些警告,关闭不当的编译器警告信息

除此之外,Java 还允许我们自定义一些注解。

2.2 自定义注解

自定义注解需要一些元注解来解释自定义的注解

2.2.1 了解元注解

以下是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)

2.2.2 自定义注解类

也许看后可能还是不太明白,没关系,接下来看我如何自定义一个注解@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注解,该注解作用在字段上,在运行时级别生效。
2.2.2.1 注解元素类型限制

也许你会好奇,上面的length()方法为啥我不用Integer类型?
这是因为注解元素仅支持的如下类型,如果使用其他类型就会报错

  • 所有基本类型 int,float,boolean 等
  • String
  • Class
  • enum
  • Annotation
2.2.2.2 注解元素默认值限制
  • 所有的注解元素默认值不能有不确定的值 也就是说注解元素要么使用默认值,要么在使用注解时提供元素的值
  • 对于非基本类型的注解元素,默认值不能设置null 也就是说注解类中不能对于非基本类型的默认值不能使用null,最多只能定义一些特俗的值,比如空字符串“”或者负数

2.2.3 使用自定义注解

定义完刚才的注解后我们就可以这么使用了

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 就是我们刚才自定义的注解

2.2.4 编写注解处理器

上面我们已经自定义了一个注解类,也学会如何使用注解了,接下来我们就学习如何让自定义注解生效,毕竟没有生效的类就没有意义。

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

看到没,我们到这里已经成功自定义一个注解并获取到这些注解的值了,
然后只需要在合适的地方加载调用这个方法,就可以获取并根据这些注解信息做一番操作了。

0x03 注解实战开发开源项目SmartMybatis3

我们已经学会了注解的基础用法,现在就来实战做点有意义的事情。

3.1.为什么创建SmartMyBatis3?

大家好,我是星云, 相信大家应该都知道的是MyBatis3 框架是一款优秀的持久层框架,但是我觉得它并不完美。

为什么这么说呢? 请大家跟我一起来看MyBatis3 现存的几个痛点

3.1.1 痛点一:MyBatis3 使用XML实体类和数据库表字段映射

使用过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>

3.1.2 痛点二:注解方式返回实体类与数据库字段映射

如果你使用的不是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;
}

3.2 如何集成SmartMyBatis3?

3.2.1 下载依赖

下载地址:https://github.com/geekxingyun/SmartMybatis3/tree/master/lib

3.2.2 添加依赖

方式一:安装到本地仓库(推荐)
  • 双击 install-smart-mybatis3-1.0-RELEASE.bat
  • 或者在*.jar 同级目录下执行maven 安装jar到本地仓库命令
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>
方式二:直接将jar 添加到项目中

3.2.3 开始使用

主要有四大接口

  • IDBTableXMLHandler iDBTableXMLHandler;
    获取@DBTable注解的表名
  • IDBColumnXMLHandler idbColumnXMLHandler;
  • IDBColumnAnnotationHandler iDBColumnAnnotationHandler;
  • ISmartObjectHandler iSmartObjectHandler;
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);
}
3.2.3.1 在实体类上添加注解
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;
}
3.2.3.2 主方法中调用
3.2.3.2.1 获取注解的表名

如果想获取这个注解的值

@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 映射表名结束----");
3.2.3.2.2 获取XML 中的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>
mapper>

那么调用方法如下:

        //打印MyBatis3 XML实体类映射Map
        IDBColumnXMLHandler idbColumnXMLHandler= SmartMyBatis3App.getDBColumnXMLHandler();
        System.out.println("----打印MyBatis3 XML实体类映射Map开始----");
        idbColumnXMLHandler.printDocument(UserInfo.class);
        System.out.println("----打印MyBatis3 XML实体类映射Map结束----");
3.2.3.2.3 获取@Results({}) 注解返回实体类映射

如果想获取类似这样的内容

@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结束---");
3.2.3.2.4 获取实体类成员属性名称集合

如果想获取类似下面这样的成员属性名称列表

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 对象字段集合结束---
3.2.3.2.5 获取解映射表名的字段类型集合
        //打印 注解映射表名的字段类型集合
        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
----打印 注解映射表名的字段类型集合结束---
3.2.3.2.6 获取带包名的成员变量类型集合
 //打印 带包名的字段类型集合
        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
----打印 带包名的字段类型集合结束---
3.2.3.2.7 获取不带包名的成员变量类型集合
  //打印 不带包名的字段类型集合
        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
----打印 不带包名的字段类型集合结束---

0x04 关于SmartMybatis3项目实现思路

4.1 定义注解实体类

4.1.1 @DBTable注解实体类

要想实体类和数据库字段之间建立映射,那么我们首先需要针对表名做一个注解

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 {
}

4.1.2 @DBColumn

其次最重要的是实体类名称和数据库字段名称做一个映射

因此我们创建注解类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;
}

4.1.3 @DBConstraints 注解

我们还需要对字段的一些基本操作制定一些约束,比如是否可以为空,是否是主键之类。

注解类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;
}

4.2 编写注解处理器

上面的注解已经写好了,也学会使用了,但是我们怎么解析我们自定义的注解呢?

4.2.1 表名@DBTable注解的解析

还记得我们之前是这个注解的使用用法吧

@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 注解对象,然后就很简单了

4.2.2列名@DBColumn注解的解析

还记得刚才我们的注解使用时这样的

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

本篇完~


交流及分享,分享才能进步,如果喜欢我的博文,欢迎,点赞、评论或转发。

我是星云,我们下篇再见~

你可能感兴趣的:(#,MyBatis3,#,Java,反射和注解)