本文对注解和反射进行了系统的介绍,对ORM框架底层的实现写了个demo,该demo能够反映出注解和反射技术之间的联系
目录
一、注解
1、简介
- java注解(Annotation)又称java标注,是JDK5.0引入的一种注释机制
- 注解可以和注释联系起来,注释是给程序员看的,注解是给机器看的。注解相当于把代码注释到运行时使用,可以简化开发
- java语言中的类、方法、变量、参数和包都可以被标注
- java标注可以通过反射获取标注内容,在编译器生成类文件时,标注可以被嵌入到字节码中。java虚拟机可以保留标注内容,在运行时可以获取到标注内容
- 也支持自定义的java标注,即自定义编写注解
- 主要用途
- 编译格式的检查
- 反射中进行解析
- 生成帮助文档
- 跟踪代码依赖情况等
2、学习注解的重点
- 学好注解的关键是理解其语法和用法
- 学习步骤
- 注解的概念(即简介中的内容)
- 怎么使用内置注解
- 怎么自定义注解
- 反射中怎么获取注解(将在后面的反射中讲解)
3、内置注解
- 下面常用的内置注解会有举例
- @Override
- 用于标注重写的方法,在编译时会进行格式的检查
- 在方法定义时使用
- 定义在java.lang.Override
-
@Deprecated- 用于标记废弃的包、类或方法
- 废弃的方法还能用,在使用时会加一条删除线,不建议使用废弃方法
- 定义在java.lang.Deprecated
- 用于标记废弃的包、类或方法
-
@SafeVarargs- java7开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告
-
@FunctionInterface- java8开始支持,用于标识一个匿名函数或函数式接口
- 函数式接口指的是接口中只有一个抽象方法的接口
- java8开始支持,用于标识一个匿名函数或函数式接口
- 函数式接口定义多个抽象方法时会报错
- 定义一个抽象方法时不报错
-
@Repeatable- java8开始支持,标识某注解可以在同一个声明上使用多次
-
@SuppressWarnings- 抑制编译时的警告信息
- 定义在java.lang.SuppressWarnings
- 有三种使用方式
- @SuppressWarnings("unchecked")
- 抑制单类型的警告
- @SuppressWarnings("unchecked","rawtypes")
- 抑制多类型的警告
- @SuppressWarnings("all")
- 抑制所有类型的警告
- @SuppressWarnings("unchecked")
- 参数列表
4、元注解与自定义注解
4.1、元注解
- 指的是作用在其他注解上的注解
- 这里的其他注解,通常指的是我们自定义的注解
- 元注解的类型
- @Retention
- 标识这个注解怎么保存。是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
- @Documented
- 标记这些注解是否包含在用户文档(javadoc)中
- @Target
- 标记这个注解应该是哪种java成员
- 即标明该注解的作用范围,是作用在类上,还是作用于方法或其他位置
- 参数传入的是ElementType中的常量
- @Inherited
- 标记这个注解是自动继承的
- 子类会继承父类使用的注解中被@Inherited修饰的注解
- 接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有被@Inherited修饰
- 类实现接口时不会继承任何接口中定义的注解
- 标记这个注解是自动继承的
- @Retention
4.2、自定义注解
4.2.1 注解架构图
- Annotation与RententionPolicy与ElementType的关系
- 每一个Annotation对象,都会有唯一的RententionPolicy属性
- 每一个Annotation对象,都会有1到n个ElementType属性
- ElementType(注解的用途类型)
- 每一个Annotation对象,都会有1到n个ElementType属性
- 当Annotation与某个ElementType关联时,就意味着Annotation有了某种用途
- 例如,若一个Annotation对象是METHOD类型,则该Annotation只能用来修饰方法
- ElementType是枚举类,具体的常量值如下
package java.lang.annotation;
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* 方法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE /* 包声明 */
}
- RetentionPolicy(注解作用域策略)
- 每一个Annotation对象,都会有唯一的RententionPolicy属性
- a)、若Annotation的类型为SOURCE,则意味着Annotation仅存在于编译器处理期间,编译器处理完之后,该Annotation就没用了。例如@Override标志就是一个Annotation,当它修饰一个方法的时候,就意味着该方法覆盖父类的方法,并且在编译期间会进行语法检查。编译器处理完后,@Override就没有任何作用了
- b)、若Annotation的类型为CLASS,则意味着编译器将Annotation存储于类对应的.class文件中,它是Annotation的默认行为
- c)、若Annotation的类型为RUNTIME,则意味着编译器将Annotation存储于class文件中,并且可由JVM读入
- 总结来说就是,RetentionPolicy通常会定义为RUNTIME,能够确保JVM可以获取到注解
- 范围比较:RUNTIME>CLASS>SOURCE
- 每一个Annotation对象,都会有唯一的RententionPolicy属性
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */
CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为 */
RUNTIME /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}
4.2.2 定义格式
@interface 自定义注解名{
参数类型 方法名();
......
}
或
@interface 自定义注解名{
参数类型 方法名1()default 默认值;
......
}
4.2.3 注意事项
- 定义的注解,自动继承了java.lang.annotation.Annotation接口
- 注解中的每一个方法,实际是声明的注解配置参数
- 方法的名称就是配置参数的名称
- 方法的返回值类型,就是配置参数的类型,只能是基本数据类型/Class/String/enum/数组
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
4.2.4 自定义注解举例
- 在idea中创建注解
- 代码
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {
String value() default "null";
}
上面定义了一个注解MyAnnotation,下面是对部分代码的解析
- @interface
- 使用@interface定义注解时,意味着它实现了java.lang.annotation.Annotation接口,即该注解是一个Annotation
- 定义Annotation必须使用@interface
- 注意:它和我们通常的implemented实现接口的方法不同。Annotation接口的实现细节都由编译器完成。通过@interface定义注解后,该注解不能继承其他的注解或接口
- @Documented
- 类和方法的Annotation在缺省的情况下是不会出现在javadoc中的。如果使用@Documented修饰该Annotation,则表示它可以出现在javadoc中
- 定义Annotation时,@Documented可有可无;如果没定义,则该Annotation不会出现在javadoc中
- @Target(ElementType.TYPE)
- 用于指定Annotation的类型属性
- 这里指定了Annotation的类型是ElementType.TYPE,意味着该注解是用来修饰类、接口(包括注释类型)或枚举声明
- 定义Annotation时,@Target可有可无
- 如果有,则该注解只能用于指定的地方
- 如果没有,则该注解可以用于任何地方
- @Retention(RetentionPolicy.RUNTIME)
- 用于指定Annotation的策略属性
- 这里指的是该Annotation册策略是RetentionPolicy.RUNTIME。编译器会将该Annotation信息保留在.class文件中,并且能被JVM读取
- 定义Annotation时,@Retention可有可无
- 如果没有,默认是RetentionPolicy.CLASS
二、反射
1、反射概述
- 定义
- java反射机制是在运行状态中,获取任意一个类的结构,通过该类的结构可以创建对象、得到方法或属性、执行方法。这种在运行状态动态获取信息以及动态调用对象方法的功能被称为java语言的反射机制
- 个人解析
- 反射是java的高级特性之一
- 通过反射可以获取任意一个类的结构,这个类可以是不存在的,可以在未来定义。比如在spring等框架就大量使用了反射,其中IOC容器可以管理对象,管理的这些对象是我们以后在项目中定义的,也就是说反射可以看成是面向未来编程
- 反射具有动态性,是java动态编程的特点,即能够在程序运行起来之后,再动态加载原本没定义的类或新的代码
- java的动态机制在移动端用的比较多
- 反射是一种反封装机制,体现在“任意”二字上。我们可以通过反射技术操作一个私有的属性或方法,甚至调用私有化的构造器创建一个对象
- 正常情况下是先有类后再有对象,反射与此不同,它是先运行代码,再加载类;或者说是外部传入一个对象,可以通过反射反推出这个对象的类结构
2、类加载器
- 类加载器(Classloader)是java运行时环境(JRE,即Java Runtime Environment)的一部分,负责动态加载java类到java虚拟机的内存空间中
- java默认有三种类加载器
类加载器名称 | 描述 |
BootstrapClassLoader(引导启动类加载器) |
嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库,引 导启动类加载器无法被应用程序直接使用 |
ExtensionClassLoader(扩展类加载器) |
是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类 库。它的父加载器是BootstrapClassLoader |
App ClassLoader(应用类加载器) |
App ClassLoader是应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文 件。它的父加载器为ExtensionClassLoader |
- 程序员自己操作加载配置文件、类等使用的是应用类加载器
面试题:多个类加载器如何避免类加载重复?
- 类通常是按需加载,即第一次使用该类时才加载。学习类加载器时,需要掌握java的委派概念
- 多个类加载器避免类加载重复的方法是:使用双亲委派模型
- 如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一层次的类加载器都是如此。因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试去加载。委派的好处就是避免有些类被重复加载
- 人话解析双亲委派模型
- 三个类加载器的关系如下图所示,最底层是我们使用的应用类加载器。从上往下,我们简称a(引导启动类加载器)、b(扩展类加载器)、c(应用类加载器)
- 当我们加载一个类时,使用的是c。c会委派b去加载,b不是最顶层,所以又会委派a去加载。如果a加载了,b和c就不用加载了,可以共用a加载后的类结构。如果a不想加载该类,就会返回给b,b如果加载了也是大家共用。如果b也不想加载,那就让委派者自己加载,也就是c自己加载。不论谁加载了,类结构都是共用的。这种委派机制能够避免重复加载同一个类。
2.1 使用类加载器加载配置文件
- 通过类加载器加载资源文件,默认加载的是项目src路径下的文件
- 读取步骤
- 通过类名.class获取类的类对象
- 通过类对象的getClassLoader()方法获取应用类加载器
- 通过类加载器的getResourceAsStream()方法获取src包下的输入流,入参要读取的文件名
- 通过缓冲流封装上面得到的输入流,读取文件内容
- 读取步骤
- 项目文件截图如下,src包下有一个book.txt文件,里面写了一句诗
- 代码如下
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class Demo1 {
public static void main(String[] args) throws IOException {
//获取类加载器
ClassLoader c = Demo1.class.getClassLoader();
//通过类加载器获取输入流
InputStream is = c.getResourceAsStream("book.txt");
//使用缓冲流读取文件内容
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
//读取内容
System.out.println(reader.readLine()); //床前明月光,疑是地上霜
}
}
- 如果给项目添加resource root目录(资源根目录),则getResourceAsStream()方法读取的不再是src下的文件,而是资源根目录下的文件
- 配置资源根目录的步骤
- 在模块目录下创建一个文件夹,命名为source
- 此时我们在source目录下同样创建一个book.txt,里面的内容为Hello World!
- 再次运行上面的代码,输出结果是Hello World!
3、Class对象及获取Class对象的方式
Class对象简介
- 想要了解一个类,必须先获取到该类的字节码文件对象(即类名.class)。在java中,每一个字节码文件被加载到内存后,都存在一个对应的Class类型的对象中,这就是Class对象,也称类对象
- Class可以理解为是一种表示类的数据类型,即Class是类 类型
获取Class对象的三种方式
1、在编写代码时,知道类的名称,且类已经存在,则可以通过
包名.类名.class得到一个类的类对象;
2、如果拥有类的对象,可以通过
对象.getClass()得到一个类的类对象;
3、如果在编写代码时,知道类的名称,但是类不存在,则可以通过
Class.forName(包名+类名)得到一个类的类对象
以上三种方式在调用时,如果类在内存中不存在,则会加载到内存;
如果类已经在内存中存在,则不会重复加载该类,而是重复利用。
(一个class文件在内存中不会存在两个类对象)
以上三种方式获取同一个类的类对象时,获取的是同一个类对象。
即使用==判断的结果是true
- 特殊的类对象
- 基本数据类型的类对象:
- 基本数据类型.class
- 包装类.type
- 基本数据类型包装类对象:
- 包装类.class
- 基本数据类型的类对象:
- 在获取Class对象时,Class可以指定泛型,也可以不指定;不指定的话后面需要强制类型转换
举例
- 目录结构如下
- 代码如下
import com.java.Person;
public class Demo2 {
public static void main(String[] args) throws ClassNotFoundException {
//这里的Person放在src下,没有
//第一种方式:通过包名.类名.class加载类
Class<Person> c1 = Person.class;
System.out.println(c1); //class com.java.Person
//第二种方式:通过类的对象获取类的信息
Person p = new Person();
Class<Person> c2 = (Class<Person>) p.getClass();
System.out.println(c2); //class com.java.Person
//第三种方式:使用Class.forName()
//该类不存在时抛出异常
Class<Person> c3 = (Class<Person>) Class.forName("com.java.Person");
System.out.println(c3); //class com.java.Person
System.out.println(c1==c2 && c2==c3); //true
}
}
4、通过反射获取Constructor(构造器)
4.1 通过class对象获取一个类的构造方法
1. 通过指定的参数类型, 获取指定的单个构造方法 (public修饰的)
getConstructor(参数类型的class对象数组)
例如:
构造方法如下: Person(String name,int age)
得到这个构造方法的代码如下:
Constructor c = p.getClass().getConstructor(String.class,int.class);或者:Constructor c = p.getClass().getConstructor(new Class[]{String.class,int.class});
2. 获取构造方法数组 (public修饰的)
getConstructors();3. 获取所有权限的单个构造方法 (除继承以外所有的:包含私有, 共有, 保护, 默认)
getDeclaredConstructor(参数类型的class对象数组)4. 获取所有权限的构造方法数组 (除继承以外所有的:包含私有, 共有, 保护, 默认)
getDeclaredConstructors();
4.2 Constructor创建对象
常用方法:
1、 newInstance(Object... para)调用这个构造方法,把对应的对象创建出来
参数:是一个Object类型的可变参数,传递的参数顺序,必须匹配构造方法中形式参数列表的顺序
2、 setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法)
4.3 举例
- 定义一个Person类
- 含全参构造器、无参构造器、私有化的单属性构造器
- get、set方法
- toString方法
package com.java;
import java.io.Serializable;
import java.util.Objects;
public class Person implements Serializable {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
private Person(String name) {
this.name = name;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
- 代码
package com;
import com.java.Person;
import java.lang.reflect.Constructor;
public class demo3 {
public static void main(String[] args) throws Exception {
//获取Class对象
Class<Person> c = (Class<Person>) Class.forName("com.java.Person");
//获取无参构造器
Constructor<Person> constructor = c.getConstructor();
//通过无参构造器创建对象
Person p1 = constructor.newInstance();
System.out.println(p1); //Person{name='null', age=0}
//获取全参构造器
Constructor<Person> constructor1 = c.getConstructor(String.class, int.class);
//通过全参构造器创建对象
Person p2 = constructor1.newInstance("张三", 18);
System.out.println(p2); //Person{name='张三', age=18}
//获取私有构造器,必须通过getDeclaredConstructor方法获取
Constructor<Person> constructor2 = c.getDeclaredConstructor(String.class);
//设置权限为true,否则无法创建对象
constructor2.setAccessible(true);
Person p3 = constructor2.newInstance("李四");
System.out.println(p3); //Person{name='李四', age=0}
}
}
5、通过反射获取Method(方法)
5.1 通过class对象获取一个类的方法
1. getMethod(String methodName , class.. clss)
根据参数列表的类型和方法名, 得到一个方法(public修饰的)
2. getMethods();
得到一个类的所有方法 (public修饰的)
3. getDeclaredMethod(String methodName , class.. clss)
根据参数列表的类型和方法名, 得到一个方法(除继承以外所有的:包含私有, 共有, 保护, 默认)
4. getDeclaredMethods();
得到一个类的所有方法 (除继承以外所有的:包含私有, 共有, 保护, 默认)
5.2 Method执行方法
invoke(Object o,Object... para) :
调用方法 ,
参数1. 要调用方法的对象
参数2. 要传递的参数列表
getName()
获取方法的方法名称
setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的方法)
5.3 举例
-
在上面Person类的基础上,加一个私有方法setName2
package com.java;
import java.io.Serializable;
import java.util.Objects;
public class Person implements Serializable {
private String name;
private int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
private Person(String name) {
this.name = name;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//添加私有方法
private void setName2(String name){
this.name = name;
this.age = 666;
}
}
- 代码
import com.java.Person;
import java.lang.reflect.Method;
public class Demo3 {
public static void main(String[] args) throws Exception {
//获取类对象
Class<Person> c = (Class<Person>) Class.forName("com.java.Person");
//获取类的构造器并创建一个对象
Person p1 = c.getConstructor().newInstance();
//获取c的setName()方法
Method setName = c.getMethod("setName", String.class);
//p1对象调用setName方法
setName.invoke(p1,"张三");
System.out.println(p1); //Person{name='张三', age=0}
//获取c的私有方法setName2()
Method setName2 = c.getDeclaredMethod("setName2", String.class);
//设置权限
setName2.setAccessible(true);
//p1调用setName2方法
setName2.invoke(p1,"李四");
System.out.println(p1); //Person{name='李四', age=666}
}
}
6、通过反射获取Field(属性)
6.1 通过class对象获取一个类的属性
1. getDeclaredField(String filedName)
根据属性的名称, 获取一个属性对象 (所有属性)
2. getDeclaredFields()
获取所有属性
3. getField(String filedName)
根据属性的名称, 获取一个属性对象 (public属性)
4. getFields()
获取所有属性 (public)
6.2 Field获取属性
常用方法:
1. get(Object o);
参数:要获取属性的对象
获取指定对象的此属性值
获取指定对象的此属性值
2. set(Object o , Object value);参数1. 要设置属性值的 对象
参数2. 要设置的值
设置指定对象的属性的值
3. getName()
获取属性的名称
4. setAccessible(boolean flag)
如果flag为true 则表示忽略访问权限检查 !(可以访问任何权限的属性)
6.3 举例
- 在前面Person的基础上加一个公有的属性hobby
package com.java;
import java.io.Serializable;
import java.util.Objects;
public class Person implements Serializable {
private String name;
private int age;
public String hobby;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", hobby='" + hobby + '\'' +
'}';
}
private Person(String name) {
this.name = name;
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//添加私有方法
private void setName2(String name){
this.name = name;
this.age = 666;
}
}
- 代码
import com.java.Person;
import java.lang.reflect.Field;
public class Demo4 {
public static void main(String[] args) throws Exception {
//获取类对象
Class<Person> c = (Class<Person>) Class.forName("com.java.Person");
//通过构造器创建一个对象
Person p = c.getConstructor().newInstance();
//获取类的公有属性
Field hobby = c.getField("hobby");
//设置p对象的hobby属性
hobby.set(p,"敲代码");
System.out.println(p); //Person{name='null', age=0, hobby='敲代码'}
//获取设置的hobby
System.out.println(hobby.get(p)); //敲代码
//获取私有属性
Field name = c.getDeclaredField("name");
//设置权限为true
name.setAccessible(true);
//设置p的name属性
name.set(p,"小明");
System.out.println(p); //Person{name='小明', age=0, hobby='敲代码'}
}
}
三、反射与注解的联系
3.1 通过反射获取注解的信息
- 获取类/属性/方法的全部注解对象
Annotation[] annotations01 = Class/Field/Method.getAnnotations();
for (Annotation annotation : annotations01) {
System.out.println(annotation);
}
- 根据类型获取类/属性/方法的注解对象
注解类型 对象名 = (注解类型) c.getAnnotation(注解类型.class);
3.2 反射+注解实现ORM框架底层demo
3.2.1 ORM框架介绍
- 下面图片的内容摘自百度百科
- 人话
- ORM框架就是对象关系映射,指的是把一个java的对象通过此模型映射到数据库表中,即根据对象的类名、字段名等,自动在数据库表中生成一个与之对应的数据库表,并填上相应字段的值,能够帮助程序员免去编写创建数据库表等基本的操作
3.2.2 利用注解+反射实现ORM的demo
思路:
1、定义一个注解TableAnnotation,用于修饰一个类,关联类名和数据库表名;
2、定义一个注解ColumnAnnotation,用于修饰类中的属性,关联属性名和数据库字段名;
3、定义一个类,使用@TableAnnotation修饰类,并指定对应数据库表的表名;使用@ColumnAnnotation修饰类的属性,并指定对应数据库字段的名及相应的值;
4、通过反射获取类的Class对象,通过Class对象获取修饰类的注解,进而获取注解中的内容,该内容表示的是类对应的数据库表名,可以通过该值去使用sql语句创建数据库表;
5、通过反射获取类的Class对象,再通过Class对象获取类的所有属性Field。使用Field获取对应的注解,并获取注解中的内容。根据注解中的内容去创建数据库的字段并赋值。
代码:
- 定义@TableAnnotation注解
import java.lang.annotation.*;
//用于将类名映射到数据库表名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TableAnnotation {
/**
* 描述数据库表的名称
* @return
*/
String value();
}
- 定义@ColumnAnnotation注解
import java.lang.annotation.*;
//用于标记类的属性与数据库表字段的映射
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ColumnAnnotation {
/**
* 描述字段的名称
* @return
*/
String columnName();
/**
* 描述字段的数据类型
* @return
*/
String type();
/**
* 描述字段的长度
* @return
*/
int length();
}
- 定义一个Book类,使用自定义的两个注解修饰
import java.io.Serializable;
import java.util.Objects;
@TableAnnotation(value = "book_table")
public class Book implements Serializable {
@ColumnAnnotation(columnName = "bookId",type = "int",length = 10)
private Integer bookId;
@ColumnAnnotation(columnName = "name",type = "varchar",length = 20)
private String name;
@ColumnAnnotation(columnName = "info",type = "varchar",length = 1000)
private String info;
@Override
public String toString() {
return "Book{" +
"bookId=" + bookId +
", name='" + name + '\'' +
", info='" + info + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Objects.equals(bookId, book.bookId) &&
Objects.equals(name, book.name) &&
Objects.equals(info, book.info);
}
@Override
public int hashCode() {
return Objects.hash(bookId, name, info);
}
public Book(Integer bookId, String name, String info) {
this.bookId = bookId;
this.name = name;
this.info = info;
}
public Book() {
}
public Integer getBookId() {
return bookId;
}
public void setBookId(Integer bookId) {
this.bookId = bookId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
}
- 定义一个demo类,获取类和属性的注解内容。后续可以根据这些获取的内容去创建数据库表
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
public class demo {
public static void main(String[] args) {
//获取Book类的Class对象
Class<Book> b = Book.class;
//获取修饰Book类的所有注解(这里只有一个TableAnnotation)
Annotation[] as = b.getAnnotations();
for (Annotation a : as) {
System.out.println(a); //@TableAnnotation(value="book_table")
}
//获取@TableAnnotation注解(获取到的是注解接口)
TableAnnotation annotation = b.getAnnotation(TableAnnotation.class);
String value = annotation.value();
//book_table,根据此注解的内容去数据库创建表名
System.out.println(value);
//获取所有属性
Field[] fs = b.getDeclaredFields();
for (Field f : fs) {
//获取修饰属性的注解
ColumnAnnotation ca = f.getAnnotation(ColumnAnnotation.class);
//获取注解中的内容,后面可以根据这些内容创建数据库表
String columnName = ca.columnName();
String type = ca.type();
int length = ca.length();
System.out.println("Book的"+f+"属性,对应数据库表中的"+columnName+"字段,类型为:"+type+",长度为:"+length);
}
}
}
- 测试结果
四、内省机制
4.1 Javabean与内省机制简介
Javabean
- 一个定义在包中的类,至少满足以下条件,且没有相关的业务逻辑处理的类,称为bean类
- 拥有无参构造器
- 所有属性私有化
- 所有属性提供set/get方法
- 实现了序列化接口
- 以后对bean类的定义一定要规范,否则在框架的内省机制中可能会出问题,比如某些属性不能赋值......
内省机制
- java提供了一套java.beans包的api,对于反射的操作进行了封装,可以更快地通过反射技术获取Javabean的相关信息
- 内省机制在后续的开发用的并不多,但是在自己定义框架的时候用的很多
- 内省机制的流程
4.2 内省机制的使用
- 内省类Introspector
获取Bean类信息
方法:
getBeanInfo(Class cls)
返回值类型:
BeanInfo
通过传入的类信息,得到这个Bean类的封装对象
- BeanInfo
常用的方法:
MethodDescriptor[] getPropertyDescriptors():
获取bean类的 get/set方法 数组
- MethodDescriptor
常用方法:
1. Method getReadMethod();
获取一个get方法
2. Method getWriteMethod();
获取一个set方法
有可能返回null 注意加判断
- 举例
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
public class demo1 {
public static void main(String[] args) throws IntrospectionException {
//获取Class对象
Class<Book> b = Book.class;
//通过内省类的getBeanInfo方法获取bean类信息
BeanInfo beanInfo = Introspector.getBeanInfo(b);
//获取bean类的get/set方法数组
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
//获取get方法
Method get = pd.getReadMethod();
//获取set方法
Method set = pd.getWriteMethod();
System.out.println(get);
System.out.println(set);
//获取属性名
System.out.println(pd.getName());
//获取属性的数据类型
System.out.println(pd.getPropertyType());
System.out.println("-----------------");
}
}
}
- 运行效果