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<T> getConstructor(Class<?>... parameterTypes) 获取某个构造器(只能获取public修饰的)
Constructor<T> 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)
}

}

获取运行返回结果

2.6 反射的作用

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

java 复制代码
1.先写好两个类,一个Student类和Teacher类
2.写一个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();
}

注意:注解的属性名如何是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是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期

例如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、这个是定义代理对象应该做什么
复制代码
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动态代理,来实现需要用到这个函数时,采取执行搜索保存操作。

相关推荐
猷咪17 分钟前
C++基础
开发语言·c++
IT·小灰灰19 分钟前
30行PHP,利用硅基流动API,网页客服瞬间上线
开发语言·人工智能·aigc·php
快点好好学习吧20 分钟前
phpize 依赖 php-config 获取 PHP 信息的庖丁解牛
android·开发语言·php
秦老师Q21 分钟前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
烟锁池塘柳021 分钟前
解决Google Scholar “We‘re sorry... but your computer or network may be sending automated queries.”的问题
开发语言
是誰萆微了承諾21 分钟前
php 对接deepseek
android·开发语言·php
vx_BS8133025 分钟前
【直接可用源码免费送】计算机毕业设计精选项目03574基于Python的网上商城管理系统设计与实现:Java/PHP/Python/C#小程序、单片机、成品+文档源码支持定制
java·python·课程设计
2601_9498683625 分钟前
Flutter for OpenHarmony 电子合同签署App实战 - 已签合同实现
java·开发语言·flutter
星火开发设计39 分钟前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
qq_177767371 小时前
React Native鸿蒙跨平台数据使用监控应用技术,通过setInterval每5秒更新一次数据使用情况和套餐使用情况,模拟了真实应用中的数据监控场景
开发语言·前端·javascript·react native·react.js·ecmascript·harmonyos