程序员内功修炼——内省、反射与注解

2021-08-16 17:01:11 浏览数 (1)

目录

  • 一、软件开发概述
    • 2.1、软件开发的生命周期
    • 2.2、软件设计原则
    • 2.3、编码规范
  • 二、软件测试
    • 2.1、黑盒测试
    • 2.2、白盒测试
    • 2.3、JUnit测试
      • 2.3.1、JUnit依赖安装
      • 2.3.2、JUnit常用注解
        • 2.3.2.1、@Before
        • 2.3.2.2、@After
  • 三、配置文件
    • 3.1、properties文件
      • 3.1.1、解析properties文件
      • 3.1.2、常见的API
    • 3.2、XML文件
      • 3.2.1、为什么要学习XML
      • 3.2.2、XML的语法
  • 四、反射
    • 4.1、什么是反射
    • 4.2、字节码对象
    • 4.3、获取字节码对象
    • 4.4、获取构造器
      • 4.4.1、获取所有构造器
      • 4.4.2、获取指定的构造器
      • 4.4.3、获取构造器练习
      • 4.4.4、调用构造器创建对象
    • 4.5、获取方法
      • 4.5.1、获取所有方法
      • 4.5.2、获取指定的方法
      • 4.5.3、获取方法的练习
    • 4.6、调用方法
    • 4.7、获取字段
      • 4.7.1、获取单个字段
      • 4.7.2、获取所有字段
    • 4.8、操作字段
  • 五、内省
    • 5.1、JavaBean
      • 5.1.1、JavaBean 的规范要求
      • 5.1.2、三大成员
      • 5.1.3、什么是属性
    • 5.2、内省的概述
    • 5.3、内省的作用
    • 5.4、内省常用的API
    • 5.5、JavaBean 和 Map 之间的转化
      • 5.5.1、JavaBean转map
      • 5.5.2、map转JavaBean
  • 六、注解
    • 6.1、注解介绍
    • 6.2、定义格式
    • 6.3、内置注解
    • 6.4、元注解
      • 6.4.1、**@Target**
      • 6.4.2、**@Retention**
      • 6.4.3、@Documented
      • 6.4.4、@Inherited
    • 6.4、元注解
      • 6.4.1、**@Target**
      • 6.4.2、**@Retention**
      • 6.4.3、@Documented
      • 6.4.4、@Inherited

一、软件开发概述

2.1、软件开发的生命周期

​ 生命周期: 从立项到软件停用的过程

  1. 问题的定义及规划: 此阶段是软件开发方与需求方共同讨论,主要确定软件的开发目标及其可行性
  2. 需求分析: 在确定软件开发可行的情况下,对软件需要实现的各功能进行详细分析。需求分析阶段是一个很重要的阶段,这一阶段做得好,将为整个软件开发项目的成功打下良好的基础。
  3. 软件设计: 此阶段主要根据需求分析的结果,把整个软件系统划分为大大小小的多个模块,设计出每一个模块的具体结构。如系统框架设计,数据库设计等。软件设计一般分为总体设计和详细设计。
  4. 程序编码: 此阶段是将软件设计的结果转换成计算机可运行的程序代码。在程序编码中必须要制定统一,符合标准的编写规范。以保证程序的可读性,易维护性,提高程序的运行效率。
  5. 软件测试: 在软件设计完成后要经过严密的测试,以发现软件在整个设计过程中存在的问题并加以纠正。整个测试过程分单元测试(白盒)、集成测试(黑盒,功能测试、强度性能测试)以及系统测试三个阶段进行。测试的方法主要有白盒测试和黑盒测试两种。在测试过程中需要建立详细的测试计划并严格按照测试计划进行测试,以减少测试的随意性。
  6. 运行维护: 安装部署软件系统,修复软件中存在的bug和升级系统。在软件开发完成并投入使后,由于多方面的原因,软件不能继续适应用户的要求。要延续软件的使用寿命,就必须对软件进行维护。软件的维护包括纠错性维护和改进性维护两个方面

2.2、软件设计原则

​ 为了提高软件的开发效率,降低软件开发成本,一个优良的软件系统应该具有以下特点:

  1. 可重用性:遵循 DRY(Don’t Repeat Yourself Principle) 原则,减少软件中的重复代码。
  2. 可拓展性:当软件需要升级增加新的功能,能够在现有的系统架构上方便地创建新的模块,而不需要改变软件现有的结构,也不会影响已经存在的模块。
  3. 可维护性:当用户需求发生变化时,只需要修改局部的模块中的少量代码即可.
  4. 高内聚性:内聚,强调一个系模块内的功能联系,每个模块只完成特定的功能,不同模块之间不会有功能的重叠,高内聚性可以提高软件的可重用性和可维护性。
  5. 低耦合性:耦合,强调的是多个模块之间的关系,模块之间相互独立,修改某一个模块,不会影响到其他的模块。低耦合性提高了软件的可维护性。

2.3、编码规范

​ 基本命名规范:使用有意义的英文单词,多个单词用驼峰表示法

包名

​ 全小写 , 域名倒写.模块名.组件名 ,例如:com.util

接口名

​ 首字母大写,形容词,副词。习惯性的以 I 开头。此时的I表示 interface,见名知意.(不强制,要结合其他规范综合考虑),例如:IUserServiceIEmployeeService

接口实现类

​ 习惯性使用 Impl 结尾.(不强制,要结合其他规范综合考虑),例如:UserServiceImplEmployeeServiceImpl

类名

​ 首字母大写,名词。遵循驼峰表示法。例如:User,Employ

方法名

​ 首字母小写。力求语义清晰,使用多个单词。遵循驼峰表示法。例如:getUserInfoByName()

变量名

​ 首字母小写。遵循驼峰表示法。例如:userInfo

常量名

​ 全大写,力求语义清晰,使用多个单词。使用下划线分割。例如:MAX_STOCK_COUNT.

二、软件测试

​ 软件测试经常分为两类:黑盒测试和白盒测试。

2.1、黑盒测试

​ 黑盒测试也称功能测试,是通过测试来检测每个功能是否能正常使用,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序的接口上进行测试,检查程序功能是否 按照需求规格说明书 的规定正常使用。

简单来说就是:不需要写代码,给输入值,看程序是否能够输出期望的值。

​ 他主要发现以下几个错误:

  1. 功能是否不正确或遗漏
  2. 界面是否有错误
  3. 输入和输出错误
  4. 数据库访问错误
  5. 性能是否有问题
  6. 初始化和终止错误等

2.2、白盒测试

​ 由开发人员来测试. 又称结构测试、透明盒测试、逻辑驱动测试或基于代码的测试。它是按照程序内部的结构测试程序,通过测试来检测产品内部动作是否按照设计规格说明书的规定正常执行。测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据。

简单来说就是:需要写代码的。关注程序具体的执行流程。

2.3、JUnit测试

​ JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework)。JUnit 测试是程序员测试,即白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。

2.3.1、JUnit依赖安装

​ 由于 JUnit 4 回归测试框架是三方提供的,不是 JDK 自带的,所有要使用需导入人家的 jar 包以及安装对应的插件,这里以maven为例,导入maven坐标:

代码语言:javascript复制
    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

2.3.2、JUnit常用注解

​ JUnit配上一些常用注解可以解决代码重复的问题

2.3.2.1、@Before

​ 修饰的方法会在测试方法之前被自动执行

2.3.2.2、@After

​ 修饰的方法会在测试方法执行之后自动被执行,如果有异常的话,@After后面的代码也会执行

三、配置文件

​ 按理说只要能保存一些配置信息,供程序动态读取数据就OK,但是为了提高效率,在 IT 行业中,习惯使用两种具有特殊特点的文件来作为配置文件

  1. properties 文件
  2. XML文件

3.1、properties文件

​ 该文件称属性文件 / 资源文件 / 配置文件, 以 properties 作为文件后缀名,存取特点是KV键值对的格式:key=value,多对数据使用换行分开

注意事项:

  1. 在配置文件中,所有的数据都是字符串,不需要使用引号。
  2. 在配置文件中不需要使用空格

3.1.1、解析properties文件

​ 如果要读取 properties 中的数据,我们使用 IO 操作,一行一行的读取,再通过=来切分字符串也可以完成.但是还是比较麻烦的,如果有注释更麻烦,此时我们可以意识到这么复杂的步骤,SUN公司肯定帮我们写好了工具方法,这就是是 Properties

Properties 是 Map 的实现类.可以继承过来map的常见的操作方法(get,put....),map中的方法,我们一般都不用.因为 properties 文件比较特殊,我们一般使用 Properties 类的新增的方法。

3.1.2、常见的API

  1. public void load(InputStream inStream); 通过输入流加载配置文件中的内容。
  2. public String getProperty(String key);// 通过属性名获取属性值
代码语言:javascript复制
package com.test;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * @author Xiao_Lin
 * @date 2020/12/28 11:55
 */
public class ProTest {

  public static void main(String[] args) throws IOException {
    //读取配置文件中的数据
    Properties properties = new Properties();
    //为了获取ClassLoder对象,跟Thread无关
    ClassLoader loader = Thread.currentThread().getContextClassLoader();
    //通过类加载器去读取配置文件发,返回一个输入流
    InputStream stream = loader.getResourceAsStream("user.properties");
    //加载配置文件
    properties.load(stream);
    System.out.println(properties.getProperty("user"));
    System.out.println(properties.getProperty("password"));
  }
}

3.2、XML文件

​ XML(Extensible Markup Language),是一种可扩展的标记语言.(使用◇>括起来)XML技术是W3C组织(World Wide Web Consortium万维网联盟)发布的,目前遵循的是W3C组织于1998年发布的。

​ XML1.0规范.它的设计宗旨是传输数据,而不是显示数据(HTML).它的标签没有被预定义需要自行定义标签.它是W3C的推荐标准。

3.2.1、为什么要学习XML

  1. XML是一种通用的数据交换格式。
  2. 许多系统的配置文件都是使用XML。
  3. JavaEE框架基本都有在使用XML

3.2.2、XML的语法

  1. XML文档需要在文档第一行声明,声明表示

四、反射

4.1、什么是反射

​ 在程序的运行过程中,通过字节码文件动态的获取类中的成员信息(构造器、方法、字段),这种就叫做反射。目的是为了通过程序自动获取构造器来创建对象、获取方法并调用。

4.2、字节码对象

​ Java代码会经历三个阶段:

​ 我们可以通过多个实物,发现他们的共性,来抽象成一个类,类就是对象的模板,而一个个的实体就是对象

​ 字节码也是真实存在的文件,每一个字节码都是一个实例,而JVM要来存放这些字节码就需要抽象成模板,再通过模板来创建对象,存放每份字节码的信息。当要使用某份字节码时(比方说创建Person对象),就从JVM中调出存了Person.class内容的Class对象,然后再去实例化Person对象。3.

​ JDK中定义好了Class类:java.lang.Class,该类中有大量gte开头的方法,表示可以使用字节码对象来获取信息,所以我们当我们拿到了字节码对象就可以直接操作当前字节码中的构造器、方法、字段等等。

4.3、获取字节码对象

​ 通过API,我们可以得知Class没有公共的构造器,原因是因为Class对象在加载类时由Java虚拟机自动构建。

方式一

​ 通过Class类的forName()方法来获取字节码对象**(常用)**,多用于配置文件,将类名定义在配置文件中。读取文件,加载类以及各种流行框架中。

代码语言:javascript复制
Class.forName(String className);  //通过类的全限定名来获取字节码对象,全限定类名是包名 类型
Class.forName("java.lang.String");  //JVM中存在则返回,不存在就抛出异常 

方拾二

​ 通过对象的getClass()方法来获取字节码对象,多用于对象的获取字节码的方式。

代码语言:javascript复制
new User().getClass();  //通过父类Object中的getClass方法

方式三

​ 通过类型(基本类型)的class字段来获取字节码对象,多用于参数的传递。

代码语言:javascript复制
int.class;

总结

  1. 以上的三种方式第一种是使用最多的,在各种框架中都有使用。
  2. 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
代码语言:javascript复制
@Test
public  void testGetClass() throws Exception {
    // 1 通过类的全限定名 Class.forName();
    Class clz1 = Class.forName("cn.linstudy.domain.Person");
    // 2 通过对象的getClass() 方法
    Person p = new Person();
    Class clz2 = p.getClass();
    // 3 通过class 字段去获取
    Class clz3 = Person.class;
    // 字节码只会加载一次,所有不管用的哪种方式去获取字节码,都是同一个
    System.out.println(clz1 == clz2);   //true
    System.out.println(clz2 == clz3);   //true
    System.out.println(clz1 == clz3);   //true
        // int 类型和int数据类型不是同一个
    System.out.println(int.class);
    System.out.println(int[].class);
}

4.4、获取构造器

​ 使用反射的目的无外乎是使用程序动态操作类的成员,比如说方法,而且操作方法首先得有对象,而对象是通过构造器来创建的,所以必须先获取构造器。

4.4.1、获取所有构造器

public Constructor[] getConstructors();:获取所有public修饰的构造器

public Constructor[] getDeclaredConstructors();:获取所有的构造器(包括非public)

4.4.2、获取指定的构造器

public Constructor getConstructor(Class... parameterTypes);

public Constructor getDeclaredConstructor(Class...parameterTypes)

​ parameterTypes : 参数的类型(构造方法的参数列表的类型).

结论

​ 带着 s 表示获取多个.带着 Declared 表示忽略权限,包括私有的也可以获取到。

4.4.3、获取构造器练习

代码语言:javascript复制
@Test
public void testGetConstructor() throws NoSuchMethodException {
    // 获取字节码对象
    Class clz = Person.class;
    // 1 获取所有 public 构造器
    Constructor[] cons1 = clz.getConstructors();
    for(Constructor con : cons1){
        System.out.println(con);
    }
    System.out.println("--------");
    // 2 获取所有构造器,包括 private
    Constructor[] cons2 = clz.getDeclaredConstructors();
    for(Constructor con : cons2){
        System.out.println(con);
    }
     // 3 获取无参构造器
    Constructor con1 = clz.getConstructor();
    System.out.println(con1);
    // 4 获取带参构造器
    Constructor con2 = clz.getConstructor(Long.class, String.class);
    System.out.println(con2);
    // 5 获取指定 private 构造器
    Constructor con3 = clz.getDeclaredConstructor(String.class);
    System.out.println(con3);
}

常见错误

​ 参数不匹配,报错.找不到指定的构造器

4.4.4、调用构造器创建对象

代码语言:javascript复制
public Object newInstance(Object... initargs);// initargs: 调用该构造器传递的实际参数.参数列表一定要匹配(类型,个数,顺序).
代码语言:javascript复制
@Test
public void testCreateObject() throws Exception {
    // 获取字节码对象
    Class clz = Class.forName("cn.linstudy.domain.Person");
    // 获取带参数构造器,参数为参数类型
    Constructor con1 = clz.getConstructor(Long.class, String.class);
    //调用构造器
    Object obj = con1.newInstance(1L, "小狼");
    System.out.println(obj);
    // 获取带有参数的 private 构造器
    Constructor con2 = clz.getDeclaredConstructor(String.class);
    // 调用私有构造器,必须先设置为可访问
    con2.setAccessible(true);
    Object obj2 = con2.newInstance("小码");
    System.out.println(obj2);
}

注意: 不能直接访问没有权限(非public)的成员,如果想要使用反射去操作非public的成员.必须设置一个可以访问的标记.

​ 我们尝试私有化构造器来创建对象,结果被告知权限不够

​ 解决办法如下:

public void setAccessible(boolean flag): 传递一个true,表示可以访问,表示不管权限.

​ 从 API 中我们可以发现,Constructor,Field,MethodAccessibleObject的子类,因为这三种成员都是可以被访问private 修饰符修饰的。

代码语言:javascript复制
package com.test.reflect;

import java.lang.reflect.Constructor;

/**
 * @author Xiao_Lin
 * @date 2020/12/28 20:17
 */
public class TestReflect {

  public static void main(String[] args) throws Exception {
    Class<?> student = Class.forName("com.test.reflect.Student");
    System.out.println(student);
    Constructor<?> constructor = student.getDeclaredConstructor(String.class);
    constructor.setAccessible(true);
    Object zs = constructor.newInstance("张三");
    System.out.println(zs);

  }
}

​ 只要看到传入全限定名,基本上都是要使用反射,通过全限定名来获取字节码对象. 只要看到无指定构造器但是能创建对象,基本上都是要通过字节码对象的 newInstance 去创建对象.

4.5、获取方法

4.5.1、获取所有方法

  1. public Method[] getMethods();: 可以获取到所有的公共的方法,包括继承的。
  2. public Method[] getDeclaredMethods();:获取到本类中所有的方法,包括非public的,不包括继承的。

4.5.2、获取指定的方法

  1. public Method getMethod(String name, Class... parameterTypes);
  2. public Method getDeclaredMethod(String name, Class... parameterTypes): name: 方法名,parameterTypes: 当前方法的参数列表的类型 注意,要找到某一个指定的方法,必须要使用方法签名才能定位到,而方法签名=方法名 参数列表,经验和获取构造器的经验一样,带着s表示获取多个,带着declared表示忽略访问权限。

4.5.3、获取方法的练习

代码语言:javascript复制
@Test
public  void testGetMethod() throws Exception {
    /**
    	1 获取所有 public 方法,包括父类的 
    	2 获取所有方法,包括 private 不包括父类的 
    	3 获取指定参数的public 的方法,包括父类的 
    	4 获取指定参数的private 方法,不包括父类的
    **/
    // 1 获取字节码对象
    Class clz = Class.forName("cn.linstudy.domain.Person");
    // 2 获取构造器来创建对象
    // 3 获取方法
    //1 获取所有 public 方法,包括父类的
    Method[] methods = clz.getMethods();
    for(Method m : methods){
        System.out.println(m);
    }
    System.out.println("---------");
    //2 获取所有方法,包括 private 不包括父类的
    Method[] methods2 = clz.getDeclaredMethods();
    for(Method m : methods2){
        System.out.println(m);
    }
    System.out.println("---------");
    //3 获取指定参数的 public 的方法,包括父类的
    Method sayHelloMethod = clz.getMethod("sayHello", String.class);
    System.out.println(sayHelloMethod);
    //4 获取指定参数的private 方法,不包括父类的
    Method doWorkMethod = clz.getDeclaredMethod("doWork", String.class);
    System.out.println(doWorkMethod);
}

4.6、调用方法

public Object invoke(Object obj, Object... args);:

obj: 表示调用该方法要作用到那个对象上…

args:调用方法的实际参数方法的返回值表示,调用该方法是否有返回值,如果有就返回,如果没有返回null。

传统的调用方法

代码语言:javascript复制
Student t = new Student(1, "张三"); 
t.sleep(5);// 张三,睡5个小时。

使用反射创建对象调用方法

代码语言:javascript复制
Method m = clz.getMethod(“sleep”, int.class);// 找到sleep方法。
m.invoke(obj, 5);// 睡,5个小时。
代码语言:javascript复制
public class Person {
    private Long id;
    private String name;
    public Person() {
    }
    public Person(Long id, String name) {
        this.id = id;
        this.name = name;
    }
    private Person(String name) {
        this.name = name;
    }
    public void sayHello(){
        System.out.println("hello");
    }
    public String sayHello(String name){
        System.out.println(name   ": hello");
        return "您好";
    }
    public static void sayHello(String name,Long id){
        System.out.println("调用静态方法");
    }
    private void doWork(){
        System.out.println("doWork");
    }
    private void doWork(String name){
        System.out.println(name   ": doWork");
    }
    // getter方法 setter 方法
    public String toString() {
        return "Person{"  
                "id="   id  
                ", name='"   name   '''  
                '}';
    }
}
代码语言:javascript复制
@Test
public  void testGetMethod() throws Exception {
    // 1 获取字节码对象
    Class clz = Class.forName("com.reflect.Person");
    // 2 获取构造器来创建对象
    Object obj = clz.newInstance(); // 使用公共的无参数的构造器
    // 3 获取方法
    //1 获取所有 public 方法,包括父类的
    Method[] methods = clz.getMethods();
    for(Method m : methods){
        System.out.println(m);
    }
    System.out.println("---------");
    //2 获取所有方法,包括 private 不包括父类的
    Method[] methods2 = clz.getDeclaredMethods();
    for(Method m : methods2){
        System.out.println(m);
    }
    System.out.println("---------");
    //3 获取指定参数的 public 的方法,包括父类的
    Method sayHelloMethod = clz.getMethod("sayHello", String.class);
    System.out.println(sayHelloMethod);
    // 调用方法
    Object val1 = sayHelloMethod.invoke(obj, "张三");

    System.out.println("值1:"   val1);
    //4 获取指定参数的private 方法,不包括父类的
    Method doWorkMethod = clz.getDeclaredMethod("doWork", String.class);
    System.out.println(doWorkMethod);
    // 设置可访问
    doWorkMethod.setAccessible(true);
    // 调用私有的方法
    doWorkMethod.invoke(obj,"李四");
     // 调用静态方法
	Method staticSayHelloMethod = clz.getDeclaredMethod("sayHello", String.class, 
Long.class);
	// 不需要对象去调用,但是参数必须加上null,不然会把后面的参数作为调用方法的对象了.
	staticSayHelloMethod.invoke(null,"小明",1L);
}

注意:

  1. 方法也是可以被访问私有修饰符修饰的,所以,如果要访问非 public 修饰的方法,需要在访问之前设置可访问 method.setAccessible(true);
  2. 如果调用的是静态方法,是不需要对象的,所以此时在invoke方法的第一个参数,对象直接传递一个 null 即可。

4.7、获取字段

4.7.1、获取单个字段

public Field getField(String name);:

public Field getDeclaredField(String name);

name 要获取的字段的名称

4.7.2、获取所有字段

public Field[];

getFields() ;

public Field[] getDeclaredFields();

代码语言:javascript复制
@Test
public void testField() throws Exception {
    // 1 获取字节码对象
    Class clz = Person.class;
    Object obj = clz.newInstance();
    // 2 获取字段
    Field[] fs = clz.getFields();
    for(Field f: fs){
        System.out.println(f);
    }
    Field[] fs2 = clz.getDeclaredFields();
    for(Field f: fs2){
        System.out.println(f);
    }
    // 获取单个字段
    Field nameField = clz.getDeclaredField("name");
    System.out.println(nameField);
}

4.8、操作字段

get(Object obj);

set(Object obj,Object value);

代码语言:javascript复制
// 设置私有字段可访问
nameField.setAccessible(true);
// 操作name字段
// 设置那么字段的数据
nameField.set(obj,"小狼");
// 获取name字段的数据
Object nameValue = nameField.get(obj);
System.out.println(nameValue);

五、内省

5.1、JavaBean

​ JavaBean 是 Java 中最重要的一个可重用的组件(减少代码重复,可重用,封装业务逻辑,封装数据)。

5.1.1、JavaBean 的规范要求

  1. 使用 public 修饰。
  2. 字段私有化。
  3. 提供 get/set 方法。
  4. 公共的无参数的构造器(使用反射,使用字节码对象.newInstance去创建对象)。

5.1.2、三大成员

  1. 事件
  2. 方法
  3. 属性

5.1.3、什么是属性

​ JavaBean 可以封装数据,就是将数据保存到一个 bean 对象的属性中的。

​ 属性不是字段,属性是通过get/set方法推导出来的。

​ **规范的get方法/获取方法/读方法:**public修饰、无参数、有返回、get开头。

​ **规范的set方法/设置方法/写方法:**public修饰、有参数、无返回、set开头。

​ 注意:

  1. 只要是标准的get/set方法,就存在属性,不一定非得是通过工具自动生成的规范的写法。
  1. 字段是 boolean 的,读方法不是 get 开头,而是 is 开头。

5.2、内省的概述

​ JavaBean是一个非常常用的组件,无外乎就是操作里面的属性。而之前我们要获取JavaBean中的方法,如果使用反射非常麻烦,于是SUN公司专门提供了一套操作 JavaBean 属性的API: 内省Introspector)。

5.3、内省的作用

  1. 获取到属性名和属性类型等相关状态信息。
  2. 获取属性对应的读写方法操作属性的值等操作方式。

5.4、内省常用的API

  1. 通过字节码对象,获取到JavaBean的描述对象,返回 JavaBean的描述对象 public static BeanInfo getBeanInfo(Class beanClass, Class stopClass);
  2. 通过 JavaBean 描述对象获取属性描述器 PropertyDescriptor[] getPropertyDescriptors();
  3. 通过属性描述器,获取到属性名、属性类型、读写(getter/setter)方法 获取属性名:public String getName(); 获取属性类型:public Class getPropertyType(); 获取读方法(getter):public Method getReadMethod(); 获取写方法(setter):public Method getWriteMethod();

​ 通过字节码对象来获取BeanInfo对象的时候,默认会内省当前字节码对象以及其所有的父类的信息。比如:getBeanInfo(A.class),其实它也会内省A的父类,如Object的信息。一般来说,我们不关心父类的属性相关信息,此时可以调用getBeanInfo的重载方法:getBeanInfo(beanClass,stopClass)

示范:BeanInfo beanInfo = Introspector.getBeanInfo(Person.class,Object.class);

代码语言:javascript复制
package com.day03.IntrospectorDemo;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

/**
 * @author Xiao_Lin
 * @date 2020/12/29 13:37
 */
public class TestIntrospector {

  public static void main(String[] args) throws Exception {
    //创建对象
    Student student = Student.class.newInstance();
    //把 JavaBean 转成 beanInfo
    BeanInfo beanInfo = Introspector.getBeanInfo(student.getClass(),Object.class);
    //通过通过beanInfo获取所有属性
    PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
    //遍历属性描述器数组,获取到每个属性描述器
    for (PropertyDescriptor pd : propertyDescriptors) {
      //获取属性名
      System.out.println("属性名 = "   pd.getName());
      //获取属性类型
      System.out.println("属性类型 = "   pd.getPropertyType());
      //获取属性的getter/setter方法
      Method getMethod = pd.getReadMethod();
      System.out.println("get方法 = "  getMethod);
      Method setMethod = pd.getWriteMethod();
      System.out.println("set方法 = "  setMethod);
      //调用age属性的set方法
      if ("age".equals(pd.getName())){
        //执行age的set方法,invoke参数含义是给哪个对象赋予哪个值
        setMethod.invoke(student,22);
      }
      //再次执行get方法
      System.out.println(student.getAge());
    }
  }
}

5.5、JavaBean 和 Map 之间的转化

​ map和JavaBean的结构很类似,我们可以将 map 和 JavaBean 相互转换.将key和属性名一 一对应起来

5.5.1、JavaBean转map

代码语言:javascript复制
 // Javabean 转 map
  public static Map<String, Object> BeanToMap(Object obj) throws Exception{
    Map<String, Object> map = new HashMap<>();
    //通过内省获得所有属性
    BeanInfo beanInfo = Introspector.getBeanInfo(obj.getClass(), Object.class);
    PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
    for (PropertyDescriptor pd : pds) {
      //获取属性名作为key
      String key = pd.getName();
      //获取属性的getter方法并且调用
      Object value = pd.getReadMethod().invoke(obj);
      map.put(key, value);
    }
    return map;
  }
代码语言:javascript复制
public class BeanMap {

  public static void main(String[] args) throws Exception{
    Map<String, Object> map = BeanToMap(new Student("张三", 20));
    map.forEach((k,v)-> System.out.println(k "->" v));
  }
}

5.5.2、map转JavaBean

代码语言:javascript复制
//map转JaveBean,这里使用泛型
  public static <T> T  MapToBean(Map<String, Object> map ,Class<T> clz) throws Exception{
    //创建JavaBean对象
    T t = clz.newInstance();
    //遍历属性,获取属性名作为mao的key 去获取value值,再设置给setter方法
    //获取所有属性
    BeanInfo beanInfo = Introspector.getBeanInfo(clz, Object.class);
    PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
    for (PropertyDescriptor pd : pds) {
      String key = pd.getName();
      Object value = map.get(key);
      pd.getWriteMethod().invoke(t,value);
    }
    return t;
  }

六、注解

6.1、注解介绍

​ 我们可以使用注解来修饰类中的成员信息,注解其实就是Annotation。

6.2、定义格式

代码语言:javascript复制
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface 注解名 {
}

​ 定义格式:@interface 注解名

​ 使用格式:@注解名(属性名=属性值, 属性名=属性值)

​ 注解贴在程序元素上,想要拥有某一些功能,必须有三个角色去参与

  1. 注解本身
  2. 被贴的程序元素
  3. 第三方程序,使用反射给注解赋予功能(在注解的背后,一定有一段代码给注解赋予功能)。

6.3、内置注解

  • @Override 限定覆写父类方法
  • @Deprecated 标记已过时,不推荐使用.在JDK5之前,使用文档注释来标记过时
  • @SuppressWarings 抑制编译器发出的警告
  • @Functionallnterface 标记该接口是一个函数接口(JDK1.8开始出现的)

6.4、元注解

注解:用来贴在类/方法/变量等之上的一个标记,第三方程序可以通过这个标记赋予一定功能。

元注解:在定义注解的时候用来贴在注解上的注解,用来限定注解的用法。他囊括了三个注解

6.4.1、@Target

​ 表示注解可以贴在哪些位置(类,方法上,构造器上等等).位置的常量封装在ElementType枚举类 中。

  • ElementType.ANNOTATION_TYPE只能修饰类。
  • ElementType.CONSTRUCTOR只能修饰构造方法。
  • ElementType.FIELD只能修饰字段(属性),包括枚举常量。
  • ElementType.LOCAL_VARIABLE只能修饰局部变量。
  • ElementType.METHOD只能修饰方法。
  • ElementType.PACKAGE只能修饰包。
  • ElementType.PARAMETER只能修饰参数。
  • ElementType.TYPE只能修饰类,接口,枚举。

6.4.2、@Retention

​ 表示注解可以保存在哪一个时期,表示时期的值,封装在RetentionPolicy枚举类中。

6.4.3、@Documented

​ 使用@Documented标注的标签会保存到API文档中。

6.4.4、@Inherited

@Inherited标注的标签可以被子类所继承。

标记已过时,不推荐使用.在JDK5之前,使用文档注释来标记过时

  • @SuppressWarings 抑制编译器发出的警告
  • @Functionallnterface 标记该接口是一个函数接口(JDK1.8开始出现的)

6.4、元注解

注解:用来贴在类/方法/变量等之上的一个标记,第三方程序可以通过这个标记赋予一定功能。

元注解:在定义注解的时候用来贴在注解上的注解,用来限定注解的用法。他囊括了三个注解

6.4.1、@Target

​ 表示注解可以贴在哪些位置(类,方法上,构造器上等等).位置的常量封装在ElementType枚举类 中。

  • ElementType.ANNOTATION_TYPE只能修饰类。
  • ElementType.CONSTRUCTOR只能修饰构造方法。
  • ElementType.FIELD只能修饰字段(属性),包括枚举常量。
  • ElementType.LOCAL_VARIABLE只能修饰局部变量。
  • ElementType.METHOD只能修饰方法。
  • ElementType.PACKAGE只能修饰包。
  • ElementType.PARAMETER只能修饰参数。
  • ElementType.TYPE只能修饰类,接口,枚举。

6.4.2、@Retention

​ 表示注解可以保存在哪一个时期,表示时期的值,封装在RetentionPolicy枚举类中。

6.4.3、@Documented

​ 使用@Documented标注的标签会保存到API文档中。

6.4.4、@Inherited

@Inherited标注的标签可以被子类所继承。

0 人点赞