03_JavaWeb||day01_基础加强||day01_Junit测试,反射,注解

第一部分 Junit测试

1.1 测试分类

  1. 黑盒测试:不需要写代码,给输入值,看程序是否能够输出期望的值。
  2. 白盒测试:需要些代码的。关注程序具体的执行流程。

在这里插入图片描述

1.2 Junit使用:白盒测试

  1. 步骤:
    1. 定义一个测试类(测试用例)
      • 建议:
        • 测试类名:被测试的类名Test(如:CalculatorTest)
        • 包名:xxx.xxx.xx.test(如:cn.itcast.test)
    2. 定义测试方法:可以独立运行
      • 建议:
        • 方法名:test测试的方法名(如:testAdd())
        • 返回值:void
        • 参数列表:空参
    3. 给方法加@Test
    4. 导入junit依赖环境
  2. 判断结果:
    • 红色:失败
    • 绿色:成功
    • 一般我们会使用断言操作来处理结果
      • Assert.assertEquals(期望的结果,运算的结果)
    • 注:可以在不同的包中
    //创建一个类:Calculator.java
    public class Calculator {
    	/**
    	 * 加法
    	 * @param a
    	 * @param b
    	 * @return
    	 */
    	public int add(int a, int b) {
    		//int i = 3/0;
    		return a - b;
    	}
    	/**
    	 * 减法
    	 * @param a
    	 * @param b
    	 * @return
    	 */
    	public int sub(int a, int b) {
    		return a - b;
    	}
    }
    //创建一个测试类(可以在不同包中):CalculatorTest.java
    import org.junit.Assert;
    import org.junit.Test;
    import webstudy.day1.junit.Calculator;
    
    public class CalculatorTest {
    
    	/**
    	 * 测试add方法
    	 */
    	@Test
    	public void testAdd() {	//结果为红色
    		//System.out.println("我被执行了");
    		//1. 创建计算器对象
    		Calculator c = new Calculator();
    		//2. 调用add方法
    		int result = c.add(1, 2);
    		//System.out.println(result);
    		
    		//3. 断言	我断言这个结束是3
    		Assert.assertEquals(3, result);
    	}
    	
    	/**
    	 * 测试sub方法
    	 */
    	@Test
    	public void testSub() {	//结果为绿色
    		Calculator c = new Calculator();
    		int result = c.sub(1, 2);
    		Assert.assertEquals(-1, result);
    	}
    }
    
  3. 补充:
    • @Before
      • 修饰的方法会在测试方法之前被自动执行
    • @After
      • 修饰的方法会在测试方法执行之后自动被执行

第二部分 反射:框架设计的灵魂

2.1 预热知识

  1. Java代码在计算机中经历的阶段:三个阶段

2.2 反射

  1. 框架:半成品软件。可以在框架的基础上进行软件开发,简化代码
  2. 反射:将类的各个组成部分封装为其他对象,这就是反射机制
    • 好处:
      1. 可以在程序运行过程中,操作这些对象
      2. 可以解耦,提高程序的可扩展性
  3. 获取Class对象的方式
    1. Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象
    2. 类名.Class:通过类名的属性Class获取
    3. 对象.getClass():getClass()方法在Object类中定义着。
    • 结论:
      1. 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次
      2. 不论通过哪一种方式获取的Class对象都是同一个
  4. Class对象功能[重点]
    1. 获取功能:
      1. 获取成员变量们
        • Field[] getFields():获取所有public修饰的成员变量
        • Field getField(String name):获取指定名称的public修饰的成员变量
        • ===============
        • Field[] getDeclaredFields():获取所有的成员变量不考虑修饰符
        • Field getDeclaredField(String name)
      2. 获取构造方法们
        • Constructor<?>[] getConstructors()
        • Constructor getConstructor<类<?>… parameterTypes)
        • ===============
        • Constructor<?>[] getDeclaredConstructors()
        • Constructor getDeclaredConstructor<类<?>… parameterTypes)
      3. 获取成员方法们
        • Method[] getMethods()
        • Method getMethod(String name, 类<?>… parameterTypes)
        • ===============
        • Method[] getDeclaredMethods()
        • Method getDeclaredMethod(String name, 类<?>… parameterTypes)
      4. 获取类名
        • String getName()
    2. Field:成员变量:Field getField(String name)
      • 操作:
        1. 设置值
          • void set(Object obj, Object value)
        2. 获取值
          • get(Obejct obj)
      • 注意:在有Field getDeclaredField(String name),要去进行访问之前:先进行忽略操作【重点】
        • 访问权限不是public修饰的时候:++忽略访问权限修饰符的安全检查++
        • setAccessible(true)暴力反射【注】
      • 后面只写一些特性,因为一个都雷同
      //创建一个Person类
      public class Person {
      	private String name;
      	private int age;
      	
      	public String a;
      	protected String b;
      	String c;
      	private String d;
      	
      	
      	
      	public Person() {
      		
      	}
      	
      	public Person(String name, int age) {
      		super();
      		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;
      	}
      
      	@Override
      	public String toString() {
      		return "Person [name=" + name + ", age=" + age + ", a=" + a + ", b=" + b + ", c=" + c + ", d=" + d + "]";
      	}
      
      
      	
      	
      
      }
      
      //Class对象功能_获取Field
      public class ReflectDemo1 {
      	public static void main(String[] args) throws Exception {
      		//获取Person的Class对象
      		Class personClass = Person.class;
      		
      		//1. Field[] getFields()
      		Field[] fields = personClass.getFields();
      		for(Field field : fields) {
      			System.out.println(field);
      		}
      		
      		System.out.println("=============");
      		//2. Field getField(String name)
      		Field a = personClass.getField("a");	//会抛出个异常
      		//获取成员变量a的值
      		Person p = new Person();
      		Object value = a.get(p);
      		System.out.println(value);
      		//设置a的值
      		a.set(p, "张三");
      		System.out.println(p);
      		
      		System.out.println("=============");
      		//3. Field[] getDeclaredFields()
      		Field[] declaredFields = personClass.getDeclaredFields();
      		for (Field declaredField : declaredFields) {
      			System.out.println(declaredField);
      		}
      		
      		System.out.println("=============");
      		//4. Field getDeclaredField(String name)
      		Field d = personClass.getDeclaredField("d");
      		//先忽略访问权限修饰符的安全检查
      		d.setAccessible(true);
      		Object value2 = d.get(p);
      		System.out.println(value2);
      	}
      }
      
      //结果:
      public java.lang.String webstudy.day1.reflect.Person.a
      =============
      null
      Person [name=null, age=0, a=张三, b=null, c=null, d=null]
      =============
      private java.lang.String webstudy.day1.reflect.Person.name
      private int webstudy.day1.reflect.Person.age
      public java.lang.String webstudy.day1.reflect.Person.a
      protected java.lang.String webstudy.day1.reflect.Person.b
      java.lang.String webstudy.day1.reflect.Person.c
      private java.lang.String webstudy.day1.reflect.Person.d
      =============
      null
      
    3. Constructor:构造方法:getConstructor<类<?>… parameterTypes)
      • 创建对象[重点]
        • T newInstance(Object… initargs)
        • 如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法[重点]
      • 注意:在有Constructor getDeclaredConstructor<类<?>… parameterTypes),要去进行访问之前:先进行忽略操作【重点】
        • 访问权限不是public修饰的时候:++忽略访问权限修饰符的安全检查++
        • setAccessible(true)暴力反射【注】
      public class ReflectDemo2 {
      	public static void main(String[] args) throws Exception {
      		
              Class personClass = Person.class;
              
              //传参的:Constructor<T> getConstructor<类<?>... parameterTypes)
              
              Constructor constructor = personClass.getConstructor(String.class, int.class);
              
              //创建对象(有参数)
              Object person = constructor.newInstance("张三", 23);
              System.out.println(person);
              
              System.out.println("=============");
              //创建对象(无参数)
              Object person1 = constructor.newInstance();
              System.out.println(person1);
      	}
      }
          
          //结果:
          Person [name=张三, age=23, a=null, b=null, c=null, d=null]
          =============
          Person [name=null, age=0, a=null, b=null, c=null, d=null]
      
    4. Method:方法对象:getMethod(String name, 类<?>… parameterTypes)
      • 执行方法[重点]
        • Object invoke(Object obj, Object… args)
      • 获取方法名称
        • String getName:获取方法名
      • 注意:在有Method getDeclaredMethod(String name, 类<?>… parameterTypes),要去进行访问之前:先进行忽略操作【重点】
        • 访问权限不是public修饰的时候:++忽略访问权限修饰符的安全检查++
        • setAccessible(true)暴力反射【注】
          ///1 现在Person类中创建一个eat方法
          
          ///2 在反射_Class对象功能_获取Method类中
          public class ReflectDemo2 {
          	public static void main(String[] args) throws Exception {
          		
                  Class personClass = Person.class;
                  //获取指定名称的方法
                  Method eat_method = persongClass.getMethod("eat");
                  //创建对象
                  Person p = new Person();
                  //执行方法
                  eat_method.invoke(p);
          	}
          }
          
          
          //结果:
          eat...
      
  5. 反射案例:
    • 需求:写一个“框架”,不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
      • 实现:
        1. 配置文件
        2. 反射
      • 步骤:
        1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
        2. 在程序中加载读取配置文件
        3. 使用反射技术来加载文件进内存
        4. 创建对象
        5. 执行方法
    ///先创建两个测试类(在domain包下):Person和Student
    public class Person {
    	
    	public void eat() {
    		System.out.println("eat...");
    	}
    }
    public class Student {
    	public void sleep() {
    		System.out.println("sleep...");
    	}
    }
    
    ///创建一个配置文件pro.properties(文件file)
    className=webstudy.day1.domain.Person
    methodName=eat
    
    ///创建反射测试类(在reflect包下):ReflectTest.java
    
    import java.io.InputStream;
    import java.lang.reflect.Method;
    import java.util.Properties;
    
    /**
     * 应用反射-》模拟框架类
     * @author Administrator
     *
     */
    public class ReflectTest {
    	public static void main(String[] args) throws Exception {
    		//可以创建任意类的对象,可以执行任意的方法
    		/*
    		 * 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
    		 */
    		
    		//1.加载配置文件
    		//1.1创建Properties对象
    		Properties pro = new Properties();
    		//1.2加载配置文件,转换为一个集合
    		//1.2.1获取class目录下的配置文件
    		ClassLoader classLoader = ReflectTest.class.getClassLoader();//获取到该字节码文件对应的类加载器(用该类加载器加载进内存的)
    		/*classLoader可以找到类路径下的文件,也可以找到src下的配置文件
    		*	getResource();	//用来获取资源的路径
    		*	getResourceAsStream();	//用来获取资源对应的字节流
    		*/
    		InputStream is = classLoader.getResourceAsStream("pro.properties");
    		pro.load(is);
    		
    		//2. 获取配置文件中定义的数据
    		String className = pro.getProperty("className");
    		String methodName = pro.getProperty("methodName");
    		
    		//3. 加载该类进内存(返回一个Class对象)
    		Class cls = Class.forName(className);
    		
    		//4. 创建对象
    		Object obj = cls.newInstance();	//使用空参数构造方法创建Class对象的简化写法
    		
    		//5. 获取对象的方法
    		Method method = cls.getMethod(methodName);
    		
    		//6. 执行方法
    		method.invoke(obj);
    		
    	}
    }
    
    //结果:
        eat...
    //这时候只需要改动一下配置文件中类名和方法名
    className=webstudy.day1.domain.Student
    methodName=sleep
    //结果:
        sleep...
    
  6. 改代码和改配置文件的区别【重点】
    • 如果将来写的系统非常的庞大
      1. 改java代码就需要从新测试,从新编译,从新上线。
      2. 改配置文件:就只是一个物理文件,改完就没事了,而且使程序的扩展性更强。
  7. 配置文件中遇到了全类名就要知道使用了反射机制
        className=webstudy.day1.domain.Student
    
  8. 个人总结
    • 我总结,,反射就是捕获到一个类中,,所有的成员变量,构造方法或者成员方法,,然后获取方法会分成获取public和所有的,,然后就一个注意点是:获取所有的时候要忽略修饰符权限,,,所在在该操作(方法名中有Declared的)之前:先用了一个方法,,setAccessible(true):进行忽略访问权限修饰符的安全检查也叫作:暴力反射

第三部分 注解(Annotation),也叫元数据

3.1 概念:

  • 注解:说明程序的,给计算机看的
  • 注释:用文字描述程序的,给程序员看的
  • 概念描述
    • 注解是JDK1.5之后的新特性
    • 说明程序的
    • 使用注解:@注解名称

3.2 作用分类(了解)

  1. 编写文档:通过代码里标识的注解(元数据)生成文档【生成doc文档(也就是JDK1.8版本的API文档)】
    • 其实doc文档的生成:是抽取代码中的文档注释自动生成的
    • 演示:
      1. 创建一个类:AnnotationDemo1.java
          package webstudy.day1.Annotation;
          /**
           * 注解演示
           * 
           * @author Administrator
           * @version 1.1
           * @since 1.5
           *
           */
          public class AnnotationDemo1 {
          	/**
          	 * 计算两数之和
          	 * @param a
          	 * @param b
          	 * @return 两数之和
          	 */
          	public int add(int a, int b) {
          		return a + b;
          	}
          }
      
      1. 桌面创建一个文件夹
      2. 将创建类复制进来(为了方便抽取,将类中包路径删除)
      3. 在文件夹下打开cmd窗口
      4. 输入:javadoc AnnotationDemo1.java
      5. 就会生成一堆HTMl文档,,其中index.html的就是我们熟悉的API文档


  2. 代码分析:通过代码里标识的注解对代码进行方法【使用反射】
  3. 编译检查:通过代码里标识的注解让编译器能过实现基本的编译检查【Override】
  • 总结:注解主要被用于:doc文档的生成(++编写文档和编译检查++)–>这两个基本上都是JDK预定义好的(不能进行操作或者修改
    • 后期程序员主要使用和学习的主要是:++代码分析++(使用反射技术来抽取注解)(能进行操作或者修改

3.3 JDK中预定义的一些注释(重点)

* @Override:检查被该注解标注的方法是否是继承自父类(接口)的
* @Deprecated:将该注解标注的内容,表示已过时(但是还可以使用)
* @SuppressWarnings:压制警告(编译器IDE会提示一些警告,当个人不想要这些警告的时候使用该注解)
    * 该注释一般写到:==类的头部==
    * @SuppressWarnings("all"):表示压制所有警告(一般传递all)
    /*
     * 三个常用注解的学习
     * @Override:检查被该注解标注的方法是否是继承自父类(接口)的
     * @Deprecated:将该注解标注的内容,表示已过时
     * @SuppressWarnings:压制警告(编译器IDE会提示一些警告,当个人不想要这些警告的时候使用该注解)
     */
    
    @SuppressWarnings("all")
    public class AnnotationDemo2 {
    	//@Override:检查被该注解标注的方法是否是继承自父类(接口)的
    	@Override
    	public String toString() {
    		return super.toString();
    	}
    
    	//@Deprecated:将该注解标注的内容,表示已过时
    	@Deprecated
    	public void show1() {
    		//有缺陷,又写了一个show1方法
    		//但是不能删掉,因为用户的软件可能还用的是show1方法
    	}
    	
    	public void show2() {
    		//替代了show2方法,以后推荐用户使用show2方法
    	}
    	
    	public void demo() {
    		show1();
    	}
    }

3.4 自定义注解_格式&本质&属性(难点)

  1. 注解的格式
        元注解
        public @interface 注解名称{
            属性列表;(其实就是成员方法)
        }
    
    • 注:++元注解,稍后讲解++
  2. 注解的本质:(通过反编译自己写的注解代码来生成如下代码)
    • public interface MyAnno extends java.lang.annotation.Annotation{}
    • 所以:注解本质上就是一个接口,该接口默认继承Annotation接口
      • 继承的Annotation接口是:所有注解类型扩展的公共接口
  3. 注解的属性:就是接口中的抽象方法
    • 解释一下就是:(接口中可以定义的内容(常量,方法等))
    • 两点要求如下:
    1. 属性的返回值类型有下列取值(其他的比如类,等都不行)
      • 基本数据类型
      • String
      • 枚举
      • 注解
      • 以上类型的数组
    2. 定义了属性,在使用时需要给属性赋值
      1. 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。
      2. 如果只有一个属性需要赋值并且属性的名称是value,则value可以省略,直接定义值即可。
            //自己定义的注解MyAnno
            public @Interface MyAnno{
                int value();
            }
            
            //使用该注解的测试类
            public class Test {
                @MyAnno(12)
                public void show(){
            
                }
            }
            
            //如果不是value的时候
            public class Test {
                @MyAnno(age = 12)
                public void show(){
            
                }
            }
        
        • 比如注解:@SuppressWarnings(“all”),因为省略了所以肯定是一个value
      3. 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
            //自己定义的注解MyAnno
            public @Interface MyAnno{
                String[] value();
            }
            
            //使用该注解的测试类
            public class Test {
                @MyAnno(strs={"aaa", "bbb"})
                public void show(){
            
                }
            }
            
            //如果数组中只有一个值的时候
            public class Test {
                @MyAnno(strs="aaa")
                public void show(){
            
                }
            }
        
        • 比如注解:@SuppressWarnings(“all”)就是一个字符串数组

3.5 元注解:++用于描述注解的注解++

* ==【常用】@Target==:描述注解能够作用的位置
    * ElementType的取值如下:
	* TYPE:可以作用于类上
	* METHOD:可以作用于方法上
	* FIELD:可以作用于成员变量上
	```
	    //创建一个注解:MyAnno2
	    import java.lang.annotation.ElementType;
        import java.lang.annotation.Target;
        
        @Target(value = {ElementType.TYPE}) //表示MyAnno2注解只能作用于类上
        public @interface MyAnno2 {
        
        }
        
        //测试该注解的类:Test
        @MyAnno2
        public class Test {
        
            //@MyAnno2->这里就报错了,因为只能作用于类上
            public String name = "aaa";
            //@MyAnno2->这里就报错了,因为只能作用于类上
            public void show(){
        
            }
        }
	```
* ==【常用】@Retention==:描述注解被保留的阶段
	* @Retention(RetentionPolicy.RUNTIME):当前被描述的注解,会保留到class字节码文件中,并被JVM读取到
	    * 还可以是CLASS和SOURCE-->但是==一般都用RUNTIME==
	    * CLASS:就不会被JVM读取到
	    * SOURCE:不会被JVM读取到,也不会保留到class字节码文件中
* ==@Documented==:描述注解是否被抽取到api文档中
* ==@Inherited==:描述注解是否被子类继承
  • 注:如何进行反编译
    1. 将注解代码放到一个后缀名为.java
    2. 运行cmd
    3. javac Anno.java先进行编译(编译后会生成class文件)
    4. javap Anno.java来进行反编译

3.6 在程序中使用(解析)注解的三个步骤:获取注解中定义的属性值

  1. 获取注解定义位置的对象(Class,Method,Field)
  2. 获取指定的注解
    • getAnnotation(Class)
      //其实就是在内存中生成了一个该注解接口的子类实现对象
    public class ProImpl implements Pro{
        public String className(){
            return "cn.javaweb.day01.annotation.Demo1";
        }
        public String methodName(){
            return "show";
        }
    }
    
  3. 调用注解中的抽象方法获取配置的属性值
  • 反射案例用注解方式改写:(解析注解的案例)
    • 和反射案例一样,只不过这里不需要写配置文件来定义呢两个属性了
    • 而是用注解来描述呢两个属性
        ///先创建两个测试类:Demo1和Demo2
        public class Demo1 {
            public void show1(){
                System.out.println("demo1...show");
            }
        }
        public class Demo2 {
            public void show2(){
                System.out.println("demo2...show");
            }
        }
        
        ///创建注解:Pro
        /*
         * 描述需要执行的类名和方法名
         */
        @Target({ElementType.TYPE}) //只能作用在类上
        @Retention(RetentionPolicy.RUNTIME) //保留在RUNTIME阶段
        public @interface Pro {
            String className();
            String methodName();
        }
        
        ///创建案例类:ReflectTest.java
        import java.lang.reflect.Method;
        
        /**
         * 应用注解来描述配置文件中的属性(替换)-》模拟框架类
         */
        @Pro(className = "cn.javaweb.day01.annotation.Demo1", methodName = "show1")
        public class ReflectTest {
            public static void main(String[] args) throws Exception {
                /*
                 * 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
                 */
        
                //1. 解析注解:
                //1.1 获取该类的字节码文件对象
                Class<ReflectTest> reflectTestClass = ReflectTest.class;
                //2. 获取上边的注解对象
                /*
                getAnnotation方法其实相当于如下代码
                    public class ProImpl implements Pro{
                        public String className(){
                            return "cn.javaweb.day01.annotation.Demo1";
                        }
                        public String methodName(){
                            return "show1";
                        }
                    }
                 */
                //其实就是在内存中生成了一个该注解接口的子类实现对象
                Pro an = reflectTestClass.getAnnotation(Pro.class);
                //3. 调用注解对象中定义的属性(抽象方法),获取返回值
                String className = an.className();
                String methodName = an.methodName();
                System.out.println(className);  //cn.javaweb.day01.annotation.Demo1
                System.out.println(methodName); //show1
        
        
                //拿到了属性值后,后面的代码就一样了(复制就行)
                //加载该类进内存(返回一个Class对象)
                Class cls = Class.forName(className);
        
                //创建对象
                Object obj = cls.newInstance();	//使用空参数构造方法创建Class对象的简化写法
        
                //获取对象的方法
                Method method = cls.getMethod(methodName);
        
                //6. 执行方法
                method.invoke(obj);
            }
        }
    

3.7 小结:

  1. 以后大多数时候,我们会使用注解,而不是自定义注解
  2. 注解给谁用?
    1. 编译器(编译器识别注解,检测编译器是否有问题)
    2. 给解析程序用
  3. 注解不是程序的一部分,++可以理解为注解就是一个标签++
  4. 注解后期大多数都是用来:替换配置文件的
    • 将配置文件的操作交给注解来简化我们的代码
发布了42 篇原创文章 · 获赞 6 · 访问量 1119

猜你喜欢

转载自blog.csdn.net/qq_40572023/article/details/105268536