1. 注解与反射机制
前面经过反编译后,我们知道 Java 中的注解都继承了 Annotation
接口。也就是说,Java 使用 Annotation
接口代表注解元素,该接口是所有 Annotation
类型的父接口。同时为了运行时能准确获取到注解的相关信息,Java 在 java.lang.reflect
包下新增了 AnnotatedElement
接口,它主要用于表示目前正在 VM 中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,如反射包的 Constructor 类、Field 类、Method 类、Package 类和 Class 类都实现了 AnnotatedElement
接口。
下面是 AnnotatedElement
中相关的 API 方法,以上5个类都实现以下的方法::
返回值 | 方法名称 | 说明 |
getAnnotation(Class annotationClass) | 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null | |
Annotation[] | getAnnotations() | 返回此元素上存在的所有注解,包括从父类继承的 |
boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定类型的注解存在于此元素上,则返回 true,否则返回 false |
Annotation[] | getDeclaredAnnotations() | 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组 |
案例:
@DocumentA
class A{ }
@DocumentB
public class B extends A{
public static void main(String... args){
Class<?> clazz = B.class;
//根据指定注解类型获取该注解
DocumentA documentA = clazz.getAnnotation(DocumentA.class);
System.out.println("A:" + documentA);
//获取该元素上的所有注解,包含从父类继承
Annotation[] an= clazz.getAnnotations();
System.out.println("an:" + Arrays.toString(an));
//获取该元素上的所有注解,但不包含继承!
Annotation[] an2=clazz.getDeclaredAnnotations();
System.out.println("an2:" + Arrays.toString(an2));
//判断注解DocumentA是否在该元素上
boolean b=clazz.isAnnotationPresent(DocumentA.class);
System.out.println("b:" + b);
}
}
执行结果:
A:@com.zejian.annotationdemo.DocumentA()
an:[@com.zejian.annotationdemo.DocumentA(), @com.zejian.annotationdemo.DocumentB()]
an2:@com.zejian.annotationdemo.DocumentB()
b:true
2. 自定义注解
2.1 场景一:使用自定义注解来创建数据表
步骤:
- 根据自定义注解组装 SQL 语句
- 连接数据库,并执行 SQL 语句
自定义注解 @MyTable
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTable {
String name() default "test";
}
此注解用于类-----对应类
自定义注解 @MyConstraints
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyConstraints {
// 判断是否是主键
boolean isPrimaryKey() default false;
// 判断是否为空
boolean isNull() default false;
// 判断是否唯一
boolean isUnique() default false;
}
此注解用于类中的属性-----约束属性
自定义注解 @MyColumn
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyColumn {
// 字段名
String name() default "";
// 字段类型
String type() default "varchar";
// 字段长度
int length() default 0;
// 字段备注
String comment() default "";
// 约束
MyConstraints constraints() default @MyConstraints;
}
在实体类 User 中使用自定义注解
@MyTable(name = "TAB_USER")
public class User {
@MyColumn(name = "ID", type = "int", length = 11, comment = "主键", constraints = @MyConstraints(isPrimaryKey = true, isNull = false, isUnique = true))
private Integer id;
@MyColumn(name = "NAME", comment = "用户名", length = 30)
private String name;
@MyColumn(name = "AGE", type = "int", length = 3, constraints = @MyConstraints(isNull = true))
private Integer age;
// getter()/setter()
}
TableCreator#createTableSql()
方法:
创建一个注解解析器,来解析注解。
public class TableCreator {
/**
*
* 功能描述: 通过类名返回一个创建表的 SQL 语句
*
* CREATE TABLE [IF NOT EXISTS] tbleName (
* columnName type[(size)] [NOT NULL | NULL] [DEFAULT defaultValue] [UNIQUE [KEY]] [[PRIMARY] KEY] [COMMENT 'string']
* )
*
* @param className 类名
* @return java.lang.String
* @author zzc
* @date 2020/12/5 14:09
*/
public static String createTableSql(String className) {
Class<?> clazz = null;
try {
clazz = Class.forName(className);
// 通过某个类的 Class 对象获取某个注解
MyTable myTable = clazz.getAnnotation(MyTable.class);
if (null == myTable) {
System.out.println("No MyTable annotations in class " + className);
throw new RuntimeException("类:" + className + "上没有注解@MyTable");
}
Field[] fields = clazz.getDeclaredFields();
if (null == fields || fields.length == 0) {
throw new RuntimeException("类:" + className + "没有字段");
}
// 获取表名
String tableName = myTable.name();
// 如果表名为空,则使用类名
if (null != tableName) {
if (tableName.length() < 1) {
tableName = clazz.getName().toUpperCase();
}
}
// 获取字段列表定义
List<String> columnDefs = new ArrayList<>();
for (Field field : fields) {
// 解析字段
parseFieldOnMyColumnAnnotation(field, columnDefs);
}
// 如果类中的属性有对应的列,则将构建数据表语句
StringBuilder createCommand = null;
if (columnDefs.size() > 0) {
createCommand = new StringBuilder();
createCommand.append("CREATE TABLE").append(" ").append(tableName).append("(");
for (String columnDef : columnDefs) {
createCommand.append("\n ").append(columnDef).append(",");
}
}
// 确保最后是一个逗号“,”,并将最后一个多余的逗号“,”给去掉
if (null != createCommand) {
String tableSql = createCommand.toString();
int lastIndex = tableSql.length() - 1;
if (tableSql.charAt(lastIndex) == ',') {
tableSql = tableSql.substring(0, lastIndex) + "\n);";
return tableSql;
}
}
throw new RuntimeException("类 " + className + " 中的属性没有对应的列注解 " + MyColumn.class);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
}
TableCreator#parseFieldOnMyColumnAnnotation()
:解析字段
public static void parseFieldOnMyColumnAnnotation(Field field, List<String> columnDefs) {
StringBuilder sb = new StringBuilder();
// 获取字段上面的所有的注解
Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
if (fieldAnnotations.length == 0) {
return;
}
// 遍历字段上面的注解
for (int i = 0; i < fieldAnnotations.length; i++) {
// 字段上面只有 @MyColumn 注解
MyColumn myColumn = (MyColumn) fieldAnnotations[i];
// 1.字段名
String columnName = null;
if (myColumn instanceof MyColumn) {
columnName = myColumn.name();
if (null != columnName) {
if (columnName.length() == 0) {
columnName = field.getName().toUpperCase();
}
}
}
// 2.字段类型
String type = myColumn.type();
// 3.字段长度
int length = myColumn.length();
// 4.字段备注
String comment = myColumn.comment();
// 字段约束
MyConstraints constraints = myColumn.constraints();
// 生成字段列定义
sb.append(columnName).append(" ").append(type);
if (length > 0) {
sb.append("(").append(length).append(")");
}
if (null != constraints) {
sb.append(" ").append(getMyConstrains(constraints));
}
if (!comment.isEmpty()) {
sb.append(" ").append("COMMENT").append(" ").append("'").append(comment).append("'");
}
columnDefs.add(sb.toString());
}
}
TableCreator#getMyConstrains()
方法:
/**
*
* 功能描述: 将 约束类型为 MyConstraints 转换为 字符串String 类型
*
* @param myConstrains 约束
* @return java.lang.String
* @author zzc
* @date 2020/12/5 14:37
*/
private static String getMyConstrains(MyConstraints myConstrains) {
StringBuilder sb = new StringBuilder();
boolean isNull = myConstrains.isNull();
boolean isPrimaryKey = myConstrains.isPrimaryKey();
boolean isUnique = myConstrains.isUnique();
// 约束是否为空
if (isNull) {
if (isPrimaryKey) {
throw new RuntimeException("主键不能为空");
}
sb.append("NULL");
} else {
sb.append("NOT NULL");
}
// 约束为主键
if (isPrimaryKey) {
sb.append(" ").append("PRIMARY KEY");
}
// 约束为唯一键
if (isUnique) {
sb.append(" ").append("UNIQUE KEY");
}
return sb.toString();
}
DbUtil
:数据库连接工具类:
public class DbUtil {
public static final String JDBC_PROPERTY = "jdbc.properties";
public static Properties properties = new Properties();
public static String JDBC_DRIVER = null;
public static String JDBC_URL = null;
public static String JDBC_USERNAME = null;
public static String JDBC_PASSWORD = null;
static {
InputStream in = DbUtil.class.getClassLoader().getResourceAsStream(JDBC_PROPERTY);
try {
properties.load(in);
JDBC_DRIVER = properties.getProperty("jdbc.driver");
JDBC_URL = properties.getProperty("jdbc.url");
JDBC_USERNAME = properties.getProperty("jdbc.username");
JDBC_PASSWORD = properties.getProperty("jdbc.password");
Class.forName(JDBC_DRIVER);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection con= DriverManager.getConnection(JDBC_URL, JDBC_USERNAME, JDBC_PASSWORD);
return con;
}
public static void close(Connection con){
if(con != null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
System.out.println("关闭异常");
}
}
}
}
DbUtil#executeSql()
:执行 SQL
public static void executeSql(String sql){
Connection con = null;
Statement stmt = null;
ResultSet re = null;
try {
con = getConnection();
stmt = con.createStatement();
stmt.execute(sql);
}
catch (SQLException e) {
System.out.println("数据库访问异常");
throw new RuntimeException(e);
}
finally{
try {
if(re!=null){
re.close();
}
if(stmt!=null){
stmt.close();
}
} catch (SQLException e2) {
System.out.println("关闭异常");
}
close(con);
}
}
jdbc.properties
:JDBC 配置文件内容
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/zzc?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root
测试:
public class Test {
public static void main(String[] args) {
String className = "com.tinady.annotation.User";
String tableSql = TableCreator.createTableSql(className);
DbUtil.executeSql(tableSql);
}
}
以上便是利用注解结合反射来构建SQL语句的简单的处理器模型
2.2 场景二:使用 @MyTest 注解写一个测试框架
使用 @MyTest 注解写一个测试框架,用来测试程序员的方法是否有问题
先自定义一个注解 @MyTest
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyTest {
}
再自定义一些需要测试的方法:
public class TestAnnotation {
@MyTest
public void add() {
System.out.println("ADD~~~");
}
@MyTest
public void sub() {
System.out.println("SUB~~~");
}
@MyTest
public void mul() {
System.out.println("MUL~~~");
}
@MyTest
public void divide() {
int i = 3 / 0;
System.out.println("DIVIDE~~~");
}
}
通过反射进行调用,并生成测试报告:
public class TestTool {
public static void main(String[] args) {
TestAnnotation testAnnotation = new TestAnnotation();
Class<?> clazz = testAnnotation.getClass();
int errorNum = 0;
StringBuilder errorMsg = new StringBuilder();
// 只获取 public 修饰的方法
Method[] methods = clazz.getMethods();
if (null == methods || methods.length < 1) {
System.out.println("该类" + clazz.getName() + "没有需要测试的方法");
return;
}
for (Method method : methods) {
// 只测试带有 @MyTest 注解的方法
boolean isAnnotationMyTest = method.isAnnotationPresent(MyTest.class);
if (!isAnnotationMyTest) {
continue;
}
method.setAccessible(true);
try {
method.invoke(testAnnotation, null);
} catch (Exception e) {
errorNum++;
errorMsg.append(method.getName()).append(" ").append("has error:").append("\r\n caused by ")
.append(e.getCause().getClass().getSimpleName()).append("\r\n");
errorMsg.append(e.getCause().getMessage()).append("\r\n");
}
}
errorMsg.append(clazz.getName()).append(" ").append("has ").append(errorNum).append(" errors.");
// 生成测试报告
System.out.println(errorMsg.toString());
}
}