文章目录
- 单元测试
- `Junit单元测试`框架
- 1、认识
- 2、使用步骤
- 1) 先定义一个工具类
- 2)编写测试类
- ⭐测试方法:
- ⭐断言测试:
- 反射
- 1. 认识反射、获取类
- 1.1 获取Class对象
- 2. ⭐获取类中的成分、并对其进行操作
- 2.1 获取构造器并操作
- 2.2 获取类的成员变量并操作
- 2.3 获取类的成员方法并操作
- 3. 作用、应用场景
- 3.1 作用
- 3.2 反射做通用框架
- 1)通用方法:接收任意对象,并存储对象信息
- 2)定义学生类和老师类
- 3)定义测试类:创建对象,并调用存储对象的方法
- 注解
- 1. 概述、自定义注解
- 1.1 注解
- 1.2 自定义注解
- 1)使用
- 2)原理
- 2. 元注解
- 3. 注解的解析
- 3.1 如何解析注解
- 1)注解MyTest4
- 2)Demo类
- 3)定义AnnotationTest3测试类
- 4. 作用、应用场景
- 4.1 作用
- 4.2 应用场景
- 4.3 理解注解的属性
- 动态代理
- 1. 什么是代理
- 1.1 如何为Java对象创建一个代理对象?
- 1) Star类和StarService接口
- 2)代理类
- 2. 解决实际问题、掌握使用代理的好处
- 1)用户类和用户管理类(接口-实现)
- 2)使用代理类,记录方法耗时情况
- 3)使用代理执行方法
单元测试 |
单元测试
- 针对最小的功能单元:方法,编写测试代码对其进行正确性测试
之前在main方法中测试代码,无法实现自动化测试,一个方法测试失败,可能影响其他方法的测试,也无法得到测试的报告
Junit单元测试
框架
1、认识
- 可以用来对
方法
进行测试,是第三方公司开源出来的 - 可以灵活编写测试代码,可以针对某个方法执行测试,也支持一键完成对全部方法的测试
- 独立测试,一个测试不会影响另一个方法的测试
2、使用步骤
需求
- 某个系统,有多个业务方法,请使用Junit单元测试框架,编写测试代码,完成对这些方法的正确性测试
1) 先定义一个工具类
/*** 字符串工具类*/
public class StringUtil {public static void printNumber(String name){if(name == null){System.out.println("名字不能为空");return ;}System.out.println("名字长度是:"+name.length());}/*** 求字符串的最大索引*/public static int getMaxIndex(String data){if(data == null || "".equals(data)){return -1;}return data.length() -1;}
}
2)编写测试类
⭐测试方法:
- 必须是公开public,无参,无返回值
- 测试方法必须加上@Test注解
// 测试类:junit单元测试框架,对业务类中的业务方法进行正确性测试
public class StringUtilTest {@Testpublic void testPrintNumber() {// 测试步骤:StringUtil.printNumber("张无忌"); // 预期结果:3// 测试用例StringUtil.printNumber("");StringUtil.printNumber(null);}@Testpublic void testGetMaxIndex() {// 测试步骤:int index = StringUtil.getMaxIndex("abcdefg"); // 预期结果:6// 测试用例int index2 = StringUtil.getMaxIndex("");int index3 = StringUtil.getMaxIndex(null);System.out.println(index);System.out.println(index2);System.out.println(index3);// 断言测试:断言结果是否与预期结果一致Assert.assertEquals("本轮测试失败业务获取的最大索引有问题,请检查",6, index);Assert.assertEquals("本轮测试失败业务获取的最大索引有问题,请检查",-1, index2);Assert.assertEquals("本轮测试失败业务获取的最大索引有问题,请检查",-1, index3);}
}
⭐断言测试:
断言结果是否与预期结果一致,有时候没有报错,但是可能不是预期的结果 → 黄色警告
反射 |
反射
1. 认识反射、获取类
反射
加载类
,并允许以编程的方式解剖内中的成分(成员变量、方法、构造器等)- 访问有关已加载类的字段、方法和构造函数的信息,以及使用反射字段,方法和构造函数在封装和安全限制内对其底层对应项进行操作
1.1 获取Class对象
- Student类
@Data
public class Student {private String name;private int age;private double score;private String sex;
}
- 测试方法
public static void main(String[] args) throws Exception {// 目标:掌握反射第一步操作:获取Class对象(获取类本身)// 1、获取类本身:类.classClass cls = Student.class;System.out.println(cls); // class com.study.demo2reflect.StudentString className = cls.getSimpleName(); // 获取类简称System.out.println(className); // StudentString fullName = cls.getName();System.out.println(fullName); // 获取类全名 com.study.demo2reflect.Student// 2、获取类本身:Class.forName("类全名")Class cls2 = Class.forName("com.study.demo2reflect.Student");System.out.println(cls2);// 3、获取类本身:对象.getClass()Student s = new Student();Class cls3 = s.getClass();System.out.println(cls3);System.out.println(cls == cls2); // trueSystem.out.println(cls == cls3); // true
}
2. ⭐获取类中的成分、并对其进行操作
💡关系反转
🤔个人理解:之前是拿着类对象去访问该类的成员变量、构造器以及成员方法;反射是获取类中成分后,就是拿着成分去找对象操作自己可以操作的部分
2.1 获取构造器并操作
- Dog类
public class Dog {private String name;private int age;private Dog() {}public Dog(String name, int age) {this.name = name;this.age = age;}public void eat(String food) {System.out.println(this.name + "在吃" + food);}public void run() {System.out.println(this.name + "在跑");}@Overridepublic String toString() {return "Dog{" +"name='" + name + '\'' +", age=" + age +'}';}
}
- 测试方法
// 2、获取类的构造器对象并对其进行操作
@Test
public void getConstructor() throws Exception {// 目标:获取类的构造器对象并对其进行操作// 1、反射第一步:获取Class对象Class cls = Dog.class;// 2、获取构造器对象Constructor[] cons = cls.getDeclaredConstructors(); //只要有就拿,不管权限for(Constructor con:cons){System.out.println(con.getName()+"("+con.getParameterTypes()+")");}// 3、获取单个构造器Constructor con = cls.getDeclaredConstructor(); // 获取无参构造器System.out.println(con.getName()+"("+con.getParameterCount()+")");Constructor con2 = cls.getConstructor(String.class,int.class); // 获取有参构造器System.out.println(con2.getName()+"("+con2.getParameterCount()+")");System.out.println("====================================================");// 4、通过获取的构造器创建对象// 无参构造器是私有方法 → 暴力反射:可以绕过访问权限con.setAccessible(true);Dog d1 = (Dog) con.newInstance();System.out.println(d1.toString());Dog d2 = (Dog) con2.newInstance("旺财", 5);System.out.println(d2.toString());}
2.2 获取类的成员变量并操作
// 3、获取类的成员变量对象并对其进行操作
@Test
public void getFieldInfo() throws Exception {// 1、获取Class对象Class cls = Dog.class;// 2、获取成员变量对象Field[] fields = cls.getDeclaredFields(); // 只要有就拿,不管权限for(Field field:fields){System.out.println(field.getName() + "(" + field.getType().getName() + ")");}System.out.println("========================================");// 3、获取单个成员变量对象Field field = cls.getDeclaredField("name");System.out.println(field.getName() + "(" + field.getType().getName() + ")");// 4、通过获取的成员变量对象操作成员变量Dog d = new Dog("旺财", 5);System.out.println(d);field.setAccessible(true); // 暴力反射:可以绕过访问权限field.set(d, "大黄");System.out.println(d);
}
2.3 获取类的成员方法并操作
// 4、获取类的成员方法对象并对其进行操作
@Test
public void getMethodInfo() throws Exception {// 1、获取Class对象Class cls = Dog.class;// 2、获取成员方法对象Method[] methods = cls.getDeclaredMethods(); // 只要有就拿,不管权限for(Method method:methods){System.out.println(method.getName() + "(" + method.getParameterCount() + ")");}System.out.println("========================================");// 3、获取单个成员方法对象Method eat = cls.getDeclaredMethod("eat", String.class);System.out.println(eat.getName() + "(" + eat.getParameterCount() + ")");Method run = cls.getDeclaredMethod("run");System.out.println(run.getName() + "(" + run.getParameterCount() + ")");// 4、通过获取的成员方法对象操作成员方法Dog d = new Dog("旺财", 5);eat.invoke(d, "骨头");run.invoke(d);
}
3. 作用、应用场景
3.1 作用
- 可以得到一个类的全部成分然后操作
- 可以破坏封装性(私有的那些)(单例不单例,私有不私有)
- 可以绕过泛型的约束
public static void main(String[] args) throws Exception {// 目标:反射的基本作用ArrayList<String> list = new ArrayList<>();list.add("hello");list.add("world");list.add("java");
// list.add(34); // 报错,泛型类型,只能放指定的类型// 通过反射绕过泛型的约束Class cls = list.getClass();Method add = cls.getDeclaredMethod("add",Object.class);add.invoke(list,34);add.invoke(list,true);System.out.println(list); // [hello, world, java, 34, true]}
- 最重要:适合做Java的框架,基本上,主流的框架都会基于反射设计出一些通用的功能
3.2 反射做通用框架
1)通用方法:接收任意对象,并存储对象信息
public class SaveObjectFrameWork {// 保存任意对象的静态方法public static void saveObject(Object obj) throws Exception {// 使用打印流,包装可以追加字节输出流PrintStream ps = new PrintStream(new FileOutputStream("F:\\object.txt",true));// 1. 获取字节码文件对象Class cls = obj.getClass();String className = cls.getSimpleName();ps.println("==============="+className+"=========================");// 2. 获取全部成员变量Field[] fields = cls.getDeclaredFields();for(Field field:fields){// 获取成员变量的名字和值field.setAccessible(true);String fieldName = field.getName();Object fieldValue = field.get(obj) + ""; // 转换为字符串System.out.println(fieldName + "=" + fieldValue);ps.println(fieldName + "=" + fieldValue);}ps.close();}
}
2)定义学生类和老师类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {private String name;private int age;private double score;private String sex;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Teacher {private String name;private int age;private double salary;
}
3)定义测试类:创建对象,并调用存储对象的方法
public class ReflectDemo4 {public static void main(String[] args) {Student s1 = new Student("小明", 14, 90, "男");Teacher t1 = new Teacher("张老师",26, 5000);SaveObjectFrameWork sof = new SaveObjectFrameWork();try {sof.saveObject(s1);sof.saveObject(t1);} catch (Exception e) {e.printStackTrace();}}
}
注解 |
注解
1. 概述、自定义注解
1.1 注解
-
Java代码里的特殊标记,比如:@Override、@Test等
-
让其他程序根据注解信息来决定怎么执行该程序
-
可以用在类上、构造器上、方法上、成员变量上、参数上等
1.2 自定义注解
1)使用
- 自定义注释
public @interface A {String value();// 特殊属性:默认在使用时,如果只有一个value属性,那么value属性可以省略不写
}public @interface B {String value();// 特殊属性:默认在使用时,如果只有一个value属性,那么value属性可以省略不写String hobby() default "打游戏";
}public @interface MyBook {String name();int age() default 18;String[] address();
}
- 使用注释
@MyBook(name = "leo", age = 20, address = {"北京", "上海"})
//@A(value = "leo")
@A("leo") // 如果只有一个value属性,那么value属性可以省略不写
@B("lay")
public class AnnotationDemo1 {@MyBook(name = "lay", age = 28, address = {"北京", "上海"})public static void main(@A("why") String[] args) {// 目标:自定义注解@A("what")int a;}
}
2)原理
2. 元注解
指的是注解注解的注解:放在注解上的注解
@target
:约束注解的范围
Retention
:声明注解的保留周期,一般都是保留到编译运行时RUNTIME
@Retention(RetentionPolicy.RUNTIME) // 注解的保留策略
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD}) // 限制注解的声明位置
public @interface StudentLimit {int minAge() default 18;
}
3. 注解的解析
判断类上、方法上、成员变量上是否存在注解,并把注解里的内容给解析出来
3.1 如何解析注解
- 要解析谁的注解,就应该先拿到谁
- Class、Method、Field、Constructor、都实现了AnnotatedElement接口,他们都拥有解析注解的能力
1)注解MyTest4
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {String value();double aaa() default 100;String[] bbb();
}
2)Demo类
@MyTest4(value = "aaa", aaa = 100, bbb = {"aaa","bbb"})
public class Demo {@MyTest4(value = "bbb",bbb={"aaa","bbb"})public void test1(){}}
3)定义AnnotationTest3测试类
- 解析类的注释
public static void main(String[] args) throws Exception {// 目标:解析注释parseClass();parseMethod();
}public static void parseClass() {// 1. 获取类对象Class cls = Demo.class;// 2. 获取注解对象:先判断是否使用了该注解,再获取注解对象if (cls.isAnnotationPresent(MyTest4.class)) {// 3. 获取注解对象MyTest4 myTest4 = (MyTest4) cls.getAnnotation(MyTest4.class);// 4. 获取注解的属性值String value = myTest4.value();double aaa = myTest4.aaa();String[] bbb = myTest4.bbb();// 5. 打印System.out.println(value); // aaaSystem.out.println(aaa); // 100.0System.out.println(Arrays.toString(bbb)); // [aaa, bbb]}
}
- 解析成员方法的注释
public static void parseMethod() throws Exception {// 1. 获取类对象Class cls = Demo.class;// 2、获取方法对象Method method = cls.getDeclaredMethod("test1");// 3、判断方法是否使用了指定的注解if(method.isAnnotationPresent(MyTest4.class)){// 4、获取注解对象MyTest4 myTest4 = method.getAnnotation(MyTest4.class);// 5、获取注解的属性值String value = myTest4.value();double aaa = myTest4.aaa();String[] bbb = myTest4.bbb();// 6、打印System.out.println(value); // bbbSystem.out.println(aaa); // 100.0System.out.println(Arrays.toString(bbb)); // [aaa, bbb]}
}
4. 作用、应用场景
4.1 作用
- 对程序做特殊标记,以方便别人针对性的处理
4.2 应用场景
需求
- 定义若干个方法,只要加了MyTest注解,就会触发该方法执行
分析
① 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在
② 定义若干个方法,部分方法加上@MyTest
public class AnnotationDemo4 {public static void main(String[] args) throws Exception{AnnotationDemo4 ad = new AnnotationDemo4();// 1.获取类对象Class cls = ad.getClass();// 2.获取存在的方法Method[] methods = cls.getDeclaredMethods();// 3.遍历方法,判断方法上是否使用了指定的注解for(Method method: methods){// 判断方法上是否使用了指定的注解if(method.isAnnotationPresent(MyTest4.class)){// 4.如果有该注解就执行方法method.invoke(ad);}}}@MyTest4(value = "aaa", aaa = 100, bbb = {"aaa","bbb"})public void test1(){System.out.println("test1方法执行啦~");}public void test2(){System.out.println("test2方法执行啦~");}public void test3(){System.out.println("test3方法执行啦~");}@MyTest4(value = "bbb",bbb={"aaa","bbb"})public void test4(){System.out.println("test4方法执行啦~");}
}// test1方法执行啦~
// test4方法执行啦~
4.3 理解注解的属性
对该成分做特殊处理:例如多次执行某一方法
动态代理 |
动态代理
1. 什么是代理
1.1 如何为Java对象创建一个代理对象?
1) Star类和StarService接口
2)代理类
通过需代理任务的接口
连接代理对象
和实际执行任务的对象
- 创建代理对象方法的返回类型:StarService接口对象
/*** 代理工具类:专门负责创建代理对象并返回给别人使用*/
public class ProxyUtil {// 创建一个明星对象的代理对象public static StarService createProxy(Star s) {/*** 参数一:用于执行用哪个类加载器去加载生成的代理类* 参数二:用于指定代理类需要实现的接口* 参数三:用于指定代理对象需要调用哪个类中的方法*/StarService proxy = (StarService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),s.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 用来声明代理对象要干的事// 参数一:proxy接收到代理对象本身(暂时用处不大)// 参数二:method接收到代理对象调用的方法// 参数三:args接收到代理对象调用的方法的参数System.out.println("代理对象开始工作了...");String methodName = method.getName();if("sing".equals(methodName)){System.out.println("准备话筒,收费");} else if ("dance".equals(methodName)) {System.out.println("准备场地,收费");}// 找真正的明星对象来执行被代理的行为:method方法Object result = method.invoke(s, args);return result;}});return proxy;}
}
2. 解决实际问题、掌握使用代理的好处
1)用户类和用户管理类(接口-实现)
/*** 用户业务实现类*/
public class UserServiceImpl implements UserService{@Overridepublic void login(String loginName, String password) throws Exception {
// long start = System.currentTimeMillis(); // 记录开始时间 1970年1月1日0时0分0秒0毫秒 走到此刻的总毫秒值if("admin".equals(loginName) && "123".equals(password)){System.out.println("登录成功");}else{System.out.println("登录失败");}Thread.sleep(1000);// long end = System.currentTimeMillis();
// System.out.println("login方法耗时:"+(end-start)/1000.0+"秒");}@Overridepublic void delete(String id) throws Exception {
// long start = System.currentTimeMillis();System.out.println("删除用户成功");Thread.sleep(1500);// long end = System.currentTimeMillis();
// System.out.println("delete方法耗时:"+(end-start)/1000.0+"秒");}@Overridepublic String[] selectUsers() throws Exception{
// long start = System.currentTimeMillis();System.out.println("查询用户成功");String[] names = {"张三","李四","王五","赵六","孙七"};Thread.sleep(500);// long end = System.currentTimeMillis();
// System.out.println("selectUsers方法耗时:"+(end-start)/1000.0+"秒");return names;}
}
🤔记录方法耗时情况的代码具有很大的重复性,且不太适合归类到用户管理类的方法中
2)使用代理类,记录方法耗时情况
public class ProxyUtil {public static UserService createProxy(UserService userService) {UserService proxy = (UserService) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),userService.getClass().getInterfaces(),new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理对象开始工作了...");long start = System.currentTimeMillis();// 真正调用业务对象被代理的方法Object result = method.invoke(userService, args);long end = System.currentTimeMillis();System.out.println(method.getName() + "耗时:" + (end - start) / 1000.0 + "秒");System.out.println("代理对象结束了工作了...");return result;}});return proxy;}
}
3)使用代理执行方法
public static void main(String[] args) throws Exception {// 1、创建用户业务对象UserService userService = new UserServiceImpl();// 创建代理对象userService = ProxyUtil.createProxy(userService);// UserService userService = ProxyUtil.createProxy(new UserServiceImpl());// 2、调用用户业务的功能userService.login("admin", "123");userService.delete("1");String[] names = userService.selectUsers();System.out.println(Arrays.toString(names));
}
💡如果要让该代理可以接收任意对象,测试任意对象的方法运行耗时,只需将对象限定改为泛型即可