Java基础加强重温_12:Junit单元测试、反射、反射的使用、反射案例、注解、使用自定义注解、元注解(@Target、@Retention)、注解解析、注解案例(模拟Junit)、动态/静态代理

摘要

Java基础加强重温_12:
Junit单元测试(@Before(@BeforeEach)、@After(@AfterEach))
反射
反射的使用【获取Class对象(类名.class,类对象.getClass(),Class.forName("…"))、获取Class对象的信息、获取Class对象的Constructor信息、获取Class对象的Method信息、获取Class对象的Field信息】
反射案例(根据配置文件产生对象)
注解
使用自定义注解(自定义注解语法格式(@interface)、注解属性value的赋值方式)
元注解(@Target(ElementType枚举值)、@Retention(RetentionPolicy枚举值))
注解解析
注解案例(模拟Junit)
代理
动态代理
静态代理

一、Junit单元测试

Junit是一个Java语言的单元测试框架,简单理解为可以用于取代java的main方法。Junit属于第三方工具,一般情况下需要导入jar包。不过,多数java开发环境已经集成了JUnit作为单元测试工具。
在Java中,一个类就是一个单元。单元测试是开发者编写的一小段代码,用于检验某个类某个方法的功能或某个业务逻辑是否正确。

理解:
什么是Junit?
Java语言编写的第三方单元测试工具(类库)
类库 ==> 类的集合 ==> junit.jar

什么是单元测试?
在Java中一个类就是一个单元
程序猿编写的一小段代码对某个类中的方法进行测试,保证方法的正确性,有效性以及稳定性。

Junit的作用
对某个类中的方法进行测试,保证方法的正确性,有效性以及稳定性。
实际项目中,需要分层开发,一般分为三层结构开发
表现层:直接面向用户,直接和用户打交道,接收用户输入的内容并展示数据给用户看
业务逻辑层:处理业务逻辑,比如登录判断用户名和密码是否正确,转账
数据访问层:直接和数据库打交道,对数据库进行增删改查操作

1、Junit的使用

下载官网: http://www.junit.org

使用步骤

1、编写业务类
2、创建测试类。编写测试方法
3、方法上添加@Test注解,导包

2、测试方法

测试方法命名规则:
以test开头,使用驼峰命名法。

测试方法声明要求:
必须使用注解@Test、
方法必须使用public修饰符、
没有返回值,
测试的方法没有参数。

IDAE运行测试方法

选中方法名:右键->Run测试方法名,则运行选中的测试方法
比如测试方法名为testsum,则右键-> Run testsum。

选中类名:右键-> Run类名,则运行该类的所有测试方法
比如类名为TestCalculte,则右键-> Run TestCalculte

选中模块名或项目名:右键->Run ‘All Tests’,则运行整个模块中所有类的所有测试方法。

查看测试结果

绿色:表示测试通过
在这里插入图片描述
红色:表示失败或出现错误
在这里插入图片描述

3、常用注解

Junit4.0 常用的注解
@Before:用来修饰方法,该方法会在每个测试方法执行之前执行1次
@After:用来修饰方法,该方法会在每个测试方法执行之后执行1次

Junit5.0 常用的注解
@BeforeEach:用来修饰方法,该方法会在每个测试方法执行之前执行1次
@AfterEach:用来修饰方法,该方法会在每个测试方法执行之后执行1次

不同版本的不同写法,作用都一样

4、Junit单元测试案例1(测试求和业务类)

业务类

/**
业务类
*/
public class Calculate {
	/*
	求a和b之和
	*/
	public int sum(int a,int b){
		return a + b;
	}
	
	/**
	求a和b之差
	*/
	public int sub(int a,int b){
	return a - b;
	}
}

测试类

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

public class TestCalculate {
	@Before
	public void myBefore(){
		System.out.println("测试方法前执行一次");
	}

	/*
	测试方法
	*/
	@Test
	public void testSum(){
		// 创建业务类对象
		Calculate c = new Calculate();
		// 调⽤用求和方法
		int result = c.sum(1,2);
		System.out.println(result);
	}

	/*
	测试方法
	*/
	@Test
	public void testSub(){
		// 创建业务类对象
		Calculate c = new Calculate();
		// 调用求和方法
		int result = c.sub(1,2);
		System.out.println(result);
	}

	@After
	public void myAfter(){
		System.out.println("测试方法后执行一次");
	}
}

5、Junit单元测试案例2

需求

使用junit进行单元测试

分析

1. Junit的使用步骤
    1. 编写业务类:实现某一个业务逻辑的类,比如对学生信息进行增删改查的类
        1.1 编写业务方法:实现某个功能的方法,比如添加学生或删除删除

    2. 编写测试类:对业务类中的业务方法进行测试的类
        2.1 编写测试方法:一个业务方法就对应一个测试方法


2. 测试方法的要求
    测试类的命名要求:
        以Test开头,以业务类名结尾
        比如业务类名是StudentDao,则测试类名为TestStudentDao
    测试方法的命名要求:
        以test开头,以业务方法名结尾
        比如业务方法名是saveStudent,则测试方法名为testSaveStudent

    测试方法的声明要求:
        必须public修饰,必须没有参数,必须没有返回值,必须使用@Test注解修饰

3. 测试方法的运行方式
    选中测试方法名 --> 右键  --> Run 测试方法名  运行选中的测试方法
    选中测试类名 --> 右键  --> Run 测试名  运行测试类中的所有测试方法
    选中项目名 --> 右键  --> Run All Tests  运行当前项目中的所有测试类中的所有测试方法

4. 如何查看测试结果
    绿色:代表测试通过
    红色:代表测试不通过,有异常

代码实现

实体类

public class Student {

}

数据访问层类:对学生进行增删改查操作(业务类)

public class StudentDao {
    // 保存学生(业务方法)
    public boolean saveStudent(Student stu){
        System.out.println("保存学生到数据库....");
        return true;
    }

    // 删除学生(业务方法)
    public void deleteById(int id){
        System.out.println("删除id为"+id+"学生");
    }
}

测试类

public class TestStudentDao {


    private StudentDao dao;

    @Before // 用来修饰方法,该方法会在每个测试方法执行之前执行1次
    public void init(){
        System.out.println("init");
        // 创建数据访问层对象
        dao = new StudentDao();
    }

    @After // 用来修饰方法,该方法会在每个测试方法执行之后执行1次
    public void close(){
        System.out.println("close");
    }

    // 测试方法
    @Test
    public void testSaveStudent(){
        // 创建学生对象
        Student stu = new Student();
        // 保存学生信息
        boolean result = dao.saveStudent(stu);
        /**
         断言:预先判断某个条件一定成立,如果条件不成立则直接奔溃
         Assert工具类的方法:
             assertEquals(String message, Object expected, Object actual)
                 message:异常描述信息
                 expected:期望值
                 actual:实际值
         */
        Assert.assertEquals("期望值和实际值不一致", true , result);
        System.out.println("result = " + result);
    }

    // 测试方法
    @Test
    public void testDeleteById(){
        // 删除学生
        dao.deleteById(1);
    }
}

二、反射

反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的方法,属性,构造方法等成员。

反射示意图
在这里插入图片描述
理解:
反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员:
构造方法:创建类的对象
成员方法:调用方法
成员变量:赋值和取值
反射理解为:就是另一种创建对象,调用方法,给成员变量赋值和取值的方式。

//传统创建对象
Student st = new Student();
st.study(); //调用方法
st.setName(); //赋值
st.getName(); //获取值

1、反射在实际开发中的应用

开发IDE(集成开发环境)

在这里插入图片描述
以上的IDE内部都大量使用了反射机制,我们在使用这些IDE写代码也无时无刻的使用着反射机制,一个常用反射机制的地方就是当我们通过对象调用方法或访问属性时,开发工具都会以列表的形式显示出该对象所有的方法或属性,以供方便我们选择使用,如下图:
在这里插入图片描述
这些开发工具之所有能够把该对象的方法和属性展示出来,就是利用了反射机制对该对象所有类进行了解剖,获取到了类中的所有方法和属性信息,这是反射在IDE中的一个使用场景。

各种框架的设计

在这里插入图片描述
以上三个图标上面的名字就是Java的三大框架,简称SSH
这三大框架的内部实现也大量使用到了反射机制,所有要想学好这些框架,则必须要求对反射机制熟练了。

小结:

反射的应用场景
开发IDE(集成开发环境,Eclispe,IDEA)
开发框架(spring,springmvc,mybatis…)
学习框架(学习反射目的)

2、使用反射解剖类的前提

必须先要获取到该类的字节码文件对象,即Class类型对象。
Class对象即是xxx.java编译后的文件xxx.class,字节码文件

关于Class描述字节码文件如下图所示:
在这里插入图片描述
说明:
1、Java中使用Class类表示某个class文件
2、任何一个class文件都是Class这个类的一个实例对象.

三、反射的使用

1、获取Class对象的三种方式

方式1:通过类名.class获取
方式2:通过类对象.getClass()方法获取
方式3:通过Class类的静态方法获取:Class.forName(“类全名字符串”)

代码示例1

创建测试类:Student

public class Student {
	static {
		System.out.println("静态代码块");
	}

	{
		System.out.println("构造代码块");
	}
}
方式1:通过类名.class获取
public class Demo01 {
	public static void main(String[] args) {
		// 获得Student的Class的对象
		Class c = Student.class;
		// 打印输出:class com.itheima.reflect.Student
		System.out.println(c);
	}
}
方式2:通过Object类的成员方法getClass()方法获取
public class Demo01 {
	public static void main(String[] args) {
		// 创建学生对象
		Student stu = new Student();
		// 获得学生类的Class对象
		Class c = stu.getClass();
		// 打印输出:class com.itheima.reflect.Student
		System.out.println(c);
	}
}
方式3:通过通过Class类的静态方法forName方法获取,Class.forName(“全限定类名”)

全限定类名,就是类名全称,带包路径的用点隔开,例如: java.lang.String。
全限定名有绝对路径的意思,从最原始最上层的地方援引版到具体的对象,这就是全限定名。

public class Demo01 {
	public static void main(String[] args) throws Exception {
		// 获得字符串的Class对象
		Class c = Class.forName("java.lang.String");
		// 打印输出:class java.lang.String
		System.out.println(c);
	}
}

代码示例2

public class Demo02 {
    public static void main(String[] args) throws Exception{
        // 通过类名.class获取
        Class c1 = Student.class;
        // 通过对象.getClass()方法获取
        Student stu = new Student();
        Class c2 = stu.getClass();
        // 通过Class类的静态方法获取:Class.forName("类全名字符串")
        Class c3 = Class.forName("com.itheima._02获取Class对象.Student");
        System.out.println(c1);
        System.out.println(c1 == c2);
        System.out.println(c1 == c3);
    }
}

2、获取Class对象的信息

获取Class对象之后,接下来介绍几个Class类常用的方法。

Class类常用方法

String getsimpleName()
	获得简单类名,只是类名,没有包
	即获得类名字符串
	
String getName ()
	获取完整类名,包含包名+类名
	即获得类全名字符串

代码示例1

获得简单类名(单纯一个类名)
public class Demo02 {
	public static void main(String[] args) throws Exception {
		// 获得字符串的Class对象
		Class c = Class.forName("java.lang.String");
		// 获得简单类名
		String name = c.getSimpleName();
		// 打印输⼊入:name = String
		System.out.println("name = " + name);
	}
}
获取完整类名(全路径类名)
public class Demo02 {
	public static void main(String[] args) throws Exception {
		// 获得字符串的Class对象
		Class c = Class.forName("java.lang.String");
		// 获得完整类名(包含包名和类名)
		String name = c.getName();
		// 打印输⼊入:name = java.lang.String
		System.out.println("name = " + name);
	}
}

代码示例2

public class Demo03 {
    public static void main(String[] args){
        // 1. 获得Class对象
        Class c = Student.class;
        // com.itheima._02获取Class对象.Student
        System.out.println(c.getName());
        // Student
        System.out.println(c.getSimpleName());
    }
}

3、获取Class对象的Constructor信息(获取构造方法)

利用反射可以在程序运行过程中对类进行解剖并操作里面的成员。
一般常操作的成员有构造方法,成员方法,成员变量等等。
要利用反射操作类的构造方法,我们先了解Constructor类。

理解:
类中的每一个构造方法都是一个Constructor类的对象
反射操作构造方法就是要获得构造方法对应的Constructor对象:通过Constructor对象创建类的对象

Constructor类(构造方法类)

Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象
通过Constructor类对象可以实例化解剖的类对象。
所有类的每一个构造方法,都是一个Constructor类对象
在这里插入图片描述

Class类中与Constructor类相关的方法(获取Constructor对象方法)

Constructor getConstructor (Class... parameterTypes)
	根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
	如果不存在对应的构造方法,则会抛出java. 1ang. NoSuchMethodException异常。
	
Constructor getDeclaredConstructor(Class... parameterTypes)
	根据参数类型获取构造方法对象,包括private修饰的构造方法。
	如果不存在对应的构造方法,则会抛出java. lang. NoSuchMethodException异常。

//多个s
Constructor[] getConstructors()
	获取所有的public修饰的构造方法
	
Constructor [] getDeclaredConstructors()
	获取所有构造方法,包括privat修饰的

Constructor类常用方法(Constructor类对象方法)

T newInstance(Object... initargs)
	根据指定参数创建对象
	创建处理的对象类型是Object,需要转换到需要的类型
	
void setAccessible(true)
	暴力反射,设置为true可以直接访问私有类型的构造方法。
	即设置是否取消权限检查
	true: 取消权限检查
	false:不取消检测权限,默认值

代码示例1

创建学生类

public class Student {
	// 姓名
	private String name;
	// 性别
	public String gender;
	// 年龄
	private int age;
	// public 有参构造方法
	public Student(String name, String gender, int age) {
		System.out.println("public 修饰有参数构造方法");
		this.name = name;
		this.gender = gender;
		this.age = age;
	}
	
	// public 无参构造方法
	public Student() {
		System.out.println("public 修饰无参数构造方法");
	}
	
	// private 有参构造方法
	private Student(String name,String gender){
		System.out.println("private 修饰构造方法");
		this.name = name;
		this.gender = gender;
	}
	// getter & setter 方法
	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public String getGender() {
		return gender;
	}
	
	public void setGender(String gender) {
		this.gender = gender;
	}
	
	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	// 普通方法
	public void sleep(){
		System.out.println("睡觉");
	}
	
	public void sleep(int hour){
		System.out.println("public修饰---sleep---睡" + hour + "小时");
	}
	
	private void eat(){
		System.out.println("private修饰---eat方法---吃饭");
	}
	
	// 静态方法
	public static void study(){
		System.out.println("静态方法---study方法---好好学习Java");
	}

	@Override
	public String toString() {
		return "Student{" +
				"name='" + name + '\'' +
				", gender='" + gender + '\'' +
				", age=" + age +
				'}';
	}
}

测试类

/**
* @author pkxing
* @version 1.0
* @description 获取Class对象的Constructor信息
* @date 2018/1/26
*/
public class Demo03 {
	public static void main(String[] args)throws Exception{
		test01();
		test02();
		test03();
		test04();
	}

	/**
	4. Constructor[] getDeclaredConstructors()
	获取所有构造方法,包括privat修饰的
	*/
	public static void test04() throws Exception{
		System.out.println("----------- test04() -----------");
		// 获取Student类的Class对象
		Class c = Student.class;
		// 获取所有的public修饰的构造方法
		Constructor[] cons = c.getDeclaredConstructors();
		// 遍历构造方法数组
		for(Constructor con:cons) {
			// 输出con
			System.out.println(con);
		}
	}

    /**
     3. Constructor[] getConstructors()
        获取所有的public修饰的构造方法
     */
    public static void test03() throws Exception{
        System.out.println("----------- test03() -----------");
        // 获取Student类的Class对象
        Class c = Student.class;
        // 获取所有的public修饰的构造方法
        Constructor[] cons = c.getConstructors();
        // 遍历构造方法数组
        for(Constructor con:cons) {
            // 输出con
            System.out.println(con);
        }
    }
    
    /**
     2. Constructor getDeclaredConstructor(Class... parameterTypes)
        根据参数类型获取构造方法对象,包括private修饰的构造方法。
        如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
     */
    public static void test02() throws Exception{
        System.out.println("----------- test02() -----------");
        // 获取Student类的Class对象
        Class c = Student.class;
        // 根据参数获取对应的private修饰构造方法对象
        Constructor cons = c.getDeclaredConstructor(String.class,String.class);
        // 注意:private的构造方法不能直接调用newInstance创建对象,需要暴力反射才可以
        // 设置取消权限检查(暴力反射)
        cons.setAccessible(true);
        // 调⽤用Constructor方法创建学生对象
        Student stu = (Student) cons.newInstance("林青霞","女");
        // 输出stu
        System.out.println(stu);
    }

    /**
     1. Constructor getConstructor(Class... parameterTypes)
     根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
     如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
     */
    public static void test01() throws Exception{
        System.out.println("----------- test01() -----------");
        // 获取Student类的Class对象
        Class c = Student.class;
        // 根据参数获取对应的构造方法对象
        Constructor cons = c.getConstructor(String.class,String.class,int.class);
        // 调⽤用Constructor方法创建学生对象
        Student stu = (Student) cons.newInstance("张曼玉","女",28);
        // 输出stu
        System.out.println(stu);
    }
}

输出结果:
在这里插入图片描述

代码示例2

需求

通过无参数构造方法创建学生对象

代码实现

学生类

public class Student {
    private String name;
    private int age;
    private String gender;

    private Student(String name, int age, String gender) {
        System.out.println("三个参数构造方法");
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public Student() {
        System.out.println("无参数构造方法");
    }

    public Student(String name, int age) {
        System.out.println("两个参数的构造方法");
        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;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public void sleep(String name,int hour){
        System.out.println("和"+name+"睡"+hour+"小时");
    }

    public String study(){
        System.out.println("好好学习....");
        return "天天向上";
    }

    private void eat(String name){
        System.out.println(name+":吃了个蛋");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}

测试类

public class Demo04 {
    /*
      反射操作public无参数构造方法
      目标:通过无参数构造方法创建学生对象
   */
    @Test
    public void test01() throws Exception{
        // 获得Class对象
        Class c = Student.class;
        // 获得Constructor对象
        Constructor cons = c.getConstructor();
        // 调用Constructor方法创建对象
        Object obj = cons.newInstance();
        System.out.println(obj);
    }

    /*
        反射操作public有参数构造方法
     */
    @Test
    public void test02() throws Exception{
        // 获得Class对象
        Class c = Student.class;
        // 获得Constructor对象
        Constructor cons = c.getConstructor(String.class, int.class);
        // 调用Constructor方法创建对象
        Student stu = (Student) cons.newInstance("小泽", 20);
        System.out.println(stu);
    }

    /*
        反射操作private有参数构造方法
     */
    @Test
    public void test03() throws Exception{
        // 获得Class对象
        Class c = Student.class;
        // 获得Constructor对象
        Constructor cons = c.getDeclaredConstructor(String.class, int.class,String.class);
        // 暴力反射
        cons.setAccessible(true);
        // 调用Constructor方法创建对象
        Student stu = (Student) cons.newInstance("小泽", 20,"女");
        System.out.println(stu);
    }
}

4、获取Class对象的Method信息

反射可以通过操作成员方法,达到调用方法目的。
同样的在操作成员方法之前我们需要学习一个类: Method类。

Method类(类方法类)

Method是方法类,类中的每一个方法都是Method的对象,通过Method对象可以调用方法。
在这里插入图片描述

Class类中与Method相关方法(获得方法对象的方法)

Method getMethod("方法名", 方法的参数类型... 类型) 
    根据方法名和参数类型获得一个方法对象,只能是获取public修饰的
    
Method getDeclaredMethod("方法名", 方法的参数类型... 类型)
    根据方法名和参数类型获得一个方法对象,包括private修饰的

//多个s
Method[] getMethods()
    获取所有的public修饰的成员方法,包括父类中。

Method[] getDeclaredMethods()
    获取当前类中所有的方法,包含private(私有)的,不包括父类中。

Method类常用方法(方法类对象方法)

Object invoke(Object obj, Object... args) 
	* 调用、调度、执行方法
	* obj:调用哪个对象的方法
	* args:调用方法时传递的参数
	* 返回值:方法执行结果
    如果obj=null,则表示该方法是静态方法
  
void setAccessible(boolean flag) 
    暴力反射,设置为true可以直接调用私有修饰的成员方法
    * 设置是否取消权限检查
    * true: 取消权限检查
    * false:不取消检测权限,默认值

代码示例1

/**
 * @author pkxing
 * @version 1.0
 * @description 获取Class对象的Method信息
 * @date 2018/1/26
 */
public class Demo04 {
    public static void main(String[] args)throws  Exception{
        // 获得Class对象
        Class c = Student.class;
        // 快速创建一个学生对象
        Student stu = (Student) c.newInstance();

        // 获得public修饰的方法对象
        Method m1 = c.getMethod("sleep",int.class);
        // 调用方法m1
        m1.invoke(stu,8);

        // 获得private修饰的方法对象
        Method m2 = c.getDeclaredMethod("eat");
        // 注意:private的成员方法不不能直接调用,需要暴力反射才可以
        // 设置取消权限检查(暴力反射)
        m2.setAccessible(true);
        // 调用方法m2
        m2.invoke(stu);

        // 获得静态方法对象
        Method m3 = c.getDeclaredMethod("study");
        // 调用方法m3
        // 注意:调用静态方法时,obj可以为null
        m3.invoke(null);
      
        System.out.println("------------获得本类和父类所有public的方法,不包括private,包括父类的---------------");
        // 获得所有public的方法,包括父类的
        Method[] ms = c.getMethods();
        // 遍历方法数组
        for(Method m : ms) {
            System.out.println(m);
        }
        System.out.println("-----------获得本类所有方法,包括private,不包括父类-----------------------");
        // 获得本类所有方法,包括private,不包括父类
        Method[] ms2 = c.getDeclaredMethods();
        // 遍历方法数组
        for(Method m : ms2) {
            System.out.println(m);
        }
    }
}

代码示例2

public class Demo05 {
    /*
     * 反射操作无参数成员方法
     */
    @Test
    public void test01() throws Exception{
        // 获得Class对象
        Class c = Student.class;
        // 创建学生对象
        Object stu = c.getConstructor().newInstance();
        // 获得无参数成员方法对象:Method对象
        Method studyMethod = c.getMethod("study");
        // 通过Method对象调用study方法
        Object result = studyMethod.invoke(stu);
        System.out.println("result = " + result);

    }


    /*
        反射操作有参数成员方法
     */
    @Test
    public void test02() throws Exception{
        // 获得Class对象
        Class c = Student.class;
        // 创建学生对象
        Object stu = c.getConstructor().newInstance();
        // 根据方法名和参数类型获得Method对象
        Method sleepMethod = c.getMethod("sleep", String.class, int.class);
        // 通过Method对象调用sleep方法
        sleepMethod.invoke(stu, "小波",10);

        System.out.println("--------------------");
        // 反射操作私有成员方法
        Method eatMethod = c.getDeclaredMethod("eat", String.class);
        // 暴力反射
        eatMethod.setAccessible(true);
        // 调用stu对象的eat方法:传递参数是:小武
        eatMethod.invoke(stu,"小武");
    }

    /*
        反射之获得所有成员方法对象
     */
    @Test
    public void test03() {
        // 获得Class对象
        Class c = Student.class;
        // 获得当前类的所有成员方法对象,包括父类的,不包括private
        // Method[] methods = c.getMethods();

        // 获得当前类的所有成员方法对象,不包括父类的,包括private
        Method[] methods = c.getDeclaredMethods();
        // 遍历输出
        for (Method method : methods) {
            System.out.println(method);
        }
    }
}

5、获取Class对象的Field信息

利用反射操作成员变量可以达到给成员变量赋值和取值的目的

Field类(属性类)

Field是属性类,类中的每一个属性(成员变量)都是Field的对象,通过Field对象可以给对应的成员变量赋值和取值。
反射操作成员变量就是要获得该成员变量对应的Field对象:给成员变量赋值和取值
在这里插入图片描述

Class类中与Field相关方法(获得Field对象的方法)

Field getDeclaredField(String name)
	根据属性名获得属性对象,包括private修饰的

Field getField(String name)
	根据属性名获得属性对象,只能获取public修饰的

Field[] getFields()
	获取所有的public修饰的属性对象,返回数组。

Field[] getDeclaredFields()
	获取所有的属性对象,包括private修饰的,返回数组。

Field类中常用方法(属性对象方法)

void set(Object obj, Object value)
	给对象obj的成员变量赋值
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z)
void setDouble(Object obj, double d)
	给对象obj的成员变量赋值

Object get(Object obj)
	获得对象obj成员变量的值
int getInt(Object obj)
long getLong(Object obj)
boolean getBoolean(Object ob)
double getDouble(Object obj)
	获得对象obj成员变量的值

Class getType(); 获取属性的类型,返回Class对象。

void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。

setXxx方法都是给对象obj的属性设置使用,针对不同的类型选取不同的方法。
getxxx方法是获取对象obj对应的属性值的,针对不同的类型选取不同的方法。

代码示例1

/**
 * @author pkxing
 * @version 1.0
 * @description 获取Class对象的Field信息
 * @date 2018/1/26
 */ public class Demo05 {
    public static void main(String[] args)throws Exception{
        // 获得Class对象
        Class c = Student.class;
        // 快速创建一个学生对象
        Student stu = (Student ) c.newInstance();
        // 获得public修饰Field对象
        Field f1 = c.getField("gender");
        // 通过f1对象给对象stu的gender属性赋值
        f1.set(stu,"风清扬");
        // 通过f1对象获取对象stu的gender属性值
        String gender = (String) f1.get(stu);
        System.out.println("性别:" + gender);

        // 获得private修饰Field对象
        Field f2 = c.getDeclaredField("age");
        // 注意:private的属性不能直接访问,需要暴力反射才可以
        // 设置取消权限检查(暴力反射)
        f2.setAccessible(true);
        // 通过f1对象给对象stu的age属性赋值
        f2.setInt(stu,30);
        // 通过f2对象获取对象stu的age属性值
        int age = f2.getInt(stu);
        System.out.println("年龄:" + age);


        System.out.println("-------获得所有public修饰的属性--------");
        // 获得所有public修饰的属性
        Field[] fs1 = c.getFields();
        // 遍历数组
        for(Field f : fs1) {
            System.out.println(f);
        }

        System.out.println("-------获得所有的属性,包括private修饰--------");
        // 获得所有的属性,包括private修饰
        Field[] fs2 = c.getDeclaredFields();
        // 遍历数组
        for(Field f : fs2) {
            System.out.println(f);
        }
    }
}

输出结果:
在这里插入图片描述

代码示例2

public class Demo06 {
    /*
       反射操作成员变量
    */
    @Test
    public void test01() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 创建学生对象
        Object stu = c.getConstructor().newInstance();
        System.out.println("赋值前..." + stu);
        // 根据成员变量名获得Field对象
        Field nameField = c.getDeclaredField("name");
        // 暴力反射
        nameField.setAccessible(true);
        // 通过Field对象给成员变量name赋值
        nameField.set(stu,"jack");
        System.out.println("赋值后..." + stu);
        // 获得对象stu成员变量name的值
        System.out.println(nameField.get(stu));// jack
    }

    /*
        反射之获得所有成员变量对象
     */
    @Test
    public void test02() {
        // 获得Class对象
        Class c = Student.class;
        // 获得所有的成员变量对象
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field);
        }
    }
}

四、反射案例(根据配置文件内容产生对象)

需求

编写一个工厂方法,可以根据配置文件产任意类型的对象。

有配置文件stu.properties,存储在项目的src文件夹下,内容如下:

class=com.itheima.reflect.Student
name=rose
gender=女
age=18

根据配置文件信息创建一个学生对象。

实现步骤分析

1、在项目src文件中新建一个包: com.itheima.reflect,并在该包下创建Student类。
2、Student类的属性: String name, String gender, int age
3、定义一个工厂方法: createObject(),方法返回值类型为: Object
4、创建Properties集合并读取stu.properties文件中的内容到集合中。
5、根据class获得学生类全名,并通过反射技术获得Class对象。
6、通过调用Class对象的方法创建学生对象。
7、遍历Properties集合,利用反射技术给学生成员变量赋值。
8、返回封装好数据的学生对象。

getResourceAsStream方法

Class类对象根据资源文件获得对应的字节输入流的方法

InputStream getResourceAsStream(String name);
	根据资源文件获得字节输入流对象,默认是从src目录下加载文件
	要求:文件路径必须以/开头

代码示例1

/**
 * @author pkxing
 * @version 1.0
 * @description com.itheima.reflect
 * @date 2018/1/26
 */ 
public class Demo06 {

    public static void main(String[] args){
        // 获取对象
        Student stu = (Student) createObject();
        // 输出对象
        System.out.println(stu);
    }
    
    /**
     * 根据配置文件创建对象
     */
    public static Object createObject(){
        try {
            // 创建属性集合
            Properties pro = new Properties();
            // 从文件中加载内容到集合中
            pro.load(Demo06.class.getResourceAsStream("/stu.properties"));

            // 根据class从集合中获得类全名
            String className = pro.getProperty("class");
            // 根据类全名,通过反射获得Class对象
            Class c = Class.forName(className);
            // 快速创建类对象
            Object obj = c.newInstance();
            
            // 获得属性集所有key的集合
            Set<String> names = pro.stringPropertyNames();
            // 遍历集合
            for (String name : names) {
                // 判断name是否class
                if (name.equals("class")) continue;
                
                // 获得值
                String value = pro.getProperty(name);
                // name:成员变量名
                // 根据成员变量名获得对应的Field对象
                Field f = c.getDeclaredField(name);
                // 暴力反射
                f.setAccessible(true);
                // 获得成员变量的类型
                Class typeClass = f.getType();
                if(typeClass == int.class){  // 判断成员变量的数据类型是否是int类型
                    f.setInt(obj, Integer.parseInt(value));
                } else {
                    // 给f对象的赋值
                    f.set(obj, value);
                }
            }
            // 返回对象
            return obj;
        }   catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

代码示例2

public class Demo07 {
    public static void main(String[] args)throws Exception{
        // 1. 创建属性集合对象
        Properties info = new Properties();
        // 2. 从配置文件中加载信息到集合中
        // InputStream in = Demo07.class.getResourceAsStream("/stu.properties");
        /**
            Class类根据资源文件获得对应的字节输入流的方法
                InputStream getResourceAsStream(String name);
                    根据资源文件获得字节输入流对象,默认是从src目录下加载文件
                    要求:文件路径必须以/开头
         */
        info.load(Demo07.class.getResourceAsStream("/stu.properties"));
        System.out.println(info);
        // 3. 根据class获得类全名字符串:"com.itheima.bean.Student"
        String className = info.getProperty("class");
        // 4. 根据类全名字符串获得Class对象:Class.forName("类全名字符串")
        Class  c = Class.forName(className);
        // 5. 获得构造方法对象并创建对象
        Object obj = c.getConstructor().newInstance();
        System.out.println(obj);
        
        // 6. 使用循环,给对象的成员变量赋值
        String[] properties = {"name","gender","age"};
        for (String property : properties) {
            // 从属性集中获得成员变量的值
            String name = info.getProperty(property); // rose female
            // 根据成员变量名获得Field对象
            Field field = c.getDeclaredField(property);
            // 暴力反射
            field.setAccessible(true);
            // 给对象obj的成员变量赋值
            field.set(obj, name);
        }

       /* 
        
        // 从属性集中获得成员变量的值
        String name = info.getProperty("name"); // rose
        // 根据成员变量名获得Field对象
        Field field = c.getDeclaredField("name");
        // 暴力反射
        field.setAccessible(true);
        // 给对象obj的成员变量赋值
        field.set(obj, name);

        // 从属性集中获得成员变量的值
        String gender = info.getProperty("gender"); // female
        // 根据成员变量名获得Field对象
        Field genderField = c.getDeclaredField("gender");
        // 暴力反射
        genderField.setAccessible(true);
        // 给对象obj的成员变量赋值
        genderField.set(obj, gender);

		*/

        System.out.println(obj);
    }
}

抽取代码

public class Demo071 {
    public static void main(String[] args)throws Exception{
        System.out.println(createObject("/stu.properties"));
        System.out.println(createObject("/book.properties"));
    }

    /**
     * 根据配置文件创建对象并返回
     */
    public static Object createObject(String fileName)throws Exception{
        // 1. 创建属性集合对象
        Properties info = new Properties();
        // 2. 从配置文件中加载信息到集合中
        /**
         Class类根据资源文件获得对应的字节输入流的方法
         InputStream getResourceAsStream(String name);
         根据资源文件获得字节输入流对象,默认是从src目录下加载文件
         要求:文件路径必须以/开头
         */
        info.load(Demo071.class.getResourceAsStream(fileName));
        // System.out.println(info);
        // 3. 根据class获得类全名字符串:"com.itheima.bean.Student"
        String className = info.getProperty("class");
        // 4. 根据类全名字符串获得Class对象:Class.forName("类全名字符串")
        Class  c = Class.forName(className);
        // 5. 获得构造方法对象并创建对象
        Object obj = c.getConstructor().newInstance();
        // System.out.println(obj);
        // 6. 给对象的成员变量赋值
        Set<String> keySet = info.stringPropertyNames();
        for (String property : keySet) {
            if (property.equals("class")) continue;
            // 从属性集中获得成员变量的值
            String value = info.getProperty(property); // "9.9"
            // 根据成员变量名获得Field对象
            Field field = c.getDeclaredField(property);
            // 暴力反射
            field.setAccessible(true);
            // 获得成员变量类型
            Class type = field.getType();
            // 判断成员变量的类型是否是int
            if (type  == int.class){
                field.setInt(obj,Integer.parseInt(value));
            } else if(type == double.class){
                field.setDouble(obj, Double.parseDouble(value));
            } else {
                // 给对象obj的成员变量赋值
                field.set(obj, value);
            }
        }
        return obj;
    }
}

案例小结

简单感受反射机制的强大之处,在后面即将学习的Spring框架中就会有大量根据配置文件信息创建对象的过程,其内部的原理和我们这个案例的原理是一样,有这个案例做基础,以后学到spring框架时就会容易理解了。

五、注解

注解是DK1.5的新特性。
注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
注解是给编译器或VM看的,编译器或VM可以根据注解来完成对应的功能。

注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后, javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。

1、注解的作用

注解的作用就是给程序带入参数。

1、编译检查标记
@override:用来修饰方法声明。用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。如下图
在这里插入图片描述
2、框架的配置(框架=代码+配置)

2、常见注解

1、@author
用来标识作者名, eclipse开发工具默认的是系统用户名。
2、@version
用于标识对象的版本号,适用范围:文件、类、方法。
3、@Override
用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。

3、自定义注解(@interface)

定义格式

public @interface 注解名{
}

//通俗
@interface 注解名{
	数据类型 属性名();
	数据类型 属性名() default 默认值;
}
代码示例

定义一个名为Student的注解

public @interface Student {

}

以上定义出来的注解就是一个最简单的注解了,但这样的注解意义不大,因为注解中没有任何内容,就好像我们定义一个类而这个类中没有任何成员变量和方法一样,这样的类意义也是不大的,所以在定义注解时会在里面添加一些成员来让注解功能更加强大,这些成员就是属性。

注解的属性

属性的作用。

可以让用户在使用注解时传递参数,让注解的功能更加强大。

属性的格式

与类定义成员属性稍微不同,注解定义属性要在属性名后面加()

格式1:
数据类型 属性名();

格式2:
数据类型 属性名() default 默认值;
属性定义示例
public @interface Student {
	String name(); // 姓名
	int age() default 18; // 年龄
	String gender() default "男"; // 性别
}
// 该注解就有了了三个属性:name,age,gender

理解:注解属性的定义相比正常的类成员属性(成员变量)定义,后面写小括号()

注解属性适用的数据类型

1、八种基本数据类型(int、float、boolean、byte、double、char、long、short)
2、String类型,Class类型,枚举类型,注解类型
3、以上所有类型的一维数组

六、使用自定义注解

注解的使用格式
无属性的注解:@注解名
有属性的注解:@注解名(属性名=属性值,属性=属性值,…)

需求

定义一个注解:Book。
包含属性:String value()书名
包含属性:double price()价格,默认值为1000
包含属性:String] authors()多位作者

代码实现

public @interface Book {
	// 书名
	String value();
	// 价格
	double price() default 100;
	// 多位作者
	String[] authors(); 
}

在类的成员方法上使自定义注解

/**
 * @author pkxing
 * @version 1.0
 * @description 书架类
 * @date 2018/1/26
 */ 
 public class BookShelf {
  
    @Book(value = "西游记",price = 998,authors = {"吴承恩","白求恩"})
    public void showBook(){

    }
}

自定义注解使用注意事项

如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
如果属性没有默认值,那么在使用注解时一定要给属性赋值。

理解:注解有属性且属性没有默认值时,在使用注解时必须给该属性赋值

特殊属性value的赋值方式

如果注解中只有一个属性且名字为value时,在使用注解时给value属性赋值时可以省略value属性名。
如果注解中除了value属性还有其他属性,只要给其他属性赋值了则value属性名不能省略了。

理解:
什么情况下给注解属性赋值时可以省略属性名?
在使用注解只给value属性赋值,没有给其他属性赋值时

只有一个属性且属性名为value时

1、当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给属性值,无论value是单值元素还是数组类型。

// 定义注解Book 
public @interface Book {
	// 书名
	String value(); 
}

// 使用注解Book 
public class BookShelf {
    @Book("西游记")
    public void showBook(){
    }
}public class BookShelf {
    @Book(value="西游记")
    public void showBook(){

    }
}

理解:注解只有一个属性且属性名定义为value时,可以写属性名或不写属性名直接赋值。

除了value还有其他属性,且有属性没有默认值

2、如果注解中除了value属性还有其他属性,且至少有一个属性没有默认值,则在使用注解给属性赋值时,value属性名不能省略。

// 定义注解Book 
public @interface Book {
	// 书名
	String value();
	// 价格
	double price() default 100;
	// 多位作者
	String[] authors();
}

// 使用Book注解:正确方式
@Book(value="红楼梦",authors = "曹雪芹") 
public class BookShelf {
	// 使用Book注解:正确方式
    @Book(value="西游记",authors = {"吴承恩","白求恩"})
    public void showBook(){

    }
}

// 使用Book注解:错误方式 
public class BookShelf {
    @Book("西游记",authors = {"吴承恩","白求恩"})
    public void showBook(){

    }
}
// 此时value属性名不能省略了。

问题分析(注解位置未限定)

刚才定义的注解是可以使用在任何成员上的,比如刚刚Book注解的使用:

// 定义注解Book 
public @interface Book {
        // 书名
        String value();
        // 价格
        double price() default 100;
        // 多位作者
        String[] authors();
}

// 使用Book注解:正确方式
@Book(value="红楼梦",authors = "曹雪芹") public class BookShelf {
	// 使用Book注解:正确方式
    @Book(value="西游记",authors = {"吴承恩","白求恩"})
    public void showBook(){

    }
}

此时Book同时使用在了类定义上或成员方法上,编译器也没有报错,因为默认情况下注解可以用在任何地方,比如类、成员方法、构造方法、成员变量等地方。如果要限制注解的使用位置,那就要学习一个新的知识点:元注解。

七、注解之元注解(限制注解使用位置)

元注解是Java API提供的注解,专门用来定义注解的注解
任何Java官方提供的非元注解的定义中都使用到了元注解。

理解:
元注解是官方定义的注解,用来定义注解的注解
官方所有的非元注解的定义中都使用到了元注解

常用元注解

@Inhertied:用来标识注解可以被子类继承
@Target:用来标识注解的作用位置
@Retention:用来标识注解的作用范围(生命周期)

元注解之@Target(作用位置)

作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。

枚举类ElementType(@Target注解属性)

@Target的value属性可用值定义在枚举类ElementType中,常用值如下:

TYPE
	注解可以使用在类和接口上

FIELD
	注解可以使用在成员变量上

METHOD
	注解可以使用在成员方法上

PARAMETER
	用在参数上

CONSTRUCTOR
	用在构造方法上

LOCAL_VARIABLE
	用在局部变量上

元注解之@Retention(生命周期)

作用:定义该注解的生命周期(有效范围)

枚举类RetentionPolicy(@Retention注解属性)

@Retention的value属性可用值定义在RetentionPolicy枚举类中,可用如下:

SOURCE
	源代码阶段:注解只存在于源码中,编译产生的class文件中就没有了,运行时也没有了

CLASS
	字节码阶段:注解存在于源码阶段,字节码阶段,运行时就没有了,默认值
	
RUNTIME
	运行时阶段:注解存在于整个项目运行过程中,三个阶段都存储
	程序可以通过反射获取该注解。

元注解使用示例

@Target({ElementType.METHOD,ElementType.TYPE}) 
@interface Stu{
    String name();
}

// 类
@Stu(name="jack") 
public class AnnotationDemo02 {
    // 成员变量
    @Stu(name = "lily")  // 编译失败
    private String gender;

    // 成员方法
    @Stu(name="rose")
    public void  test(){

    }

    // 构造方法
    @Stu(name="lucy") // 编译失败
    public AnnotationDemo02(){}
}

使用元注解后注解效果

在这里插入图片描述

八、注解解析

通过Java技术获取注解数据的过程则称为注解解析。

理解:
注解解析就是通过Java代码获得注解信息的过程

与注解解析相关的接口

Anontation接口

所有注解类型的公共接口,类似所有类的父类Object

AnnotatedElement接口

定义了与注解解析相关的方法,常用方法以下四个:

boolean isAnnotationPresent(Class annotationClass);
	判断当前对象上是否使用了指定的注解,如果使用了则返回true,否则false

T getAnnotation(Class<T> annotationClass);
	根据注解类型获得当前对象上使用的注解对象

Annotation[] getAnnotations(); 
	获得类上使用的所有注解数据,包括父类继承的
	
Annotation[] getDeclaredAnnotations();
	获得类上使用的所有注解数据,不包括父类继承的

获取注解数据的原理

注解作用在那个成员上,就通过反射获得该成员的对象来得到它的注解。

理解:
注解作用在哪个成员上就获得该成员对应的对象来获得注解数据
比如注解作用在成员变量上,则获得该成员变量对应的Field对象来获得注解数据
比如注解作用在成员方法上,则获得该成员方法对应的Method对象来获得注解数据
比如注解作用在构造方法上,则获得该构造方法对应的Constructor对象来获得注解数据
比如注解作用在类上,则获得该类对应的Class对象来获得注解数据
须知:Class,Constructor,Method,Field等类都实现了AnnotatedElement接口

注解作用在方法上,就通过方法(Method)对象得到它的注解

 // 得到方法对象
 Method method = clazz.getDeclaredMethod("方法名"); 
 // 根据注解名得到方法上的注解对象
 Book book = method.getAnnotation(Book.class);  

注解作用在类上,就通过Class对象得到它的注解

// 获得Class对象
Class c = 类名.class;
// 根据注解的Class获得使用在类上的注解对象 
Book book = c.getAnnotation(Book.class);

注解解析案例(使用反射获取注解的数据)

需求

1、定义注解Book,要求如下:
包含属性: String value()书名
包含属性: double price()价格,默认值为100
包含属性: StringD authors()多位作者
限制注解使用的位置:类和成员方法上
指定注解的有效范围:RUNTIME2
2、定义BookStore类,在类和成员方法上使用Book注解
3、定义TestAnnotation测试类,获取Book注解上的数据

代码示例

自定义注解Book

@Target({ElementType.METHOD,ElementType.TYPE}) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface Book {
        // 书名
        String value();
        // 价格
        double price() default 100;
        // 作者
        String[] authors();
}

BookStore类

@Book(value = "红楼梦",authors = "曹雪芹",price = 998) 
public class BookStore {
    @Book(value = "西游记",authors = "吴承恩")
    public void buyBook(){
    }
}

TestAnnotation类

/**
 * @author pkxing
 * @version 1.0
 * @description com.itheima.annotation
 * @date 2018/1/26
 */ 
public class TestAnnotation {
    public static void main(String[] args)  throws Exception{
        System.out.println("---------获取类上注解的数据----------");
        test01();
        System.out.println("---------获取成员方法上注解的数据----------");
        test02();
    }

    /**
     * 获取BookStore类上使用的Book注解数据
     */
    public static void test01(){
        // 获得BookStore类对应的Class对象
        Class c = BookStore.class;
        // 根据注解Class对象获取注解对象
        Book book = (Book) c.getAnnotation(Book.class);
        // 输出book注解属性值
        System.out.println("书名:" + book.value());
        System.out.println("价格:" + book.price());
        System.out.println("作者:" + Arrays.toString(book.authors()));
    }

    /**
     * 获取BookStore类成员方法buyBook使用的Book注解数据
     */
    public static void test02() throws Exception{
        // 获得BookStore类对应的Class对象
        Class c = BookStore.class;
        // 获得成员方法buyBook对应的Method对象
        Method m = c.getMethod("buyBook");
        // 根据注解Class对象获取注解对象
        Book book = (Book) m.getAnnotation(Book.class);
        // 输出book注解属性值
        System.out.println("书名:" + book.value());
        System.out.println("价格:" + book.price());
        System.out.println("作者:" + Arrays.toString(book.authors()));
    }
}

输出结果:
在这里插入图片描述

解析案例存在问题

TestAnnotation类在获取注解数据时处理得不够严谨,假如出现下面的其中一种情况:
1、把BookStore类或成员方法buyBook上的注解删除。
2、将Book注解的有效范围改为: CLASS。
再运行TestAnnotation类代码则会出现空指针异常,如下图所示:
在这里插入图片描述

原因分析

类上没有使用注解
在这里插入图片描述
或者 将注解生命周期Runtime改为CLASS
在这里插入图片描述
就会出现空指针异常
在这里插入图片描述

解决方案

在获取注解对象时,先判断是否有使用注解,如果有才获取,否则就不用获取。
修改TestAnnotation类的代码。修改后如下

public class TestAnnotation {
    public static void main(String[] args)  throws Exception{
        System.out.println("---------获取类上注解的数据----------");
        test01();
        System.out.println("---------获取成员方法上注解的数据----------");
        test02();
    }
    
    /**
     * 获取BookStore类上使用的Book注解数据
     */
    public static void test01(){
        // 获得BookStore类对应的Class对象
        Class c = BookStore.class;
        // 判断BookStore类是否使用了Book注解
        if(c.isAnnotationPresent(Book.class)) {
            // 根据注解Class对象获取注解对象
            Book book = (Book) c.getAnnotation(Book.class);
            // 输出book注解属性值
            System.out.println("书名:" + book.value());
            System.out.println("价格:" + book.price());
            System.out.println("作者:" + Arrays.toString(book.authors()));
        }
    }
    
    /**
     * 获取BookStore类成员方法buyBook使用的Book注解数据
     */
    public static void test02() throws Exception{
        // 获得BookStore类对应的Class对象  Class c = BookStore.class;
        // 获得成员方法buyBook对应的Method对象
        Method m = c.getMethod("buyBook");
        // 判断成员方法buyBook上是否使用了Book注解
        if(m.isAnnotationPresent(Book.class)) {
            // 根据注解Class对象获取注解对象
            Book book = (Book) m.getAnnotation(Book.class);
            // 输出book注解属性值
            System.out.println("书名:" + book.value());
            System.out.println("价格:" + book.price());
            System.out.println("作者:" + Arrays.toString(book.authors()));
        }
    }
}

九、注解案例(模拟Junit)

模拟Junit测试的@Test

案例分析

1、模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
2、然后编写目标类(测试类) ,然后给目标方法(测试方法)使用@MyTest注解,编写三个方法。其中两个加上@MyTest注解。
3、最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。

代码实现1

定义注解MyTest

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) 
public @interface MyTest {

}

定义目标类MyTestDemo

public class MyTestDemo {
    @MyTest
    public  void  test01(){
        System.out.println("test01");
    }

    public  void  test02(){
    	System.out.println("test02");
    }

    @MyTest
    public  void  test03(){
        System.out.println("test03");
    }
}
    

调用类TestMyTest

public class TestMyTest {
    public static void main(String[] args) throws  Exception{
        // 获得MyTestDemo类Class对象
        Class c = MyTestDemo.class;
        // 获得所有的成员方法对象
        Method[] methods = c.getMethods();
        // 创建MyTestDemo类对象
        Object obj = c.newInstance();
        // 遍历数组
        for (Method m:methods) {
            // 判断方法m上是否使用注解MyTest
            if(m.isAnnotationPresent(MyTest.class)){
                // 执行方法m
                m.invoke(obj);
            }
        }
    }
}

代码实现2

自定义注解MyTest

// 模拟Junit测试的@Test
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {

}

定义使用自定义注解的类MyTestTest

public class MyTestTest {

    @MyTest
    public void test01() {
        System.out.println("test01");
    }

    @MyTest
    public void test02() {
        System.out.println("test02");
    }

    @MyTest
    public void test03() {
        System.out.println("test03");
    }
}

测试

public class Demo14 {
    public static void main(String[] args) throws Exception{
        // 获得Class对象
        Class c = MyTestTest.class;
        // 创建对象
        Object obj = c.getConstructor().newInstance();
        // 获得所有成员方法对象
        Method[] methods = c.getMethods();
        // 遍历成员方法对象
        for (Method method : methods) {
            // 判断成员方法上是否使用了MyTest注解
            if(method.isAnnotationPresent(MyTest.class)) {
                // 调用方法
                method.invoke(obj);
            }
        }
    }
}

十、代理模式

生活中的代理模式

假如我们要去买电脑,我们通常需要去找电脑代理商购买,电脑代理商去电脑工厂提货,电脑代理商可以赚取中间的差价。假如我们想买火车票,我们可以直接去12306网站买票。可是太难抢到票了,于是我们去找火车票的黄牛,让黄牛帮我们去12306买票,黄牛买到票再加价卖给我们,赚取中间的差价。

你买电脑  ->     电脑代理商   ->       电脑工厂 
你买火车票 ->    黄牛        ->    12306
调用者        代理对象         真正干活的目标对象

我们发现代理对象和真正干活的目标都具有相同的功能(卖电脑/卖票) ,代理可以在中间赚取差价(增强功能) 。

代理模式的作用

代理对象可以在调用者和目标对象之间起到中介的作用。代理对象可以对目标对象的功能进行增强。

理解:
拦截对真实对象的直接访问,在不修改真实对象代码的情况下对真实对象的功能进行增强。

代理模式涉及到四个要素

1、调用者:你。
2、代理对象:联想电脑代理商/黄牛。
3、目标对象:电脑工厂/12306。
4、抽象对象:代理对象和目标对象都共有的接口,保证代理对象和真实对象都有相应的方法,如电脑代理商和电脑工厂都需要有卖电脑的功能。
在这里插入图片描述

代理模式分类

静态代理

由程序员创建代理类,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

理解:
在程序运行之前创建代理类,并和程序一起编译

动态代理

在程序运行过程中,动态的创建代理类然后根据代理类创建代理对象。

十一、动态代理

在程序运行的过程中,动态创建出代理对象。

1、动态代理类相应的API

创建代理对象(Proxy类,newProxyInstance方法)

public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
    作用:创建代理对象
    loader:类加载器对象,传递真实对象(目标类)的类加载器
    interfaces:传递的是真实对象(目标类)实现的接口,是Class类型的数组
    h:传递实现InvocationHandler接口的类对象(代理类对象)。创建代理类对象的构造方法参数为真实对象
    返回值:创建好的代理对象

增强真实方法的方法(InvocationHandler接口,invoke方法)

代理类实现了InvocationHandler接口,在代理类内重写invoke方法

Object invoke(Object proxy, Method method, Object[] args)
    每当通过代理对象调用目标对象的方法时,目标对象方法都会被该方法拦截
    proxy:代理对象本身
    method:代理对象调用的目标对象方法(目标方法)
    args:代理对象调用目标对象方法时传递的参数
	返回值:是真实对象方法的返回值。

2、类加载器(newProxyInstance方法的一个参数)

类加载器是一个负责加载类的对象
作用:将类的字节码文件从硬盘加载到内存中并创建类的Class对象。

类加载器的分类

引导类加载器:BootstrapClassLoader 负责加载核心包的类,比如rt.jar包中的类…
扩展类加载器:ExtClassLoader 负责加载扩展包中的类
应用类加载器:AppClassLoader 负责加载自定义的类,比如Student

JVM中的类的加载器主要有三种:启动类加载器,拓展类加载器,应用类加载器。
1、启动类加载器(Bootstrap classLoader)
又称为引导类加载器,由C++编写,无法通过程序得到。主要负责加载JAVA中的一些核心类库,主要是位于<JAVA_HOME>/lib/rt.jar中。

2、拓展类加载器(Extension classLoader)
主要加载JAVA中的一些拓展类,位于<JAVA_HOME>/lib/ext中,是启动类加载器的子类。

3、应用类加载器(System classLoader)
又称为系统类加载器,主要用于加载CLASSPATH路径下我们自己写的类,是拓展类加载器的子类。

Object类是由哪个类加载器加载的?
BootStrap ClassLoader

我们自己写的类是由哪个类加载器加载的?
System ClassLoader

类加载器都是我们Java中的一个类ClassLoader的子类吗?
BootStrap ClassLoader不是的,另外两个是的。

JVM类加载器分类
https://blog.csdn.net/heihei_100/article/details/88363562
Java的类加载器种类
https://www.cnblogs.com/fengbs/p/7595849.html

通过Class对象获得类加载器对象(getClassLoader)

通过Class对象的方法获得,该方法如下:

ClassLoader getClassLoader()
	获得类加载器对象

3、如何创建代理对象

通过Proxy类的静态方法newProxyInstance创建代理对象

4、动态代理模式的开发步骤

1. 要明确要代理的功能(方法)有哪些
2. 将需要代理的方法定义在接口中
3. 真实对象要实现接口:重写接口中的方法
4. 创建真实对象:但不能直接调用真实对象的方法
5. 通过Proxy类的静态方法创建代理对象
	参数1:真实对象的类加载器
	参数2:真实对象的实现的接口
	参数3:传递InvocationHandler接口实现类对象,并重新invoke方法
		invoke方法的参数有如下三个:
			参数1:代理对象本身
			参数2:拦截到的方法
			参数3:代理对象调用方法传递参数
		在invoke方法中根据需求决定是否需要调用真实对象的方法
6. 通过代理对象调用方法:触发invoke方法调用

5、动态代理模式案例1

需求:

使用动态代理模式买电脑

代码实现

抽象对象/接口(自定义接口Providable)
/*
    提供商品的接口
 */ 
public interface Providable {
    // 卖电脑
    public abstract void sellComputer(double price);
    // 维修电脑
    public abstract void repairComputer(double price); 
}
目标对象(自定义类ComputerFactory,实现Providable接口)
/*
    电脑工厂,真正生产电脑的厂商
 */ 
public class ComputerFactory implements Providable {
    @Override
    public void sellComputer(double price) {
        System.out.println("电脑工厂卖出一台电脑,价格: " + price);
    }

    @Override
    public void repairComputer(double price) {
        System.out.println("电脑工厂修好一台电脑,价格: " + price);
    }
}
代理对象(自定义类MyInvocationHandler,实现InvocationHandler接口)

1、自定义成员变量存储目标对象,提供构造方法获得目标对象
2、重写invoke方法,自定义方法体增强真实方法

// InvocationHandler实现类 
class MyInvocationHandler implements InvocationHandler {
    private ComputerFactory computerFactory;

    public MyInvocationHandler(ComputerFactory computerFactory) {
        this.computerFactory = computerFactory;
    }

    // 4. 在InvocationHandler的invoke进行处理
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("sellComputer")) { //对于是要增强的方法进行增强
            // 代理对象处理卖电脑的方法。
            double price = (double) args[0];
            double realPrice = price* 0.75;
            System.out.println("代理商收到:" + price + "元,实际使用" + realPrice + "去电脑工厂买电脑");
            computerFactory.sellComputer(realPrice);
            System.out.println("代理商赚到:" + (price - realPrice) + "元钱");
            return null;
        } else  { // 不需要增强的方法直接调用目标对象的方法
            return method.invoke(computerFactory, args);
        }
    }
}
调用者,动态生成代理对象(测试使用)
public class Demo05 {
    public static void main(String[] args) {
        // 1. 直接创建目标对象
        ComputerFactory computerFactory = new ComputerFactory();
        // 2. 通过Proxy类,创建代理对象
        Providable proxy = (Providable) Proxy.newProxyInstance(
        		//目标对象(目标类)的类加载器
                ComputerFactory.class.getClassLoader(),
                //目标对象(目标类)实现的接口
                new Class[] {Providable.class},
                //创建代理类对象(代理类),传入目标类对象(代理类自定义构造方法接收目标对象)
                new MyInvocationHandler(computerFactory)
        );

        // 3. 代理对象调用代理方法
        proxy.sellComputer(5000);
        proxy.repairComputer(800);
    }
}

运行效果
在这里插入图片描述

6、动态代理案例2(三层结构开发应用)

创建Dao接口

// 用来描述增删改查的功能
public interface Dao<T>{
    // 增
    public void save(T t);
    // 删
    public void delete(T t);
    // 改
    public void update(T t);
    // 查
    public T find(int id);
}

创建实体类Student

public class Student {

}

数据访问层(创建StudentDao类实现Dao接口)

实现Dao接口并重写接口的方法

public class StudentDao implements Dao<Student>{

    @Override
    public void save(Student t) {
        System.out.println("添加学生信息:" + t);
    }

    @Override
    public void delete(Student t) {
        System.out.println("删除学生信息:" + t);
    }

    @Override
    public void update(Student t) {
        System.out.println("更新学生信息:" + t);
    }

    @Override
    public Student find(int id) {
        System.out.println("查询学生信息");
        Student s =  new Student();
        return s;
    }
}

业务层(创建StudentService类)

测试

public class StudentService {
    public static void main(String[] args) {
        // 创建数据访问层对象(真实对象)
        StudentDao stuDao = new StudentDao();
        // 创建学生对象
        Student stu = new Student();

        // 创建代理对象:通过代理对象调用方法
        /**
         Proxy类的静态方法newProxyInstance内部执行了如下操作:
                1. 动态创建代理类
                    class XxxProxy implement Dao{
                         // 成员变量
                         private InvocationHandler handler;

                         // 增
                         public void save(T t){
                            Method m = this.getClass().getMethod("save");
                            handler.invoke(this,m,t);
                         }
                         ....
                    }

               2. 根据代理类创建代理对象
                    Dao proxy = new XxxProxy();

         */
        Dao proxy = (Dao) Proxy.newProxyInstance(
                stuDao.getClass().getClassLoader(), // 真实对象的加载器
                new Class[]{ Dao.class}, // 真实对象实现的接口
                //匿名内部类实现InvocationHandler接口
                new InvocationHandler() {
                    /**
                     * 保存日志信息
                     */
                    public void log(){
                        System.out.println("保存日志信息...");
                    }
                    /**
                     * 检查是否有权限
                     * 返回true,代表有权限
                     * 返回false,代表没有权限
                     */
                    private boolean check(){
                        System.out.println("检查是否有权限...");
                        return true;
                    }

                    /* 每当通过代理对象调用方法时都会被该方法拦截:拦截代理对象方法的调用
                            proxy:代理对象本身
                            method:代理对象的调用的方法(拦截到的方法)
                            args:代理对象调用方法传递的参数
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // System.out.println("进来了吗:" + method.getName());
                        // 检查是否有权限
                        if (check()) {
                            // 有权限调用真实对象的方法
                            Object result = method.invoke(stuDao,args);
                            // 记录日志
                            log();
                            return result;
                        }
                        return null;
                    }
                });

        // 对学生进行增删改查
        proxy.save(stu);
        proxy.find(1);
        proxy.delete(stu);
        proxy.update(stu);
    }
}

三层结构开发与正常动态代理相比,忽略了Invocation接口的实现类(代理对象的类),用匿名内部类实现这个接口

动态代理模式小结

1、什么是动态代理:
在程序运行的过程中,动态创建出代理对象。

2、动态代理使用到的API
Proxy类,newProxyInstance方法

newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h)

InvocationHandler接口,invoke方法

invoke(Object proxy, Method method,Object] args)

3、Proxy.newProxylnstance()方法的本质
创建了代理对象,代理对象实现了传入的接口。

4、InvocationHandler接口的作用
调用代理对象的方法就会执行到InvocationHandler接口的invoke方法。

5、动态代理使用步骤

  • 1 直接创建真实对象
  • 2 通过Proxy类,创建代理对象
  • 3 调用代理方法
  • 4 在InvocationHandler的invoke进行处理

6、代理模式的好处在不改变目标类的代码情况下,代理对象可以对目标对象的功能进行增强。

十二、静态代理

由程序员创建代理类,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

实现静态代理示例

抽象对象/接口(自定义接口Providable)

/*
    提供商品的接口
 */ 
public interface Providable {
    // 卖电脑
    public abstract void sellComputer(double price);
    // 维修电脑
    public abstract void repairComputer(double price); 
}

目标对象(自定义类ComputerFactory实现Providable接口)

/*
    电脑工厂,真正生产电脑的厂商
 */ 
public class ComputerFactory implements Providable {
    @Override
    public void sellComputer(double price) {
        System.out.println("电脑工厂卖出一台电脑,价格: " + price);
    }

    @Override
    public void repairComputer(double price) {
        System.out.println("电脑工厂修好一台电脑,价格: " + price);
    }
}

代理对象(自定义类ComputerProxy实现InvocationHandler接口)

自定义成员变量存储目标对象,提供构造方法获得目标对象
重写invoke方法,自定义方法体增强方法

// InvocationHandler实现类 
class ComputerProxy implements InvocationHandler {
    private ComputerFactory computerFactory;

    public MyInvocationHandler(ComputerFactory computerFactory) {
        this.computerFactory = computerFactory;
    }

    // 4. 在InvocationHandler的invoke进行处理
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("sellComputer")) { //对于是要增强的方法进行增强
            // 代理对象处理卖电脑的方法。
            double price = (double) args[0];
            double realPrice = price* 0.75;
            System.out.println("代理商收到:" + price + "元,实际使用" + realPrice + "去电脑工厂买电脑");
            computerFactory.sellComputer(realPrice);
            System.out.println("代理商赚到:" + (price - realPrice) + "元钱");
            return null;
        } else  { // 不需要增强的方法直接调用目标对象的方法
            return method.invoke(computerFactory, args);
        }
    }
}

调用者,动态生成代理对象(测试使用)

public class Demo05 {
    public static void main(String[] args) {
        // 1. 直接创建真实对象
        ComputerFactory computerFactory = new ComputerFactory();
        /* 动态代理
        // 2. 通过Proxy类,创建代理对象
        Providable proxy = (Providable) Proxy.newProxyInstance(
                ComputerFactory.class.getClassLoader(),
                new Class[] {Providable.class},
                new MyInvocationHandler(computerFactory)
        );

        // 3. 代理对象调用代理方法
        proxy.sellComputer(5000);
        proxy.repairComputer(800);
        */
        
        //静态代理
        // 2. 创建代理对象
		ComputerProxy proxy = new ComputerProxy(computerFactory);
		// 3.调用代理对象的⽅方法
		proxy.sellComputer(8000);
		proxy.repairComputer(1200);
    }
}

静态代理模式的优缺点

优点:不需要修改目标对象就实现了目标对象功能的增加
缺点:
1、一个真实对象必须对应一个代理对象,如果大量使用会导致类的急剧膨胀。
2、如果抽象对象中方法很多,则代理对象也要编写大量的代码。

小结

动态代理使用是通过Proxy类静态方法创建动态代理对象(多态创建目标对象实现的接口对象),再用接口对象调用目标类方法
静态代理使用是直接创建自定义代理类的对象,再用代理类对象调用目标方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值