JAVA_TWENTY—ONE_单元测试+注解+反射
目录
一、单元测试
概念:
Junit单元测试框架
概念:
优点:
JUnit 4注解说明
JUnit 5注解说明
二、反射
认识反射,获取类
反射概念:
反射获取类的信息
反射获取类的三种方法
获取类的结构
获取类的构造器的方法
newInstance
setAccessible
获取成员变量
获取成员方法
反射的作用和应用场景
三、注解
注解
概念:
自定义注解
原理
元注解
概念:
@Target注解
@Retention注解
注解的解析
概念:
解析注解的常用方法
应用场景
动态代理
如何使用java动态代理
一、单元测试
概念:
就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试
Junit单元测试框架
概念:
可以用来对方法进行测试,第三方公司开源出来的(很多开发工具已经集成了Junit单元测试框架,如 Idea)
优点:
1、可以灵活的编写测试代码,可以针对某个测试方法执行测试,也支持一键完成对全部方法的自动化测试,且一键独立
2、不需要程序员去分析测试的结果会自动生成测试报告
JUnit 4注解说明
注解 | 说明 |
---|---|
@Test: | 标识一条测试用例。 (A) (expected=XXEception.class) (B) (timeout=xxx) |
@Ignore: | 忽略的测试用例。 |
@Before: | 每一个测试方法之前运行。 |
@After : | 每一个测试方法之后运行。 |
@BefreClass | 所有测试开始之前运行。 |
@AfterClass | 所有测试结果之后运行。 |
JUnit 5注解说明
注解 | 说明 |
---|---|
@Test: | 标识一条测试用例。 (A) (expected=XXEception.class) (B) (timeout=xxx) |
@Ignore: | 忽略的测试用例。 |
@BeforeEach: | 每一个测试方法之前运行。 |
@AfteEachr : | 每一个测试方法之后运行。 |
@BefreClassAll | 所有测试开始之前运行。 |
@AfterClassAll | 所有测试结果之后运行。 |
-
Junit框架的简单单元测试
public class StringUtil {//求名字长度public static void printString(String name) {if(name==null){System.out.println("name为空,请输入测试值");}else {System.out.println("名字长度:" + name.length());}}/*求字符串的最大索引*/public static int getMaxIndex(String data){if(data==null){return -1;}return data.length()-1;}}
public class TestStringUtil {@BeforeEach // 每一个测试方法之前运行public void test1(){System.out.println("------test1方法执行了-------");}@BeforeAll //每一个测试方法之后运行public static void test3(){System.out.println("------test3方法执行了-------");}@AfterEach // 所有测试开始之前运行,只执行一次public void test2(){System.out.println("------test2方法执行了-------");}@AfterAll // 所有测试结果之后运行,只执行一次public static void test4(){System.out.println("------test4方法执行了-------");}@Test //测试方法public void testPrintString(){StringUtil.printString("13231231");StringUtil.printString("");StringUtil.printString(null);}@Test //测试方法public void TestGetMaxIndex(){int index1=StringUtil.getMaxIndex("abcde");System.out.println(index1);int index2=StringUtil.getMaxIndex(null);System.out.println(index2);//断言机制,测试员可以通过预测方法的结果Assert.assertEquals("方法内部出现Bug",4,index1);}}//输出 /* ------test3方法执行了------- ------test1方法执行了------- 名字长度:8 名字长度:0 name为空,请输入测试值 ------test2方法执行了------- ------test1方法执行了------- 4 -1 ------test2方法执行了------- ------test4方法执行了------- */
二、反射
认识反射,获取类
反射概念:
反射就是:加载类,并允许以编程的方式解剖类中的各种成分(成员变量,方法,构造器)
用于调试器,解释器,对象检查器,类浏览器等应用程序,以及需要访问目标对象的公共成员(基于其运行时类)的对象序列化和JavaBean等服务
反射获取类的信息
1、反射的第一步:加载类,获取类的字节码:Class对象
2、获取类的构造器:Constructor对象
3、获取类的成员变量:Filed对象
4、获取类的成员方法:Method对象
反射获取类的三种方法
方式一: 如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取: String s = “Hello”; Class cls = s.getClass();
方式二: 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取: Class cls = Class.forName(“java.lang.String”);
方式三: 直接通过一个class的静态变量class获取: Class cls = String.class;
获取类的结构
获取类的构造器的方法
Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
public Constructor<?>[] getConstructors():该方法只能获取当前Class所表示类的public修饰的构造器 public Constructor<?>[] getDeclaredConstructors():获取当前Class所表示类的所有的构造器,和访问权限无关
public Constructor<T> getConstructor(Class<?>... parameterTypes) :获取当前Class所表示类中指定的一个public的构造器
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) :获取当前Class所表示类中指定的一个的构造器
newInstance
创建此类
对象表示的类的新实例。 该类实例化为new
带有空参数列表的表达式。 如果尚未初始化,则初始化该类。
setAccessible
方法:public void setAccessible (AccessibleObject[] array, boolean flag) 禁止检查访问权限(暴力反射)
-
获取类的构造器的方法
public class Cat {private String name;private String age;private Cat() {}public Cat(String name, String age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}@Overridepublic String toString() {return "Cat{" +"name='" + name + '\\'' +", age='" + age + '\\'' +'}';} }
import org.junit.jupiter.api.Test; import org.junit.jupiter.params.shadow.com.univocity.parsers.common.processor.ObjectColumnProcessor;import java.lang.reflect.Constructor;/* * 目标:获取类的构造器,并对其进行操作 * */ public class TestClass {//获取构造器@Test //测试方法public void textGetConstructors(){//1.反射第一步,必须先得到这个类的Class对象 // Class c=new Cat().getClass();Class c= Cat.class;//2.获取类的全部构造器,只能获取public构造器 // Constructor[] constructors=c.getConstructors();//可以获取全部的构造器Constructor[] constructors=c.getDeclaredConstructors();//3.遍历数组中的每个构造器对象for (Constructor constructor : constructors) {System.out.println(constructor.getName() + "----->"+ constructor.getParameterCount());}}//对其进行操作@Testpublic void testGetConstructor() throws Exception {//1.反射第一步,必须先得到这个类的Class对象Class c= Cat.class;//2.获取某个构造器 // Constructor constructor=c.getConstructor();//可以不管修饰符权限,获取构造器Constructor constructor=c.getDeclaredConstructor();constructor.setAccessible(true); //禁止检查访问权限Cat o1 = (Cat) constructor.newInstance();System.out.println(o1);//3.获取有参构造器Constructor constructor1 =c.getDeclaredConstructor(String.class, String.class);System.out.println(constructor1.getName() + "----->"+ constructor1.getParameterCount());//获取对象Cat o = (Cat) constructor1.newInstance("小多啦","12"); //进行强转System.out.println(o);}}//输出 /* Cat{name='null', age='null'} org.example.reflect.Cat----->2 Cat{name='小多啦', age='12'} org.example.reflect.Cat----->0 org.example.reflect.Cat----->2 */
获取成员变量
-
获取成员变量
public class Cat {private String name;private String age;public Cat() {}public Cat(String name, String age) {this.name = name;this.age = age;}public void run(){System.out.println("小猫在跑~");}public void eat(String food){System.out.println("小猫在吃"+food);}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}@Overridepublic String toString() {return "Cat{" +"name='" + name + '\\'' +", age='" + age + '\\'' +'}';} }
import org.example.homework.Cat; import org.junit.jupiter.api.Test;import java.lang.reflect.Field;public class TestFiled {@Testpublic void testGetFields() throws NoSuchFieldException, IllegalAccessException {//1.反射第一步,必须得到类的Class对象Class cat = Cat.class;//2.获取类的全部成员变量Field[] fields = cat.getDeclaredFields();//3.遍历这个成员变量数组for (Field field : fields) {System.out.println(field.getName());}//定位某个成员变量Field name = cat.getDeclaredField("name");System.out.println(name.getName()+"------->"+name.getType().getName());//赋值Cat cat1=new Cat();name.setAccessible(true); //禁止访问控制权限name.set(cat1,"猫0");//取值String n =(String)name.get(cat1);System.out.println(n);}}//输出 /* name age name------->java.lang.String 猫0 */
获取成员方法
-
获取成员方法
public class Cat {private String name;private String age;public Cat() {}public Cat(String name, String age) {this.name = name;this.age = age;}public void run(){System.out.println("小猫在跑~");}public void eat(String food){System.out.println("小猫在吃"+food);}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}@Overridepublic String toString() {return "Cat{" +"name='" + name + '\\'' +", age='" + age + '\\'' +'}';} }
import org.example.homework.Cat; import org.junit.jupiter.api.Test;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class TestMethod {@Testpublic void testGetMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {//反射第一步,获得类的对象Class cat = Cat.class;//获取全部的方法Method[] methods = cat.getDeclaredMethods();//开始遍历方法for (Method method : methods) {System.out.println(method.getName()+" "+method.getParameterCount()+" "+method.getReturnType());}//获取指定的方法Method run = cat.getDeclaredMethod("run");System.out.println(run.getName());Method eat = cat.getDeclaredMethod("eat", String.class);System.out.println(eat.getName());//创建class对象Cat cat1=new Cat();run.setAccessible(true);run.invoke(cat1); //调用无参方法eat.invoke(cat1,"猫粮"); //调用有参方法} }//输出 /* getName 0 class java.lang.String run 0 void toString 0 class java.lang.String setName 1 void eat 1 void setAge 1 void getAge 0 class java.lang.String run eat 小猫在跑~ 小猫在吃猫粮 */
反射的作用和应用场景
作用:
可以得到一个类的全部成分然后进行操作
可以破坏封装性
最重要的功能:适合做Java的框架,基本上,主流的框架都会基于发射设计出一些通用的功能
-
框架的简单应用
public class Cat {private String name;private String age;public Cat() {}public Cat(String name, String age) {this.name = name;this.age = age;}public void run(){System.out.println("小猫在跑~");}public void eat(String food){System.out.println("小猫在吃"+food);}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getAge() {return age;}public void setAge(String age) {this.age = age;}@Overridepublic String toString() {return "Cat{" +"name='" + name + '\\'' +", age='" + age + '\\'' +'}';} }
public class Dog {private String name;private int age;public Dog() {}public Dog(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;} }
import java.io.FileOutputStream; import java.io.PrintStream; import java.lang.reflect.Field;public class ObjectFrame {public static void saveObject(Object object) throws Exception {//获取打印流PrintStream Ps = new PrintStream(new FileOutputStream("opp_Junit/src/test/java/org/example/homework/writer.txt",true), true);//获取Object的Class的对象Class aClass = object.getClass();//获取类名,并打印Ps.println("------------"+aClass.getSimpleName()+"------------");//获取全部实例变量Field[] Fields = aClass.getDeclaredFields();//开始遍历实例变量for (Field field : Fields) {field.setAccessible(true); //禁止访问权限String name = field.getName();System.out.println(name);Object value = field.get(object);System.out.println(value);//打印name+valuePs.println(name+":"+value);}Ps.close();} }
public class Test {public static void main(String[] args) throws Exception{Cat cat = new Cat("咪咪", "1");Dog dog = new Dog("旺财", 3);//调用框架ObjectFrame.saveObject(cat);ObjectFrame.saveObject(dog);} }
writer.txt
-----------Cat------------
name:咪咪
age:1
-----------Dog------------
name:旺财
age:3
三、注解
注解
概念:
就是Java代码里的特殊标记,比如:@Override @Test等,作用是让其他程序根据注解信息来决定怎么执行程序
自定义注解
public @interface 注解名称{
public 属性类型 属性名() default 默认值;
}
原理
本质上是一个接口,Java中的所有注解都是继承了Annotation接口的
元注解
概念:
指的是:修饰注解的注解
@Target注解
声明注解属性
public enum ElementType {
TYPE, // 类、接口、枚举类FIELD, // 成员变量(包括:枚举常量)METHOD, // 成员方法PARAMETER, // 方法参数CONSTRUCTOR, // 构造方法LOCAL_VARIABLE, // 局部变量ANNOTATION_TYPE, // 注解类PACKAGE, // 可用于修饰:包TYPE_PARAMETER, // 类型参数,JDK 1.8 新增TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
}
@Retention注解
声明注解保留周期
public enum RetentionPolicy {
SOURCE, // 源文件保留
CLASS, // 编译期保留,默认值
RUNTIME // 运行期保留,可通过反射去获取注解信息
}
注解的解析
概念:
就是判断类上、方法上、成员变量上是否存在注解,并把注解的内容解析出来
解析注解的常用方法
通过反射来获得注解,先得到class对象
Class cls = Student. Class;方法一: Annotation[] annotations = cls.getAnnotations()
作用: 获取所有注解方法二: Annotation annotation = cls.getAnnotation(MyAnnotation.class)
作用:获MyAnnotation注解,类型是Annotation方法三: Field field = cls.getDeclaredField(“name”)
annotations = field.getAnnotations()
作用: 获取属性上的注解方法四:annotation = field.getAnnotation(MyAnnotation.class)
作用:获取属性上的MyAnnotation注解
annotation = field.getAnnotation(MyAnnotation.class);方法五: Method method = cls.getDeclaredMethod(“show”, int.class);
annotations = method.getAnnotations()
作用:获取方法上的注解方法六:annotation = method.getAnnotation(MyAnnotation.class)
作用:获取方法上的MyAnnotation注解方法七:annotation instanceof MyAnnotation
作用:判断annotation是否是MyAnnotation类型方法八:MyAnnotation ma=(MyAnnotation) annotation
System.out.println(ma.d());
System.out.println(ma.value());
作用: 获取注解上的属性值方法九:method.isAnnotationPresent(SuppressWarnings.class)
作用: 判断method是否使用了指定的SuppressWarnings注解
应用场景
需求:
定义若干个方法,只要加了MyTest注解,就会触发该方法执行
-
需求
import java.lang.annotation.*; import java.lang.reflect.Method; /* 定义一个注解 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTest{}
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;/** 模拟Junit框架的设计* */public class AnnotationTest {@MyTestpublic void test1(){System.out.println("test1执行了");}// @MyTestpublic void test2(){System.out.println("test2执行了");}@MyTestpublic void test3(){System.out.println("test3执行了");} // @MyTestpublic void test4(){System.out.println("test4执行了");}public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {AnnotationTest a=new AnnotationTest();//获取Class对象Class at = AnnotationTest.class;//获取全部方法Method[] methods = at.getDeclaredMethods();//遍历方法for (Method method : methods) {//判断方法是否存在MyTest注解if(method.isAnnotationPresent(MyTest.class)){method.invoke(a); //方法执行}}} }
动态代理
如何使用java动态代理
创建java动态代理需要使用如下类
java.lang.reflect.Proxy
调用其newProxyInstance方法,例如我们需要为Map创建一个代理:
Map mapProxy = (Map) Proxy.newProxyInstance(HashMap.class.getClassLoader(),new Class[]{Map.class},new InvocationHandler(){...}
);
我们接着就来分析这个方法。先查看其签名:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
ClassLoader类型的loader:被代理的类的加载器,可以认为对应4要素中的被代理的对象。
Class数组的interfaces:被代理的接口,这里其实对应的就是4要素中的被代理的行为,可以注意到,这里需要传入的是接口而不是某个具体的类,因此表示行为。
InvocationHandler接口的h:代理的具体行为,对应的是4要素中的行为的完全控制,当然也是java动态代理的核心。
最后返回的对象Object对应的是4要素中的代理对象。
接着我们来示例用java动态代理来完成记录方法执行时间戳的需求:
首先定义被代理的行为,即接口:
public interface ExecutorInterface {void execute(int x, int y);
}
接着定义被代理的对象,即实现了接口的类:
public class Executor implements ExecutorInterface {public void execute(int x, int y) {if (x == 3) {return;}for (int i = 0; i < 100; i++) {if (y == 5) {return;}}return;}
}
接着是代理的核心,即行为的控制,需要一个实现了InvocationHandler接口的类:
public class TimeLogHandler implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}
}
这个接口中的方法并不复杂,我们还是先分析其签名
Object类型的proxy:最终生成的代理对象
Method类型的method:被代理的方法。这里其实是2个要素的复合,即被代理的对象是如何执行被代理的行为的。因为虽然我们说要对行为完全控制,但大部分时候,我们只是对行为增添一些额外的功能,因此依然是要利用被代理对象原先的执行过程的。
Object数组的args:方法执行的参数
因为我们的目的是要记录方法的执行的时间戳,并且原方法本身还是依然要执行的,所以在TimeLogHandler的构造函数中,将一个原始对象传入,method在调用invoke方法时即可使用。
定义代理的行为如下:
public class TimeLogHandler implements InvocationHandler {private Object target;public TimeLogHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {log.info("start:{}", System.nanoTime());Object result = method.invoke(target, args);log.info("end:{}", System.nanoTime());return result;}
}
接着我们来看Invoker如何使用代理,这里为了方便演示我们是在构造函数中实例化代理对象,在实际使用时可以采用依赖注入或者单例等方式来实例化:
public class Invoker {private ExecutorInterface executor;public Invoker() {executor = (ExecutorInterface) Proxy.newProxyInstance(Executor.class.getClassLoader(),new Class[]{ExecutorInterface.class},new TimeLogHandler(new Executor()));}public void invoke() {executor.execute(1, 2);}
}
此时如果Exector新增了任何方法,那么Invoker和TimeLogHandler将不需要任何改动就可以支持新增方法的的时间戳记录,有兴趣的同学可以自己尝试一下。
另外如果有其他类也需要用到时间戳的记录,那么只需要和Executor一样,通过Proxy.newProxyInstance方法创建即可,而不需要其他的改动了。