JAVA_TWENTY—ONE_单元测试+注解+反射
目录
一、单元测试
概念:
Junit单元测试框架
概念:
优点:
JUnit 4注解说明
JUnit 5注解说明
二、反射
认识反射,获取类
反射概念:
反射获取类的信息
反射获取类的三种方法
获取类的结构
获取类的构造器的方法
newInstance
setAccessible
获取成员变量
获取成员方法
反射的作用和应用场景
三、注解
注解
概念:
自定义注解
原理
元注解
概念:
@Target注解
@Retention注解
注解的解析
概念:
解析注解的常用方法
应用场景
动态代理
如何使用java动态代理
一、单元测试
概念:
就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试
Junit单元测试框架
概念:
可以用来对方法进行测试,第三方公司开源出来的(很多开发工具已经集成了Junit单元测试框架,如 Idea)
优点:
1、可以灵活的编写测试代码,可以针对某个测试方法执行测试,也支持一键完成对全部方法的自动化测试,且一键独立
2、不需要程序员去分析测试的结果会自动生成测试报告
JUnit 4注解说明
JUnit 5注解说明
-
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名字长度:0name为空,请输入测试值------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 getConstructor(Class... parameterTypes) :获取当前Class所表示类中指定的一个public的构造器
public Constructor 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; } @Override public 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()); } } //对其进行操作 @Test public 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----->2Cat{name=\'小多啦\', age=\'12\'}org.example.reflect.Cat----->0org.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; } @Override public 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 { @Test public 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); }}//输出/*nameagename------->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; } @Override public 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 { @Test public 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.Stringrun 0 voidtoString 0 class java.lang.StringsetName 1 voideat 1 voidsetAge 1 voidgetAge 0 class java.lang.Stringruneat小猫在跑~小猫在吃猫粮*/
反射的作用和应用场景
作用:
可以得到一个类的全部成分然后进行操作
可以破坏封装性
最重要的功能:适合做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; } @Override public 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+value Ps.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) annotationSystem.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 { @MyTest public void test1(){ System.out.println(\"test1执行了\"); }// @MyTest public void test2(){ System.out.println(\"test2执行了\"); } @MyTest public void test3(){ System.out.println(\"test3执行了\"); }// @MyTest public 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 { @Override public 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; } @Override public 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方法创建即可,而不需要其他的改动了。