单元测试、反射、注解、动态代理

单元测试、反射、注解、动态代理

一、单元测试

1.1 概述

什么是单元测试?

  • 单元测试就是针对最小的功能单元编码测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法的正确性

为什么要用到单元测试?我们之前的测试方法存在什么问题呢?

  • 只有一个main方法,如果一个方法测试失败了,其他方法测试都会受到影响。
  • 无法得到测试的结果报告,需要程序员自己去观察测试是否成功
  • 无法实现自动化测试

1.2 JUnit单元测试框架

什么是JUnit?

  • JUnit是使用Java语言实现的单元测试框架,它是开源的,Java开发者都应该学习并使用JUnit编写单元测试
  • 几乎所有的IDE工具都集成了JUnit,这样我们就可直接在IDE中编写运行JUnit测试,JUnit目前最新版本是5。
  • JUnit用来测试类中方法的正确性

JUnit优点有哪些?

  • JUnit可以灵活的选择执行哪些测试方法,可一键执行全部测试方法
  • JUnit可生产全部方法的测试报告
  • 单元测试中某个方法测试失败不会影响其他测试方法的测试

1.3 JUnit框架使用

  • 将JUnit的jar包导入项目中
    • IDEA通常整合了JUnit框架,一般不需导入(在联网条件下输入@Test,然后ALT+回车选择下载对应版本的JUnit)
    • 如果没有整合,需手动导入JUnit的两个jar包到模块(hamcrest-core-1.3.jar和junit-4.12.jar)
  • 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法
  • 测试方法上使用@Test注解:标注该方法是一个测试方法
  • 测试方法中完成被测试方法的预期正确性测试(断言),可以使用Assert.assertEquals方法(三个参数分别为:非预期结果打印,预期结果值,被测试方法的返回值)
  • 选中测试方法,选择“JUnit运行”,如果测试良好则是绿色,测试失败为红色;还可以选择测试类或者模块右击启动测试全部方法
public class method {

    //被测试的方法
    public String log_in(String user, String password) {
        if ("admin".equals(user) && "123456".equals(password)) {
            return "登录成功";
        }
        return "登录失败";
    }

    //被测试的方法
    public void selectUser() {
        System.out.println(10 / 0);
        System.out.println("用户存在");
    }
}


public class TestMethod {
    /**
     测试方法
     注意点:
     1、必须是公开的,无参数 无返回值的方法
     2、测试方法必须使用@Test注解标记。
     */
    //被测试类的实例
    method method = new method();

    @Test
    public void testLog_in(){
        String rs = method.log_in("admin", "123456");

        // 进行预期结果的正确性测试:断言。
        Assert.assertEquals("登录业务可能出现问题","登录成功",rs);
    }

    @Test
    public void testSelectUser(){
        method.selectUser();//java.lang.ArithmeticException: / by zero
    }
}

在这里插入图片描述

1.4 JUnit常用注解

JUnit4和JUnit5只是注解名字变了,作用没变

JUnit 4.xxxx版本

注解说明
@Test测试方法
@Before用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次
@After用来修饰实例方法,该方法会在每个测试方法执行后执行一次
@BeforClass用来修饰静态方法,该方法会在所有测试方法之前只执行一次
@AfterClass用来修饰静态方法,刚方法会在所有测试方法之后只执行一次

用处:

  • 开始执行的方法:初始化资源
  • 执行完后的方法:释放资源

JUnit 5.xxxx版本

注解说明
@Test测试方法
@BeforeEach用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次
@AfterEach用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次
@BeforAll用来修饰静态方法,该方法会在所有测试方法之前只执行一次
@AfterAll用来修饰静态方法,该方法会在所有测试方法之后只执行一次
   //因为我下载的JUnit 4版本所有使用4版本注解
	@Before
    public void befor() {
        System.out.println("我是befor注解,我在实例方法每次执行之前执行一次");
    }

    @After
    public void after(){
        System.out.println("我是after注解,我在实例方法每次执行之后执行一次");
    }

    @BeforeClass
    public static void beforClass(){
        System.out.println("我是beforClass注解,我修饰静态方法,在所有测试方法之前只执行一次");
    }

    @AfterClass
    public static void afterClass(){
        System.out.println("我是afterClass注解,我修饰静态方法,在所有测试方法结束后执行一次");
    }

在这里插入图片描述

二、反射

反射就是通过一个类名(字符串)对类进行加载,获取类的信息,并对该类创建对象调用法等操作。

2.1 概述

什么是反射?

  • 反射就是指可以对于任何Class类,在运行时可直接得到这个类的全部成分
  • 运行时,可直接得到这个类的构造器对象:Constructor
  • 运行时,可直接得到这个类的成员变量对象:Field
  • 运行时,可直接得到这个类的成员方法对象:Method
  • 这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制

注意: 反射的关键:第一步都是先得到编译后的Class类对象,然后就可以得到这个Class中的全部成分

例如:HelloWorld.java ->通过javac编译 -> HelloWorld.class字节码文件
Class c = HelloWorld.class;

个人理解:为什么叫反射?

例如之前我们创建对象、给对象属性赋值、使用实例方法,都是用 对象 = new 构造方法()、对象.属性=值、对象.方法调用。而反射就是根据类对象来进行这些操作,反射创建对象格式为 构造方法.newInstance(构造方法的参数)来创建对象,相当于在之前的格式上倒过来根据构造方法创建对象。

2.2 反射获取类对象

反射的第一步:获取Class类对象

类对象获取有三种方法:

  • 源代码阶段:Class类中的静态方法forName获取
    • forName(“全路径的类名”)
  • Class对象阶段:类名.class
  • Runtime运行阶段:对象.getClass()

在这里插入图片描述

public class Student {

}

public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一种方法,使用class类的静态方法forName获取类对象
        Class s1 = Class.forName("reflect.Demo2.Student");
        System.out.println(s1);//class reflect.Demo2.Student

        //第二种方法,使用类名.class获取类对象
        Class<Student> s2 = Student.class;
        System.out.println(s2);//class reflect.Demo2.Student

        //第三种方法,使用对象.getClass()获得类对象
        Student student = new Student();
        Class s3 = student.getClass();
        System.out.println(s3);
    }
}

2.3 反射获取构造器对象

反射第一步:获取Class类对象

然后根据类对象获取构造器对象并使用。

作用:获取构造器对象后可以使用构造器创建对象

在这里插入图片描述

Class类中获取构造器的方法:

方法说明
Constructor<?>[] getConstructors()返回所有构造器对象的数组(只能获取public修饰的构造方法)
Constructor<?>[] getDeclaredConstructors()返回所有构造器对象的数组,存在就能拿到
Constructor getConstructor(Class<?>…parameterTypes)返回单个构造器对象(只能拿public修饰的构造方法)
Constructor getDeclaredConstructor(Class<?>…parameterTypes)返回单个构造器对象,存在就可以拿到

根据构造器创建对象

符号说明
T newInstance(Object…initargs)根据指定的构造器创建对象
public void setAccessible(boolean flag)设置为true,表示取消访问检查,进行暴力反射
public class Demo1 {
    public static void main(String[] args) {
        try {
            //第一步:获得Class类对象
            Class c = Student.class;
            //获取单个构造器(参数为:构造器形参列表的类对象) getDeclaredConstructor
            Constructor dc1= c.getDeclaredConstructor();//无参构造
            Constructor dc2= c.getDeclaredConstructor(String.class,String.class,int.class,String.class);//有参构造
            //private reflect.Demo2.Student()==>参数个数:0
            //public reflect.Demo2.Student(java.lang.String,java.lang.String,int,java.lang.String)==>参数个数:4
            //getParameterCount方法:获取当前构造器的参数个数
            System.out.println(dc1 + "==>参数个数:" + dc1.getParameterCount());
            System.out.println(dc2 + "==>参数个数:" + dc2.getParameterCount());


            //获取全部构造器 getDeclaredConstructors
            Constructor[] dcs = c.getDeclaredConstructors();
            for (Constructor dc : dcs) {
                System.out.println(dc + "==>参数个数:" + dc.getParameterCount());
                //private reflect.Demo2.Student()==>参数个数:0
                //public reflect.Demo2.Student(java.lang.String,java.lang.String,int,java.lang.String)==>参数个数:4
            }

            //当指定的构造器为private修饰时,可以使用暴力反射来打开权限,临时打开使用一次(反射可以破坏封装型,私有的也可执行)
            dc1.setAccessible(true);

            //根据指定构造器创建对象
            Student create1 = (Student) dc1.newInstance();//无参构造创建
            Student create2 = (Student) dc2.newInstance("孙悟空","男",20,"二班");//有参构造创建
            //这里我们重写了toString方法,所以可以直接打印值
            System.out.println(create1);//Student{name='null', sex='null', age=18, classRoom='一班'}
            System.out.println(create2);//Student{name='孙悟空', sex='男', age=20, classRoom='二班'}

        } catch (Exception e) {
            //当没有想要的构造器时报错
            e.printStackTrace();
        }
    }
}



//类
public class Student {

    private String name;
    private String sex;
    private int age = 18;
    private String classRoom = "一班";

    private Student() {
    }

    public Student(String name, String sex, int age, String classRoom) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.classRoom = classRoom;
    }
    
    private void run(String name){
        System.out.println(name + "在跑步");
    }

    public void eat(String name){
        System.out.println(name + "在吃东西");
    }

    public String replaceClassRoom(String classRoom){
        return "替换班级为" + classRoom;
    }

	//此处省略getter和setter方法,以及toSting方法,后面反射获取成员变量对象以及方法对象,也是使用该类操作
}

2.4 反射获取成员变量对象

反射第一步:获取Class类对象

然后根据类对象获取成员变量对象并使用。

作用:获取成员变量对象后可以对某个对象赋值、取值

在这里插入图片描述

方法说明
Field[] getFields()返回所有成员变量对象的数组(只能拿public修饰的成员变量)
Field[] getDeclaredFields()返回所有成员变量对象的数组,存在就能拿到
Field getField(String name)返回单个成员变量对象(只能拿public的)
Filed getDeclaredField(String name)返回单个成员变量对象(存在就能拿到)
赋值、取值方法说明
void set(Object obj,Object obj)赋值
Object get(Object obj)获取值
    public static void main(String[] args) {

        try {
            //第一步:获取类对象
            Class c = Student.class;
            //获取存在的成员变量classRoom的信息,例如:修饰符 数据类型 包名.类名.成员变量名 getDeclaredField
            Field df1 = c.getDeclaredField("classRoom");
            System.out.println(df1);//private java.lang.String reflect.Demo2.Student.classRoom

            //获取所有存在的成员变量信息  getDeclaredFields
            Field[] dfs1 = c.getDeclaredFields();
            for (Field field : dfs1) {
                System.out.println(field);
            }

            //利用反射来获取对象
            Constructor dc1 = c.getDeclaredConstructor();
            dc1.setAccessible(true);
            Student s1 = (Student) dc1.newInstance();

            //当成员变量为private时,可以使用暴力反射,临时取值和赋值
            df1.setAccessible(true);

            //利用反射来获取对象的成员变量值
            System.out.println(df1.get(s1));//一班

            //利用反射来设置成员变量值
            //格式为: 成员变量.set(对象,值)
            df1.set(s1,"三班");//设置s1对象的classRoom属性为三班
            System.out.println(df1.get(s1));//三班

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

2.5 反射获取方法对象

反射第一步:获取Class类对象

然后根据类对象获取方法对象并使用。

作用:获取方法对象后可以调用方法

在这里插入图片描述

方法说明
Method[] getMethods()返回所有成员方法对象的数组(只能拿public的)
Method[] getDeclaredMethods()返回所有成员方法对象的数组,存在就能拿到
Method getMethod(String name,Class<?>…parameterTypes)返回单个成员方法对象(只能拿public的)
Method getDeclaredMethod(String name,Class<?>…parameterTypes)返回单个成员方法对象,存在就能拿到

触发执行的方法

符号说明
Object invoke(Object obj,Object…args)运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(如果没有就不写)
返回值:方法的返回值(㘝没有就不写)
    public static void main(String[] args) {

        try {
            //获取类对象
            Class c = Student.class;

            //获取指定方法(参数为:方法名称,该方法的参数列表类型的类对象)
            Method r1 = c.getDeclaredMethod("run", String.class);
            System.out.println(r1);//private void reflect.Demo2.Student.run(java.lang.String)

            //获取所有方法对象(全部)
            Method[] dm1 = c.getDeclaredMethods();
            for (Method method : dm1) {
                System.out.println(method + "=>参数个数为:" + method.getParameterCount());
            }


            //使用反射来的方法对象
            //如果要执行的方法属于私有方法,则需要暴力反射
            r1.setAccessible(true);

            //这里我们直接new一个对象,就不使用反射来创建对象
            Student s1 = new Student("猪八戒","男",100,"五班");
            //执行方法,格式:方法对象.invoke(对象,传入方法的参数值);
            Object invoke = r1.invoke(s1, s1.getName());//猪八戒在跑步
            //当方法没有返回值时,返回为null
            System.out.println(invoke);//null

        }catch (Exception e){
            e.printStackTrace();
        }
    }

2.6 反射的作用

  • 在运行时得到一个类的全部成分然后操作
  • 可以破坏封装性。
  • 可以破坏泛型的约束性
  • 适合做Java高级框架
  • 基本上主流框架都会基于反射设计一些通用技术功能

·

2.6.1 绕过编译阶段为集合添加数据
  • 反射是作用在运行时的技术,此时集合的泛型将不能产生约束,此时可以为集合存入其他任意类型的元素的
  • 泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段时,其真实类型都是ArrayList,泛型相当于被擦除了,所以Java的泛型属于伪泛型。
        try {
            //此时arr1编译时只能存放Integer类型数据
            ArrayList<Integer> arr1 = new ArrayList<>();
            arr1.add(1);
            arr1.add(2);
            arr1.add(3);
//        arr1.add("中国");//无法直接存储字符串类型
            System.out.println(arr1);//[1, 2, 3]

            //方法一:此时我们如果想要该集合存储任何类型数据,可以使用反射
            //获取类对象
            Class c = arr1.getClass();
            //获得该类的add添加元素方法
            Method add = c.getDeclaredMethod("add", Object.class);

            //使用获得的方法来添加元素到arr1,就可以添加成功
            add.invoke(arr1,"中国");
            add.invoke(arr1,"湖北");
            System.out.println(arr1);//[1, 2, 3, 中国, 湖北]

            //方法二:集合添加任何类型数据还可直接赋值,如下
            //将arr1地址赋值给一个没有指定泛型的集合
            ArrayList arr2 = arr1;
            arr2.add("我是arr2添加的");
            System.out.println(arr1);//[1, 2, 3, 中国, 湖北, 我是arr2添加的]
            //由此可知在编译成Class文件进入运行阶段时,其真实类型都是ArrayList,因为指定泛型的集合可以赋值给没指定泛型集合,属于一个类

        } catch (Exception e) {
            e.printStackTrace();
        }
2.6.2 通用框架的底层原理(了解)

此时给你一个任意类型对象,在不清楚字段的情况下,可以把对象的字段名称和对应的值存储到文本中。

分析:

  • 定义一个方法,可以接收任意类型对象
  • 每次收到一个对象后,需要解析这个对象的全部成员变量名称
  • 这个对象可能是任意的,那么怎么样才能知道这个对象的全部成员变量名称呢
  • 使用反射获取对象的Class类对象,然后获取全部成员变量信息
  • 遍历成员变量信息,然后提取本成员变量在对象中的具体值
public class Demo3 {

    public static void main(String[] args) {
        Student s1= new Student("张三","男",50,"4208812001");
        Student s2=new Student("李四","男",10,"4208812002");
        Teacher t1 = new Teacher("法外狂徒",10000);

        Mybasic.save(s1);
        Mybasic.save(s2);
        Mybasic.save(t1);
    }
}



public class Mybasic {

    //- 定义一个方法,可以接收任意类型对象
    public static void save(Object obj) {
        try (
                //创建打印流写到本地txt文件,必须以追加形式写
                PrintStream ps = new PrintStream(new FileOutputStream("D:\\练习目录\\数据.txt",true));
        ) {
            //一、获得当前对象的类对象
            Class c = obj.getClass();

            //- 每次收到一个对象后,需要解析这个对象的全部成员变量名称
            //- 这个对象可能是任意的,那么怎么样才能知道这个对象的全部成员变量名称呢
            //- 使用反射获取对象的Class类对象,然后获取全部成员变量信息
            Field[] dfs = c.getDeclaredFields();

            //获得当前类对象的类名    c.getSimpleName()获取当前类名   c.getName获取全限名:包名+类名
            ps.println("========" + c.getSimpleName() + "========");
            //- 遍历成员变量信息,然后提取本成员变量在对象中的具体值
            for (Field df : dfs) {
                //因为成员变量是私有,所以需要暴力反射
                df.setAccessible(true);
                String str = df.getName() + "=" + df.get(obj);
                //打印到记事本
                ps.println(str);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}



public class Student {

    private String name ;
    private String sex ;
    private int age ;
    private String idCard ;

    public Student() {
    }

    public Student(String name, String sex, int age, String idCard) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        this.idCard = idCard;
    }
}


public class Teacher {

    private String name;
    private double salary;

    public Teacher() {
    }

    public Teacher(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
}

在这里插入图片描述

2.7 反射练习

此时给定一个类,且类中只定义了一些成员变量,那么该如何给这个类添加getter和setter方法呢?其他方法也是与之类似。

在这里插入图片描述

public class Test1 {

    public static void main(String[] args) throws Exception {

        generate("reflect.Demo6.Student","D:\\IDEA_Projects\\TestModule\\src\\reflect\\Demo6\\Student.java");
        Student s1 = new Student();
        s1.setName("lmz");
        s1.setAge(20);
        s1.setClassRoom("五班");
        System.out.println(s1.toString());
    }

    public static void generate(String className,String path) throws Exception {
        //获得类对象
        Class c = Class.forName(className);
        //先将成员变量和方法都存入可变长字符串中,最后再将写好的字符串使用IO流写成文件覆盖之前的Student文件即可
        StringBuffer sb = new StringBuffer();

        //类文件第一行都是包名,所以先写入包名,全路径包名 package reflect.Demo6;
        sb.append(c.getPackage() + ";\n\n");

        //包名过后就是类名 public class 类名{
        sb.append("public class " + c.getSimpleName() + "{\n");

        //获取所有成员变量,写入字符串  格式: 访问修饰符 数据类型 变量名;
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            //获取成员变量的访问修饰符
            String m = Modifier.toString(field.getModifiers());
            //获取成员变量数据类型
            String t = field.getType().getSimpleName();
            //获取成员变量的变量名
            String n = field.getName().toString();
            sb.append("\t" + m + " " + t + " " + n + ";\n");
        }

        sb.append("\n");

        for (Field field : fields) {
            //获取成员变量的访问修饰符
            String m = Modifier.toString(field.getModifiers());
            //获取成员变量数据类型
            String t = field.getType().getSimpleName();
            //获取成员变量的变量名
            String n = field.getName().toString();


            //编写成员变量的getter和setter方法,
            //getter格式为:public 数据类型 get变量名(数据类型 变量名){}
            //变量名首字母大写(因为可能变量名为一个字母,所以做出判断给出不同的首字母大写变量名)
            String nMethod = null;
            if (n.length() >= 2) {
                nMethod = n.substring(0, 1).toUpperCase() + n.substring(1);
            } else {
                nMethod = n.substring(0, 1);
            }
            sb.append("\tpublic " + t + " get" + nMethod + "() {\n");
            sb.append("\t\treturn " + n + ";\n");
            sb.append("\t}\n");

            //setter格式为:public void set变量名(数据类型 变量名){}
            sb.append("\tpublic void set" + nMethod + "(" + t + " " + n + ") {\n");
            sb.append("\t\tthis." + n + " = " + n + ";\n");
            sb.append("\t}\n");
        }

        //toString方法重写  格式:    @Override
        //                          public String toString() {
        //                              return "Student{" +
        //                                      "name='" + name + '\'' +
        //                                      ", age=" + age +
        //                                      ", classRoom='" + classRoom + '\'' +
        //                                      '}';
        //                          }
        sb.append("\n");
        sb.append("\t@Override\n");
        sb.append("\tpublic String toString() {\n");
        sb.append("\t\treturn \"" + c.getSimpleName() + "{\" + \n");

        //计数,判断当前是不是第一个变量,如果是第一个变量就不需要在前面加,
        int count = 0;
        //遍历变量
        for (Field field : fields) {
            sb.append("\t\t\t\"");
            if (count != 0){
                sb.append(", ");
            }

            //获取成员变量的变量名
            String n = field.getName().toString();
            //获取成员变量数据类型
            String t = field.getType().getName();

            //判断当前数据类型是引用数据类型还是基本数据类型,因为两者打印时不同,
            // 基本数据类型只有int或者char,引用数据类型就是 包名.类名
            if (! t.contains(".")){
                sb.append(n + "=\" + " + n + " + " + "\n");
            }else{
                sb.append(n + "='\" + " + n + " + '\\'' + " + "\n");
            }
            count++;
        }
        sb.append("\t\t\t'}';\n");
        sb.append("\t}\n");

        //类结束
        sb.append("}\n");

		//将sb中存储的所有内容写成文件,替换指定的类
        try(
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
        ){

            bos.write(sb.toString().getBytes());

        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

三、注解

3.1 概述

什么是注解?

  • Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制。
  • Java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。

注解的作用:

  • 对Java中类、方法、成员变量做标记,然后进行特殊处理,至于到底做何种处理由业务需求来决定。
  • 例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当测试方法执行

3.2 自定义注解

自定义注解就是自己做一个注解来使用。

格式如下:

public @interface 注解名称{
    public 属性类型 属性名() default 默认值;
}

特殊属性

  • value属性,如果只有一个value属性的情况下,使用value属性时可以省略value名称不写
  • 但是如果有多个属性,且多个属性没有默认值,那么value名称不能省略
//注解没有约束时,可以标注任何地方
@Books(title = "《javaSE大全》",author ="张三")
public class Annotation {

    //注解
    @Books(title = "《javaSE大全》",author ="张三")
    //当只有一个value属性,可以省略不写value=
    @Book("《我不是药神》")
    public static void main(String[] args) {
        @Books(title = "《javaSE大全》",author ="张三")
        String name;
    }

}
//自定义注解Book
@interface Book {

    public String value();

}

//自定义注解Books
public @interface Books {

    //public @interface 注解名称{
    // public 属性类型 属性名() default 默认值;
    //}

    public String title();
    public double price() default 99.99;
    public String author();

}

3.3 元注解

元注解:就是用来注解 注解的注解

元注解有两个:

  • @Target:约束自定义注解只能在那些地方使用
    • 可使用的值定义在ElementType枚举类中,值如下:
      • TYPE,类,接口
      • FIELD,成员变量
      • METHOD,成员方法
      • PARAMETER,方法参数
      • CONSTRUCTOR,构造器
      • LOCAL_VARIABLE,局部变量
  • @Retention:申明注解的生命周期
    • 可使用的值定义在RetentionPolicy枚举类中,常用值如下:
      • SOURCE:注解只作用与源码阶段,生成的字节码文件中不存在
      • CLASS:注解只作用与源码阶段,字节码文件阶段,运行阶段不存在,默认值
      • RUNTIME:注解作用与源码阶段,字节码文件阶段,运行阶段(开发常用)
//@Book() //该注解不能注解类,因为设置了范围
public class Annotation {

    @Book("我是注解")
    public static void main(String[] args) {
        //@Book("我是注解") //不能注解成员变量,因为有范围
        String name;
    }

}

//设置该注解的作用范围只能是方法
@Target(ElementType.METHOD)
//申明注解的生命周期:一直存在
@Retention(RetentionPolicy.RUNTIME)
@interface Book {
    public String value();
}

3.4 注解解析

注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。

注解相关接口:

  • Annotation:注解的顶级接口,注解都是Annotation类型的对象
  • AnnotatedElement:该接口定义与注解解析相关的解析方法
  • 所有的类成分Class,Method,Field,Constructor,都实现了AnnotatedElement接口他们都拥有解析注解的能力
方法说明
Annotation[] getDeclaredAnnotations()获得当前对象上使用的所有注解,返回注解数组
T getDeclaredAnnotation(Class annotationClass)根据注解类型获得对应注解对象
boolean isAnnotationPresent(Class annotationClass)判断当前对象是否使用了指定的注解,如果使用了返回true,否则false

解析注解的技巧:

  • 注解在那个成分上,我们就先拿那个成分对象
  • 比如注解作用在成员方法上,则就要获得该成员方法对应的Method对象,再来拿上面的注解
  • 比如注解作用在类上,则要该类的Class对象,再来拿上面的注解
  • 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解

3.5 注解解析案例

分析:

  • 定义注解Book,要求如下:
    • 包含属性:String value() 书名
    • 包含属性: double price() 价格,默认值为100
    • 包含属性:String[] authors() 多位作者
    • 限制注解使用的位置上:类和成员方法上
    • 指定注解的有效范围:RUNTIME
  • 定义BookStore类,在类和成员方法上使用Book注解
  • 定义AnnotationDemo01测试类获取Book注解上的数据
public class Annotation {
    //类注解获取
    @Test
    public void classAnnotation() {
        //- 定义AnnotationDemo01测试类获取Book注解上的数据
        //先获取类对象
        Class c = BookStore.class;
        if (c.isAnnotationPresent(Book.class)) {
            //获取类上的所有注解
            java.lang.annotation.Annotation[] das = c.getDeclaredAnnotations();
            for (java.lang.annotation.Annotation da : das) {
                Book b = (Book) da;
                System.out.println("书名:" + b.value() + "价格" + b.price() + "作者:" + Arrays.toString(b.authors()));
                //书名:《天天向上》价格200.0作者:[张三, 李四]
            }
        } else {
            System.out.println("该对象没有使用Book注解");
        }
    }

    //方法注解获取
    @Test
    public void MethodAnnotation() {
        //先获取类对象
        Class c = BookStore.class;

        //想要获取哪个地方注解就要先获取哪个成分对象
        Method[] dms = c.getDeclaredMethods();

        if (c.isAnnotationPresent(Book.class)) {
            //遍历方法,查看每个方法上是否存在Book注解
            for (Method dm : dms) {
                if (dm.isAnnotationPresent(Book.class)) {
                    Book b = (Book) dm.getDeclaredAnnotation(Book.class);
                    System.out.println("书名:" + b.value() + "价格" + b.price() + "作者:" + Arrays.toString(b.authors()));
                    //书名:《天天向下》价格100.0作者:[法外狂徒, 赵六]
                }
            }
        } else {
            System.out.println("该对象没有使用Book注解");
        }
    }
}

@Book(value = "《天天向上》", price = 200, authors = {"张三", "李四"})
class BookStore {
    //该注解不能添加再成员变量,因为约束了范围
    //@Book(value = "《天天向上》",price = 200,authors = {"张三","李四"})
    private String name;

    @Book(value = "《天天向下》", price = 100, authors = {"法外狂徒", "赵六"})
    @Books(value = "《Books》", price = 100, authors = {"法外狂徒", "赵六"})
    public static void show() {

    }

}


//  - 限制注解使用的位置上:类和成员方法上
@Target({ElementType.TYPE,ElementType.METHOD})
//  - 指定注解的有效范围:RUNTIME
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
      - 包含属性:String value() 书名
      - 包含属性: double price()  价格,默认值为100
      - 包含属性:String[] authors() 多位作者
      - 限制注解使用的位置上:类和成员方法上
      - 指定注解的有效范围:RUNTIME
    String value() ;
    double price() default 100;
    String[] authors();
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Books {
    String value() ;
    double price() default 100;
    String[] authors();
}

3.6 注解应用场景:junit框架

需求:定义若干方法,只要在MyTest注解,就可以在启动时被触发执行

分析:

  • 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在
  • 定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有这个注解的方法不能执行

注意:自定义注解无法直接使用右击运行,我们可以创建main方法,然后再main中获得指定注解的方法,然后执行该方法

public class Annotation {

    //因为我们自定义的注解无法直接运行,所以我们可以使用main方法运行方法中加注解的方法
    public static void main(String[] args) {
        //创建一个对象,便于后面使用方法
        Annotation s = new Annotation();
        //获得类对象
        Class c = Annotation.class;
        //获得该类对象的所有方法对象
        Method[] dms = c.getDeclaredMethods();
        //遍历所有方法对象
        for (Method dm : dms) {
            //获得被MyTest注解的方法
            if (dm.isAnnotationPresent(MyTest.class)) {
                try {
                    //执行被MyTest注解的方法
                    dm.invoke(s);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //需求:定义若干方法,只要在MyTest注解,就可以在启动时被触发执行
    @MyTest
    public static void run() {
        System.out.println("跑步");
    }

    public static void eat() {
        System.out.println("吃饭");
    }

    @MyTest
    public static void play() {
        System.out.println("玩电脑");
    }
}



@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {

}

四、动态代理

模拟企业业务功能开发,并完成每个功能的性能统计

需求:模拟某企业用户管理业务,需包含用户登录,用户删除,用户查询功能,并统计每个功能的耗时

分析:

  • 定义一个UserService表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能。
  • 定义一个实现类UserServicelmpl实现UserService,并完成相关功能,且统计每个功能的耗时
  • 定义测试类,创建实现类对象,调用方法
//测试类
public class Test1 {
    public static void main(String[] args) {
        UserServicelmpl userServicelmpl = new UserServicelmpl();

        userServicelmpl.login("admin","123456");
        userServicelmpl.selectUser("用户1");
        userServicelmpl.deleteUser("用户2");
    }
}


public interface UserService {

    //- 定义一个UserService表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能。
    //- 定义一个实现类UserServicelmpl实现UserService,并完成相关功能,且统计每个功能的耗时
    //- 定义测试类,创建实现类对象,调用方法
    String login(String user,String password);
    String selectUser(String user);
    void deleteUser(String user);

}


public class UserServicelmpl implements UserService {
    @Override
    public String login(String user, String password) {

        //计算耗时
        long start = System.currentTimeMillis();
        try {
            Thread.sleep(1000);//这里用于模拟登录时的速度慢一些
            if ("admin".equals(user) && "123456".equals(password)) {
                return "登陆成功";
            }
            return "账号或密码错误";
        } catch (Exception e) {
            e.printStackTrace();
            return "登录错误";
        } finally {
            //将运行的最后时间放进finally可以保证计算时间在return之前且必定执行
            long end = System.currentTimeMillis();
            System.out.println("登录耗时:" + (end - start) * 1000.0 + "s");
        }

    }

    @Override
    public String selectUser(String user) {

        //计算耗时
        long start = System.currentTimeMillis();
        try {
            Thread.sleep(2000);//这里用于模拟查询时的速度慢一些
            System.out.println("查询用户" + user);
            return "查询成功";
        } catch (Exception e) {
            e.printStackTrace();
            return "查询错误";
        } finally {
            //将运行的最后时间放进finally可以保证计算时间在return之前且必定执行
            long end = System.currentTimeMillis();
            System.out.println("查询耗时:" + (end - start) * 1000.0 + "s");
        }
    }

    @Override
    public void deleteUser(String user) {
        //计算耗时
        long start = System.currentTimeMillis();
        try{
            Thread.sleep(3000);//这里用于模拟删除时的速度慢一些
            System.out.println("删除用户成功");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //将运行的最后时间放进finally可以保证计算时间在return之前且必定执行
            long end = System.currentTimeMillis();
            System.out.println("删除用户耗时:" + (end - start) * 1000.0 + "s");
        }
    }
}

4.1 动态代理解决问题

上面案例存在哪些问题?

业务对象的每个方法都要进行性能统计,存在大量重复的代码。

4.1.1 动态代理
  • 代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,动态代理就是用来对业务功能(方法)进行代理的。

关键步骤:

  • 必须有接口,实现类要实现接口(代理通常是基于接口实现的)。
  • 创建一个实现类的对象,该对象为业务对象,紧接着为业务对象做一个代理对象。

在这里插入图片描述

动态代理的优点:

  • 非常灵活,支持任意接口类型的实现类对象做代理,也可以直接为接口本身做代理。
  • 可以为被代理对象的所有方法做代理。
  • 可以在不改变方法源码的情况下,实现对方法功能的增强。
  • 不仅简化了编程工作、提高了软件系统的可扩展性,同时也提高了开发效率。
//测试类
public class Test1 {
    public static void main(String[] args) {
        //使用代理来接收业务对象
        UserService userServicelmpl = ProxyUtil.getProxy(new UserServicelmpl());
        userServicelmpl.login("admin","123456");
        userServicelmpl.selectUser("用户1");
        userServicelmpl.deleteUser("用户2");

        //此时如果还有一个新的业务对象要来计算效率,只需要用代理对象来接收业务对象
        //注意:代理通常基于接口实现
        //使用代理计算耗时
        School four = ProxyUtil.getProxy(new fourSchool());
        four.theClass(20);
        four.numberOf(1000);

    }
}



//代理对象类
public class ProxyUtil {
    //该写法可以代理任何接口的方法
    public static  <T> T getProxy(T obj) {
        // 返回了一个代理对象了
        return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 参数一:代理对象本身。一般不管
                        // 参数二:正在被代理的方法
                        // 参数三:被代理方法,应该传入的参数
                        //将计算方法效率的代码写入代理方法中(提高了扩展性)
                        long start = System.currentTimeMillis();
                        Object invoke = method.invoke(obj,args);
                        long end = System.currentTimeMillis();
                        System.out.println(method.getName() + "耗时:" + (end-start)/1000.0 + "s");
                        return invoke;
                    }
                });
    }

    //这个代理写法只能应用于一个接口
//    public static UserService getProxy(UserService userServicelmpl) {
//        // 返回了一个代理对象了
//        return (UserService) Proxy.newProxyInstance(userServicelmpl.getClass().getClassLoader(), userServicelmpl.getClass().getInterfaces(),
//                new InvocationHandler() {
//                    @Override
//                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//                        // 参数一:代理对象本身。一般不管
//                        // 参数二:正在被代理的方法
//                        // 参数三:被代理方法,应该传入的参数
//                        long start = System.currentTimeMillis();
//                        Object invoke = method.invoke(userServicelmpl,args);
//                        long end = System.currentTimeMillis();
//                        return invoke;
//                    }
//                });
//    }
}



//接口
public interface UserService {

    //- 定义一个UserService表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能。
    //- 定义一个实现类UserServicelmpl实现UserService,并完成相关功能,且统计每个功能的耗时
    //- 定义测试类,创建实现类对象,调用方法
    String login(String user,String password);
    String selectUser(String user);
    void deleteUser(String user);

}

//接口的实现类
public class UserServicelmpl implements UserService {
    @Override
    public String login(String user, String password) {

        try {
            Thread.sleep(1000);//这里用于模拟登录时的速度慢一些
            if ("admin".equals(user) && "123456".equals(password)) {
                return "登陆成功";
            }
            return "账号或密码错误";
        } catch (Exception e) {
            e.printStackTrace();
            return "登录错误";
        }

    }

    @Override
    public String selectUser(String user) {

        try {
            Thread.sleep(2000);//这里用于模拟查询时的速度慢一些
            System.out.println("查询用户" + user);
            return "查询成功";
        } catch (Exception e) {
            e.printStackTrace();
            return "查询错误";
        }
    }

    @Override
    public void deleteUser(String user) {
        try{
            Thread.sleep(3000);//这里用于模拟删除时的速度慢一些
            System.out.println("删除用户成功");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}



//学校接口
public interface School {

    void theClass(int n);
    void numberOf(int n);

}


//学校接口的实现类
public class fourSchool implements School{
    @Override
    public void theClass(int n) {
        //用休眠模拟查询班级的时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("四中一共" + n + "个班级");
    }

    @Override
    public void numberOf(int n) {
        //用休眠模拟查询人数的时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("一共" + n + "个人");
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值