java高级之单元测试、反射

1、Junit测试工具

@Test定义测试方法
1.被@BeforeClass标记的方法,执行在所有方法之前
2.被@AfterCalss标记的方法,执行在所有方法之后
3.被@Before标记的方法,执行在每一个@Test方法之前
4.被@After标记的方法,执行在每一个@Test方法之后

public class StringUtilTest{
    @Before
    public void test1(){
        System.out.println("--> test1 Before 执行了");
    }
    @BeforeClass
    public static void test11(){
        System.out.println("--> test11 BeforeClass 执行了");
    }
    @After
    public void test2(){
        System.out.println("--> test2 After 执行了");
    }
    @AfterCalss
    public static void test22(){
        System.out.println("--> test22 AfterCalss 执行了");
    }
}

2、反射

定义:反射技术,指的是加载类的字节码到内存,并以编程的方法解刨出类中的各个成分(成员变量、方法、构造器等)

2.1、获得class对象(字节码对象)

由于Java的设计原则是万物皆对象,获取到的类其实也是以对象的形式体现的,叫字节码对象,用Class类来表示。获取到字节码对象之后,再通过字节码对象就可以获取到类的组成成分了,这些组成成分其实也是对象,其中每一个成员变量用Field类的对象来表示每一个成员方法用Method类的对象来表示每一个构造器用Constructor类的对象来表示

获取Class对象的三种方式

  • Class c1=类名.class
  • 调用Class提供方法:public static Class forName(String package);
  • Object提供的方法: public Class getClass();Class c3=对象getClass();

2.2 获取类的构造器

Class提供了从类中获取构造器的方法
方法
Constructor[] getConstructors() 获取全部构造器(只能获取public修饰的)
Constructor[] getDeclaredConstructors() 获取全部构造器(只要存在就能拿到)
Constructor getConstructor(Class... parameterTypes) 获取某个构造器(只能获取public修饰的)
Constructor getDeclaredConstructor(Class... parameterTypes) 获取某个构造器(只要存在就能拿到)

get:获取
Declared: 有这个单词表示可以获取任意一个,没有这个单词表示只能获取一个public修饰的
Constructor: 构造方法的意思
后缀s: 表示可以获取多个,没有后缀s只能获取一个

2.3获取构造器的作用

获取类构造器的作用: 依然是初始化对象返回
Constructor提供的方法
T newInstance(Object... initargs)(可以是有的任意参数数量)调用此构造器对象表示的构遣器,并传入参数,完成对象的初始化并返回

public void  setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)

2.4 反射获取成员变量&使用

Class提供了从类中获取成员变量的方法
方法
public Field[] getFields() 获取类的全部成员变量(只能获取public修饰的)
public Field[] getDeclaredFields() 获取类的全部成员变量(只要存在就能拿到)
public Field getField(String name) 获取类的某个成员变量(只能获取public修饰的)
public Field getDeclaredField(String name) 获取类的某个成员变量(只要存在就能拿到)
获取到成员变量的作用: 依然是赋值、取值。
方法
void set(Object obj,object value): 赋值(需要传入对象,不然不知道为哪个对象赋值)
object get(Object obj) 取值
public void  setAccessible(boolean flag)  设置为true,表示禁止检查访问控制 (暴力反射)

2.5成员方法的获取和取值

获取类的成员方法
Class提供了从类中获取成员方法的API。
方法
Method[] getMethods() 获取类的全部成员方法(只能获取public修饰的)
Method[] getDeclaredMethods() 获取类的全部成员方法(只要存在就能拿到)
Method getMethod(String name, Class... parameterTypes) 获取类的某个成员方法(只能获取public修饰的)
Method getDeclaredMethod(String name, Class.. parameterTypes) 获取类的某个成员方法(只要存在就能拿到)

成员方法的作用: 依然是执行
Method提供的方法
public Object invoke(Object obj,Object... args) 触发某个对象的该方法执行。
public void setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)

public class Test3Method{
public static void main(String[] args){
//1、反射第一步:先获取到Class对象
Class c = Cat.class;

    //2、获取类中的全部成员方法
    Method[] methods = c.getDecalaredMethods();
    
    //3、遍历这个数组中的每一个方法对象
    for(Method method : methods){
        System.out.println(method.getName()+"-->"+method.getParameterCount()+"-->"+method.getReturnType());
    }
    
    System.out.println("-----------------------");
    //4、获取private修饰的run方法,得到Method对象
    Method run = c.getDecalaredMethod("run");
    //执行run方法,在执行前需要取消权限检查
    Cat cat = new Cat();
    run.setAccessible(true);
    Object rs1 = run.invoke(cat);
    System.out.println(rs1)
    
    //5、获取private 修饰的eat(String name)方法,得到Method对象
    Method eat = c.getDeclaredMethod("eat",String.class);
    eat.setAccessible(true);
    Object rs2 = eat.invoke(cat,"鱼儿");
    System.out.println(rs2)
}

}

获取运行返回结果
java高级之单元测试、反射_第1张图片

2.6 反射的作用

反射使用来写框架的,也就是不管输入的什么类型的对象,我们都可以获取对应的class文件去做一些统一的处理。下面让我们写一个框架,能够将任意一个对象的属性名和属性值写到文件中去。不管这个对象有多少个属性,也不管这个对象的属性名是否相同。

1.先写好两个类,一个Student类和Teacher2.写一个ObjectFrame类代表框本架
	在ObjectFrame类中定义一个saveObject(Object obj)方法,用于将任意对象存到文件中去
	参数:Object obj: 就表示要存入文件中的对象
	
3.编写方法内部的代码,往文件中存储对象的属性名和属性值
	1)参数obj对象中有哪些属性,属性名是什么实现值是什么,中有对象自己最清楚。
	2)接着就通过反射获取类的成员变量信息了(变量名、变量值)
	3)把变量名和变量值写到文件中去

写一个ObjectFrame表示自己设计的框架,代码如下图所示

public class ObjectFrame{
    public static void saveObject(Object obj) throws Exception{
        PrintStream ps = 
            new PrintStream(new FileOutputStream("模块名\\src\\data.txt",true));
        //1)参数obj对象中有哪些属性,属性名是什么实现值是什么,中有对象自己最清楚。
		//2)接着就通过反射获取类的成员变量信息了(变量名、变量值)
        Class c = obj.getClass(); //获取字节码
        ps.println("---------"+class.getSimpleName()+"---------");
        
        Field[] fields = c.getDeclaredFields(); //获取所有成员变量
		//3)把变量名和变量值写到文件中去
        for(Field field : fields){
            String name = field.getName();
            Object value = field.get(obj)+"";
            ps.println(name);
        }
        ps.close();
    }
}

使用自己设计的框架,往文件中写入Student对象的信息和Teacher对象的信息。

先准备好Student类和Teacher类

public class Student{
    private String name;
    private int age;
    private char sex;
    private double height;
    private String hobby;
}
public class Teacher{
    private String name;
    private double salary;
}

创建一个测试类,在测试中类创建一个Student对象,创建一个Teacher对象,用ObjectFrame的方法把这两个对象所有的属性名和属性值写到文件中去。

public class Test5Frame{
    @Test
    public void save() throws Exception{
        Student s1 = new Student("黑马吴彦祖",45, '男', 185.3, "篮球,冰球,阅读");
        Teacher s2 = new Teacher("播妞",999.9);
        
        ObjectFrame.save(s1);
        ObjectFrame.save(s2);
    }
}

最终结果是,将不同类的信息保存至文件中

3注解的使用

注解是和反射一起使用的,为了实现框架而服务。我们可以理解为JUnit这个注解一样,告诉系统加了这个注解就会执行,同样也和spring中定义bean的意思差不多,可以通过注解知道系统中有哪些bean。

  • Java注解是代码中的特殊标记,比如@Override、@Test等,作用是:让其他程序根据注解信息决定怎么执行该程序

3.1自定义注解的格式

public @interface MyTest{
    String aaa();
    boolean bbb() default true;	//default true 表示默认值为true,使用时可以不赋值。
    String[] ccc();
}

java高级之单元测试、反射_第2张图片
注意:注解的属性名如何是value的话,并且只有value没有默认值,使用注解时value名称可以省略。比如现在重新定义一个MyTest2注解

public @interface MyTest2{
    String value(); //特殊属性
    
}
@MyTest2("孙悟空") //等价于 @MyTest2(value="孙悟空")

3.2注解本质是什么呢

1.MyTest1注解本质上是接口,每一个注解接口都继承子Annotation接口
2.MyTest1注解中的属性本质上是抽象方法
3.@MyTest1实际上是作为MyTest接口的实现类对象
4.@MyTest1(aaa=“孙悟空”,bbb=false,ccc={“Python”,“前端”,“Java”})里面的属性值,可以通过调用aaa()、bbb()、ccc()方法获取到

3.3元注解

元注解是修饰注解的注解

@Target是用来声明注解只能用在那些位置,比如:类上、方法上、成员变量上等
@Retetion是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期

java高级之单元测试、反射_第3张图片
例如test的@target就是type,@retention就是Runtime

3.4 解析注解

1.如果注解在类上,先获取类的字节码对象,再获取类上的注解
2.如果注解在方法上,先获取方法对象,再获取方法上的注解
3.如果注解在成员变量上,先获取成员变量对象,再获取变量上的注解
总之:注解在谁身上,就先获取谁,再用谁获取谁身上的注解
public class AnnotationTest3{
    @Test
    public void parseClass(){
        //1.先获取Class对象
        Class c = Demo.class;
        
        //2.解析Demo类上的注解
        if(c.isAnnotationPresent(MyTest4.class)){
            //获取类上的MyTest4注解
            MyTest4 myTest4 = (MyTest4)c.getDeclaredAnnotation(MyTest4.class);
            //获取MyTests4注解的属性值
            System.out.println(myTest4.value());
            System.out.println(myTest4.aaa());
            System.out.println(myTest4.bbb());
        }
    }
    
    @Test
    public void parseMethods(){
        //1.先获取Class对象
        Class c = Demo.class;
        
        //2.解析Demo类中test1方法上的注解MyTest4注解
        Method m = c.getDeclaredMethod("test1");
        if(m.isAnnotationPresent(MyTest4.class)){
            //获取方法上的MyTest4注解
            MyTest4 myTest4 = (MyTest4)m.getDeclaredAnnotation(MyTest4.class);
            //获取MyTests4注解的属性值
            System.out.println(myTest4.value());
            System.out.println(myTest4.aaa());
            System.out.println(myTest4.bbb());
        }
    }
}

上图为获取类和方法上的注解。

3.4 注解的应用场景-模拟Junit写一个测试框架

也就是将有注解的方法或者类进行特殊处理

public class AnnotationTest4{
    @MyTest
    public void test1(){
        System.out.println("=====test1====");
    }
    
    @MyTest
    public void test2(){
        System.out.println("=====test2====");
    }
    

    public void test3(){
        System.out.println("=====test2====");
    }
    
    public static void main(String[] args){
        AnnotationTest4 a = new AnnotationTest4();
        
        //1.先获取Class对象
        Class c = AnnotationTest4.class;
        
        //2.解析AnnotationTest4类中所有的方法对象
        Method[] methods = c.getDeclaredMethods();
        for(Method m: methods){
            //3.判断方法上是否有MyTest注解,有就执行该方法
            if(m.isAnnotationPresent(MyTest.class)){
            	m.invoke(a);
        	}
        }
    }
}

4.动态代理

关键点:接口(申明代理的方法,相当于把要做的抽象出来,可以在proxy产生代理对象的时候作为参数2表示代理的样子是什么样子)、工具类生成动态代理对象、Proxy类中的newInstamce产生代理对象、重写invoke方法可以实现回调函数

ProxyUtil工具类,为BigStar对象生成代理对象

public class ProxyUtil {
    public static Star createProxy(BigStar bigStar){
       /* newProxyInstance(ClassLoader loader,
                Class[] interfaces,
                InvocationHandler h)
                参数1:用于指定一个类加载器
                参数2:指定生成的代理长什么样子,也就是有哪些方法
                参数3:用来指定生成的代理对象要干什么事情
                */
        // Star starProxy = ProxyUtil.createProxy(s);
        // starProxy.sing("好日子") starProxy.dance()
        Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
                new Class[]{Star.class}, new InvocationHandler() {
                    @Override // 回调方法
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 代理对象要做的事情,会在这里写代码
                        if(method.getName().equals("sing")){
                            System.out.println("准备话筒,收钱20万");
                        }else if(method.getName().equals("dance")){
                            System.out.println("准备场地,收钱1000万");
                        }
                        return method.invoke(bigStar, args);
                    }
                });
        return starProxy;
    }
}

new proxyInstance(loader,class [] interfaces,invocationhalder):

  • 1、定义类加载器
  • 2、定义代理对象中应该有的方法,可以有多个接口
  • 3、这个是定义代理对象应该做什么

java高级之单元测试、反射_第4张图片

public class ProxyUtil {
    public static UserService createProxy(UserService userService){
        UserService userServiceProxy
            = (UserService) Proxy.newProxyInstance(
            ProxyUtil.class.getClassLoader(),
            new Class[]{UserService.class}, 
            new InvocationHandler() {
                                                                            			@Override
            public Object invoke(                                                                             Object proxy, 
                              Method method, 
                                  Object[] args) throws Throwable {                             if(
                    method.getName().equals("login") ||                                             method.getName().equals("deleteUsers")||
                    method.getName().equals("selectUsers")){
                    //方法运行前记录毫秒值         
                    long startTime = System.currentTimeMillis();
                    //执行方法
                    Object rs = method.invoke(userService, args);
                    //执行方法后记录毫秒值
                    long endTime = System.currentTimeMillis();

                    System.out.println(method.getName() + "方法执行耗时:" + (endTime - startTime)/ 1000.0 + "s");
                    return rs;
               }else {
                    Object rs = method.invoke(userService, args);
                    return rs;                                                                }
           }                                                                 });
        //返回代理对象
        return userServiceProxy;
    }
}

AOP的底层也是动态代理实现,别的地方使用到的是mybatis中的延迟加载就是使用CGLIB动态代理,来实现需要用到这个函数时,采取执行搜索保存操作。

你可能感兴趣的:(java,单元测试,开发语言)