• 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 执行了");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    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只能获取一个
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.3获取构造器的作用

    获取类构造器的作用: 依然是初始化对象返回
    Constructor提供的方法
    T newInstance(Object... initargs)(可以是有的任意参数数量)调用此构造器对象表示的构遣器,并传入参数,完成对象的初始化并返回
    
    public void  setAccessible(boolean flag) 设置为true,表示禁止检查访问控制(暴力反射)
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

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

    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,表示禁止检查访问控制(暴力反射)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      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)
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

      }

      获取运行返回结果
      在这里插入图片描述

      2.6 反射的作用

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

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

      写一个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();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

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

      先准备好Student类和Teacher类

      public class Student{
          private String name;
          private int age;
          private char sex;
          private double height;
          private String hobby;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      public class Teacher{
          private String name;
          private double salary;
      }
      
      • 1
      • 2
      • 3
      • 4

      创建一个测试类,在测试中类创建一个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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

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

      3注解的使用

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

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

      3.1自定义注解的格式

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

      在这里阿插入图片描述
      注意:注解的属性名如何是value的话,并且只有value没有默认值,使用注解时value名称可以省略。比如现在重新定义一个MyTest2注解

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

      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是用来声明注解保留周期,比如:源代码时期、字节码时期、运行时期
      
      • 1
      • 2

      在这里插入图片描述
      例如test的@target就是type,@retention就是Runtime

      3.4 解析注解

      1.如果注解在类上,先获取类的字节码对象,再获取类上的注解
      2.如果注解在方法上,先获取方法对象,再获取方法上的注解
      3.如果注解在成员变量上,先获取成员变量对象,再获取变量上的注解
      总之:注解在谁身上,就先获取谁,再用谁获取谁身上的注解
      
      • 1
      • 2
      • 3
      • 4
      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());
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34

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

      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);
              	}
              }
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32

      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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29

      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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30

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

    • 相关阅读:
      Dubbo之属性设置。
      QT 自定义信号
      Kotlin 实现自定义下拉阴影弹窗的功能
      springboot实现项目启动前的一些操作
      超详细教程:在Blender中打造毛毡风格角色
      WPF向Avalonia迁移(二、一些可能使用到的库)
      electron-vue 项目启动动态获取配置文件中的后端服务地址
      简单几步让你的远程桌面更安全
      面向机器理解的多视角上下文匹配
      指针和数组笔试题深度解析(下)
    • 原文地址:https://blog.csdn.net/m0_57084845/article/details/134233283