JUnit单元测试、反射、注解、动态代理
一、JUnit单元测试
概念
- 概述 : Junit是Java语言编写的第三方单元测试框架(工具类)
- 作用 : 用来做“单元测试”——针对某个普通方法,可以像main()方法一样独立运行,它专门用于测试某个方法。
使用步骤
-
在模块下创建lib文件夹,把Junit的jar包复制到lib文件夹中
-
选中Junit的jar包,右键选中 add as Library,把JUnit4的jar包添加到classPath中
-
在测试方法上面写上@Test注解
-
执行测试方法
public class Person { @Test public void test1(){ System.out.println("Person test1 方法执行了...."); } @Test public void test2(){ System.out.println("Person test2 方法执行了...."); } }
注意事项
- 测试方法的权限修饰符一定是public
- 测试方法的返回值类型一定是void
- 测试方法一定没有参数
- 测试方法 的声明之上一定要使用**@Test**注解
Junit其他注解
- @Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
- @After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
- @BeforeClass:用来静态修饰方法,该方法会在所有测试方法之前执行一次,而且只执行一次。
- @AfterClass:用来静态修饰方法,该方法会在所有测试方法之后执行一次,而且只执行一次。
Junit断言
断言:预先判断某个条件一定成立,如果条件不成立,则直接报错。 使用Assert类中的assertEquals()方法
public class Demo02 {
@Test
public void addTest(){
//测试
int add = add(3, 6);
//断言判断结果
//第一个参数表示期望值
//第二个参数表示实际值
//如果结果正确的就测试通过,如果结果错误的,就会报错
Assert.assertEquals(9,add);
}
//加法
//这个代码的语法没问题,也没有异常。他是逻辑错误,系统不知道你要算的是加法
public int add(int a, int b){
int sum = a * b;
return sum;
}
}
二、反射
概念: 反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)
**前提:**要获得该类字节码文件对象,就是Class对象
应用:
- 开发IDE(集成开发环境),比如IDEA,Eclipse
- 各种框架的设计和学习 比如Spring,Hibernate,Struct,Mybaits…
1.类加载器
当我们的程序在运行后,第一次使用某个类的时候,会将此类的class文件读取到内存,并将此类的所有信息存储到一个Class对象中
1.1类的加载时机:
- 创建类的实例。
- 类的静态变量,或者为静态变量赋值。
- 类的静态方法。
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
- 初始化某个类的子类。
- 直接使用java.exe命令来运行某个主类。
1.2类加载器:是负责将磁盘上的某个class文件读取到内存并生成Class的对象。
Java中有三种类加载器,它们分别用于加载不同种类的class:
- **启动类加载器(Bootstrap ClassLoader):**用于加载系统类库<JAVA_HOME>\bin目录下的class,例如:rt.jar。
- **扩展类加载器(Extension ClassLoader):**用于加载扩展类库<JAVA_HOME>\lib\ext目录下的class。
- **应用程序类加载器(Application ClassLoader):**用于加载我们自定义类的加载器。
2.Class对象
2.1获取方式
- 通过类名.class获得
- 通过对象名.getClass()方法获得
- 通过Class类的静态方法获得: static Class forName(“类全名”)
2.2常用方法
String getSimpleName() | 获得类名字符串:类名 |
---|---|
String getName() | 获得类全名:包名+类名 |
T newInstance() | 创建Class对象关联类的对象 |
3.反射之操作构造方法
**目的:**获得Constructor对象来创建类的对象。
3.1Constructor类概述:
类中的每一个构造方法都是一个Constructor类的对象
3.2获取类的构造方法
- Constructor getConstructor(Class… parameterTypes)
- 根据参数类型获得对应的Constructor对象。
- 只能获得public修饰的构造方法
- Constructor getDeclaredConstructor(Class… parameterTypes)
- 根据参数类型获得对应的Constructor对象
- 可以是public、protected、(默认)、private修饰符的构造方法。
- 根据参数类型获得对应的Constructor对象
- Constructor[] getConstructors()
- 获得类中的所有构造方法对象,只能获得public的
- Constructor[] getDeclaredConstructors()
- 获得类中的所有构造方法对象
- 可以是public、protected、(默认)、private修饰符的构造方法。
3.3执行构造方法
- T newInstance(Object… initargs) 根据指定的参数创建对象
- void setAccessible(true) 设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消
4.反射之操作成员方法
**目的:**通过反射获取类的成员方法,并执行成员方法
4.1Method类概述
每一个成员方法都是一个Method类的对象。
4.2获取类的成员方法
- Method getMethod(String name,Class…args)
- 根据方法名和参数类型获得对应的构造方法对象,只能获得public的
- Method getDeclaredMethod(String name,Class…args)
- 根据方法名和参数类型获得对应的构造方法对象,包括public、protected、(默认)、private的
- Method[] getMethods()
- 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
- Method[] getDeclaredMethods()
- 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的
4.3执行成员方法
- Object invoke(Object obj, Object… args)
- 调用指定对象obj的该方法
- args:调用方法时传递的参数
- void setAccessible(true)
- 设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消
public class Test {
public static void main(String[] args)throws Exception {
/*
Method类:
public String getName() 获取方法名
*/
// 需求: 访问Student类中的成员
// 1.获取该类的字节码对象
Class<Student> c = Student.class;
// 2.获取所有构造方法
Constructor<?>[] cons = c.getDeclaredConstructors();
// 3.遍历所有的构造方法
Student stu = null;
for (Constructor<?> con : cons) {
stu = (Student) con.newInstance();
}
// 4.获取所有的成员方法
Method[] methods = c.getDeclaredMethods();
// 5.遍历所有的成员方法
for (Method m : methods) {
if (m.getName().equals("show1")){
m.invoke(stu);
}
if (m.getName().equals("show2")){
m.invoke(stu,10);
}
if (m.getName().equals("show5")){
Object res = m.invoke(stu, 20);
System.out.println(res);
}
// ...
}
}
}
5.反射之操作成员变量
**目的:**通过反射获取类的成员变量,并访问成员变量
5.1Field类概述
每一个成员变量都是一个Field类的对象。
5.2获取类的成员变量
- Field getField(String name)
- 根据成员变量名获得对应Field对象,只能获得public修饰
- Field getDeclaredField(String name)
- 根据成员变量名获得对应Field对象,包括public、protected、(默认)、private的
- Field[] getFields()
- 获得所有的成员变量对应的Field对象,只能获得public的
- Field[] getDeclaredFields()
- 获得所有的成员变量对应的Field对象,包括public、protected、(默认)、private的
5.3访问成员变量
-
void set(Object obj, Object value)
-
void setInt(Object obj, int i)
-
void setLong(Object obj, long l)
-
void setBoolean(Object obj, boolean z)
-
void setDouble(Object obj, double d)
-
Object get(Object obj)
-
int getInt(Object obj)
-
long getLong(Object obj)
-
boolean getBoolean(Object ob)
-
double getDouble(Object obj)
**void setAccessible(true):**暴力反射,设置为可以直接访问私有类型的属性。
Class getType(): 获取属性的类型,返回Class对象。
三、注解
注解(annotation),是一种代码级别的说明,和类 接口平级关系.
- 注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无标记,看你的程序有什么标记,就去干相应的事
1.注解的作用
-
生成帮助文档**:**@author和@version
-
执行编译期的检查 例如:@Override·
-
框架的配置(框架=代码+配置)
- 具体使用请关注框架课程的内容的学习。
2.JDK提供的三个基本的注解
-
**@Override:**描述方法的重写
-
**@SuppressWarnings:**压制\忽略警告
-
**@Deprecated:**标记过时
@SuppressWarnings("all") class Fu{ public void show(){ } } class Zi extends Fu{ @Override public void show(){ } } public class Demo { public static void main(String[] args) { /* JDK提供的三个基本的注解: @Override:描述方法的重写. @SuppressWarnings:压制\忽略警告. @Deprecated:标记过时 */ @SuppressWarnings("all") int num; } @Deprecated public static void method1(){ } public static void method2(){ } }
3.自定义注解
3.1语法
public @interface 注解名{
属性
}
3.2注解属性
格式
数据类型 属性名();
属性类型
1.基本类型
2.String
3.Class类型
4.注解类型
5. 枚举类型
6.以上类型的一维数组类型
public @interface Annotation01 {
// 1.基本数据类型(4类8种)
int a();
double b();
// 2.String类型
String c();
// 3.Class类型
Class d();
// 4.注解类型
Annotation02 f();
// 5.枚举类型
Sex e();
// 6.以上类型的一维数组类型
int[] g();
double[] h();
String[] i();
Sex[] j();
Annotation02[] k();
}
3.3.使用注解并给注解属性赋值
注意:带有属性的注解在使用的时候一定要给属性赋值,并且所有属性都要赋值
- 使用注解:
- 如果一个注解中有属性,那么使用注解的时候一定要给注解属性赋值
- 如果一个注解没用属性,那么就不需要给注解属性赋值,直接使用即可
- 如何给注解属性赋值:
- @注解名(属性名=值,属性名2=值2)
3.4给注解属性赋值的注意事项
- 一旦注解有属性了,使用注解的时候,属性必须有值
- 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
- 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
- 注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值;
3.5元注解
**元注解:**定义在注解上的注解
-
**@Target:**表示该注解作用在什么上面(位置),默认注解可以在任何位置. 值为:ElementType的枚举值
- **METHOD:**方法
- **TYPE:**类 接口
- **FIELD:**字段
- **CONSTRUCTOR:**构造方法声明
-
@Retention:定义该注解保留到那个代码阶段, 值为:RetentionPolicy类型,默认只在源码阶段保留
- SOURCE:只在源码上保留(默认)
- CLASS:在源码和字节码上保留
- RUNTIME:在所有的阶段都保留
.java (源码阶段) ----编译—> .class(字节码阶段) ----加载内存–> 运行(RUNTIME)
3.6注解解析
java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了AnnotatedElement
- T getAnnotation(ClassannotationType):得到指定类型的注解引用。没有返回null。
- boolean isAnnotationPresent(Class<?extends Annotation> annotationType):判断指定的注解有没有。
3.7注解的MyTest案例
在一个类(测试类,TestDemo)中有三个方法,其中两个方法上有@MyTest,另一个没有.还有一个主测试类(MainTest)中有一个main方法. 在main方法中,让TestDemo类中含有@MyTest方法执行. 自定义@MyTest, 模拟单元测试.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
public class TestDemo {
@MyTest
public void show1(){
System.out.println("show1方法执行了...");
}
@MyTest
public void show2(){
System.out.println("show2方法执行了...");
}
public void show3(){
System.out.println("show3方法执行了...");
}
}
public class MainTest {
public static void main(String[] args) throws Exception {
// 让第一个类中含有@MyTest注解的方法执行
// 1.获取TestDemo类的字节码对象
Class<TestDemo> clazz = TestDemo.class;
// 2.使用字节码对象获取该类中所有方法对象
Method[] methods = clazz.getDeclaredMethods();
// 3.循环遍历所有方法对象
for (Method method : methods) {
// 4.在循环中,判断遍历出来的方法对象是否含有@MyTest注解
boolean res = method.isAnnotationPresent(MyTest.class);
if (res) {
// 5.如果有,就调用该方法执行
method.invoke(clazz.newInstance());
}
}
}
}
四、动态代理
1.代理模式
被代理者没有能力或者不愿意去完成某件事情,那么就需要找个人代替自己去完成这件事,这个人就是代理者, 所以代理模式包含了3个角色: 被代理角色 代理角色 抽象角色(协议)
- **协议:**被代理者需要代理的方法,就定义在这里,然后让代理者和被代理者去实现
- 被代理者实现: 为了确保和代理者实现的方法一致
- 代理者实现: 为了增强被代理者的这些方法
2.动态代理
-
概述 : 动态代理就是直接通过反射生成一个代理对象,代理对象所属的类是不需要存在的
-
动态代理的获取:
jdk提供一个Proxy类可以直接给实现接口类的对象直接生成代理对象
2.1动态代理相关api介绍
Java.lang.reflect.Proxy类可以直接生成一个代理对象
- **Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)**生成一个代理对象
- 参数1:ClassLoader loader 被代理对象的类加载器
- 参数2:Class<?>[] interfaces 被代理对象的要实现的接口
- 参数3:InvocationHandler h (接口)执行处理类
- 返回值: 代理对象
- 前2个参数是为了帮助在jvm内部生成被代理对象的代理对象,第3个参数,用来监听代理对象调用方法,帮助我们调用方法
- **InvocationHandler中的Object invoke(Object proxy, Method method, Object[] args)**方法:调用代理类的任何方法,此方法都会执行
- 参数1:代理对象(慎用)
- 参数2:当前执行的方法
- 参数3:当前执行的方法运行时传递过来的参数
- 返回值:当前方法执行的返回值
-
3动态代理演示
对Collection接口进行代理,以前的remove(Object obj)方法是删除集合中第一次出现的元素(比如集合中有多个“abc”,调用remove(“abc”)后只会删除一个元素)。代理后,要求在调用remove(Object obj)方法后,能够删除集合中所有匹配的元素。
public static void main(String[] args) {
List<String> names = new ArrayList();
names.add("大明");
names.add("大明");
names.add("二明");
names.add("小明");
System.out.println(names);
names.remove("大明");
System.out.println(names);
names.add("大明");
System.out.println(names);
List<String> proxy = (List<String>) Proxy.newProxyInstance(names.getClass().getClassLoader(), names.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = method.invoke(names, args);
if (method.getName().equals("remove")) {
Iterator<String> it = names.iterator();
while (it.hasNext()) {
String e = it.next();
// 判断集合中的元素是否是要删除的元素
if (e.equals(args[0])) {
it.remove();
}
}
}
return res;
}
});
proxy.remove("大明");
System.out.println(names);
}