单元测试、反射、注解、动态代理
🏡个人主页 :@ 守夜人st
🚀系列专栏:Java
…持续更新中敬请关注…
🙉博主简介:软件工程专业,在校学生,写博客是为了总结回顾一些所学知识点
目录
- 单元测试、反射、注解、动态代理
- 单元测试
- 单元测试概述
- 单元测试快速入门
- 单元测试常用注解
- 反射
- 反射概述
- 反射获取类对象
- 反射获取成员变量
- 反射获取方法
- 反射的作用——绕过编译阶段为集合添加数据(泛型擦除)
- 反射的作用——通用框架的底层原理
- 注解
- 注解概述
- 自定义注解
- 元注解
- 注解解析
- 注解的应用场景一:JUnit框架
- 动态代理
- 动态代理概述、快速入门
- 动态代理的应用案例:做性能分析,代理的好处
单元测试、反射、注解、动态代理
单元测试
单元测试概述
单元测试就是针对最小的功能单位编写测试代码,Java程序最小功能单元是方法,因此,单元测试就是针对方法的测试,进而检查方法的正确性
我们平常采用的测试存在的弊端:
只有一个main方法,如果一个方法的测试失败了,其他反复测试会受到影响
无法得到测试的结果报告,需要程序员自己去观察测试是否成功
无法实现自动化测试
JUnit单元测试框架:
JUnit是使用Java语言实现的单元测试框架,他是开源的,Java开发者都应当学习并使用JUnit编写单元测试
此外,几乎所有的IDE工具都集成了JUnit,这样我们就直接可以在IDE中编写并运行JUnit单元测试
JUnit优点:
- JUnit可以灵活的选择执行哪些测试方法,可以一键执行全部测试方法
- 可以生成全部方法的测试报告
- 单元测试中的某个方法测试失败了,不影响其他测试方法的测试
单元测试快速入门
需求:使用单元测试进行业务方法预期结果、正确性测试的快速入门
- 将JUnit的jar包导入到项目中
- IDEA通常整合好了JUnit框架,一般不需要导入
- 如果IDEA没有整合好,需要自己手工导入JUnit的jar包到模块
- 编写测试方法:该方法必须是公共的无参数无返回值的非静态方法
- 在测试方法上使用@Test注解:标注该方法是一个测试方法
- 在测试方法中完成被测试方法的预期正确性测试
- 选中测试方法,选择“ JUnit运行 ”,如果测试良好是绿色,测试失败是红色
package com.shouyeren.test;
public class UserService {
public String loginName(String loginName,String passWord){
if ("admin".equals(loginName) && "123456".equals(passWord)){
return "登录成功";
}else {
return "用户名或者密码不正确";
}
}
public void selectNames(){
System.out.println("查询所有用户名成功!");
}
}
package com.shouyeren.test;
import org.junit.Assert;
import org.junit.Test;
public class TestUserService {
@Test
public void testLoginName(){
UserService userService = new UserService();
String rs = userService.loginName("admin","123456");
Assert.assertEquals("您的登录业务功能可能出BUG","登录成功",rs);
}
@Test
public void testSelectNames(){
UserService userService = new UserService();
userService.selectNames();
}
}
单元测试常用注解
注解 | 说明(JUnit4) |
---|---|
@Test | 测试方法 |
@Before | 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次 |
@After | 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次 |
@BeforeClass | 用来修饰静态方法,该方法会在所有测试方法执行之前执行一次 |
@AfterClass | 用来修饰静态方法,该方法会在所有测试方法执行之后执行一次 |
- 开始执行的方法:初始化资源
- 执行完之后的方法:释放资源
反射
反射概述
反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类的全部成分
构造器对象:Constructor
成员变量对象:Field
成员方法对象:Method
这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制
反射的基本作用、关键?
- 反射是在运行时获取类的字节码文件对象,解析获得类中全部成分
- 反射的核心思想和关键就是:得到编译后的Class文件
反射获取类对象
获取Class类对象的三种方式:
- Class c1 = Class.forName(“全类名”);
- Class c2 = 类名.class;
- Class c3 = 对象.getClass();
Class类中用于获取构造器的方法
方法 | 说明 |
---|---|
Constructor<?>[ ] getConstructors() | 返回所有构造器对象的数组(只能拿public的) |
Constructor<?>[ ] getDeclaredConstructors() | 返回所有构造器对象的数组 |
Constructor<?>[ ] getConstructor(Class<?>…parmeterTypes) | 返回单个构造器对象(只能拿public的) |
Constructor<?>[ ] getDeclaredConstructors(getConstructor(Class<?>…parmeterTypes) | 返回单个构造器对象 |
package com.shouyeren.reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class TestStudent {
/**
* 获取类中的全部构造器对象(public)
*/
@Test
public void getConstructors(){
Class<Student> c = Student.class;
Constructor[] constructors = c.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "==>" + constructor.getParameterCount());
}
}
/**
* 获取类中的全部构造器对象
*/
@Test
public void getDeclaredConstructors(){
Class<Student> c = Student.class;
Constructor[] constructors = c.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "==>" + constructor.getParameterCount());
}
}
/**
* 获取某个构造器对象(public)
*/
@Test
public void getConstructor() throws NoSuchMethodException {
Class<Student> c = Student.class;
//可以添加参数
Constructor cons = c.getConstructor(String.class,int.class);
System.out.println(cons.getName() + "==>" + cons.getParameterCount());
}
/**
* 获取某个构造器对象
*/
@Test
public void getDeclaredConstructor() throws NoSuchMethodException {
Class<Student> c = Student.class;
//可以添加参数类型
Constructor cons = c.getDeclaredConstructor();
System.out.println(cons.getName() + "==>" + cons.getParameterCount());
Constructor cons1 = c.getDeclaredConstructor(String.class,int.class);
System.out.println(cons.getName() + "==>" + cons1.getParameterCount());
}
}
package com.shouyeren.reflect;
public class Student {
private String name;
private int age;
private Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
使用反射技术获取构造器对象并使用
- 获取构造器的作用依然是初始化一个对象返回
符号 | 说明 |
---|---|
T newInstance(Object… initargs) | 根据指定的构造器创建对象 |
public void setAccessible(boolean flag) | 设置为true,表示取消访问检查,进行暴力反射 |
package com.shouyeren.reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class TestStudent1 {
@Test
public void getDeclaredConstructor() throws Exception {
Class<Student> c = Student.class;
//可以添加参数类型
Constructor cons = c.getDeclaredConstructor();
System.out.println(cons.getName() + "==>" + cons.getParameterCount());
//私有构造器无法访问时,可以暴力反射
cons.setAccessible(true);
Student s = (Student) cons.newInstance();
System.out.println(s);
Constructor cons1 = c.getDeclaredConstructor(String.class,int.class);
System.out.println(cons.getName() + "==>" + cons1.getParameterCount());
Student s1 = (Student) cons1.newInstance("叶文洁", 58);
System.out.println(s1.getName() + s1.getAge());
}
}
反射获取成员变量
使用反射技术获取成员变量对象并使用
- 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象
- Class类中用于获取成员变量的方法
方法 | 说明 |
---|---|
Field[] getFields() | 返回所有成员变量对象的数组(public) |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field[] getField(String name) | 返回单个成员变量对象(public) |
Field[] getDeclaredField(String name) | 返回单个成员变量对象 |
package com.shouyeren.reflect;
import org.junit.Test;
import java.lang.reflect.Field;
/**
* | Field[] getFields() | 返回所有成员变量对象的数组(**public**) |
* | Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
* | Field[] getField(String name) | 返回单个成员变量对象(**public**) |
* | Field[] getDeclaredField(String name) | 返回单个成员变量对象 |
*/
public class FieldDemo {
/**
* Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
*/
@Test
public void getDeclaredFields(){
//定位Class对象
Class<Student> s = Student.class;
//定位所有成员变量对象
Field[] fields = s.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + "==>" + field.getType());
}
}
/**
* Field[] getFields() | 返回所有成员变量对象的数组(**public**) |
*/
@Test
public void getFields(){
//定位Class对象
Class<Student> s = Student.class;
//定位所有成员变量对象
Field[] fields = s.getFields();
for (Field field : fields) {
System.out.println(field.getName() + "==>" + field.getType());
}
}
/**
* Field[] getField(String name) | 返回单个成员变量对象(**public**)
* @throws Exception
*/
@Test
public void getField() throws Exception {
//定位Class对象
Class<Student> s = Student.class;
//成员变量对象
Field field = s.getField("schoolName");
System.out.println(field.getName() + "==>" + field.getType());
}
/**
* Field[] getDeclaredField(String name) | 返回单个成员变量对象 |
* @throws Exception
*/
@Test
public void getDeclaredField() throws Exception {
//定位Class对象
Class<Student> s = Student.class;
//成员变量对象
Field field = s.getDeclaredField("age"); //打开权限
field.setAccessible(true);
Student student = new Student("张三",20);
//赋值
field.set(student,18);
//取值
System.out.println(field.get(student));
}
}
反射获取方法
使用反射技术获取方法对象并使用
- 反射的第一步是先得到类对象,然后从类对象中获取类的成分对象
- Class类中用于获取成员方法的方法
方法 | 说明 |
---|---|
Method[ ] getMethods() | 返回所有成员方法对象的数组(public) |
Method[ ] getDeclaredMethods() | 返回所有成员方法对象的数组 |
Method[ ] getMethod() | 返回单个成员方法对象的数组(public) |
Method[ ] getDeclaredMethod() | 返回单个指定成员方法对象的数组 |
反射的作用——绕过编译阶段为集合添加数据(泛型擦除)
反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的
ArrayList<Integer> list = new ArrayList<>();
list.add(100);
list.add("字符串");//报错
泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList,泛型相当于被擦除了
package com.shouyeren.reflect;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass());
System.out.println("--------------------------------------");
ArrayList<Integer> list3 = new ArrayList<>();
list3.add(18);
list3.add(20);
//list3.add("字符串");
Class c = list3.getClass();
Method add = c.getDeclaredMethod("add", Object.class);
boolean rs = (boolean) add.invoke(list3,"字符串");
System.out.println(list3);
System.out.println(rs);
}
}
反射的作用——通用框架的底层原理
需求:给你任意一个对象,在不清楚对象字段的情况下,可以把对象的字段名称和对应值存储到文件中去
- 定义一个方法,可以接收任意类对象
- 每次收到一个对象后,需要解析这个对象的全部成员变量名称。
- 这个对象可能是任意的,那么怎么样才可以知道这个对象的全部成员变量名称呢
- 使用反射获取对象的Class文件,然后获取全部成员变量信息
- 遍历全部成员变量信息,然后提取本成员变量在对象中的具体值
- 存入成员变量名称和值到文件中
package com.shouyeren.reflect.framework;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
public class MybatisUtil {
/**
* 保存任意类型的对象
* @param obj
*/
public static void save(Object obj){
try(PrintStream ps = new PrintStream(new FileOutputStream
("src/data.txt",true))) {
//获取这个对象的全部成员变量
Class c = obj.getClass();
ps.println("----------------" + c.getSimpleName() + "----------------");
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
String name = field.getName();
field.setAccessible(true);
String value = field.get(obj) + "";
ps.println(name + " = " + value);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package com.shouyeren.reflect.framework;
/**
* 提供一个通用框架,支持保存所有对象的具体信息
*/
public class ReflectDemo {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setClassName("实验一班");
student.setAge(20);
student.setHobby("打球");
student.setSex('男');
MybatisUtil.save(student);
Teacher teacher = new Teacher();
teacher.setName("苍老师");
teacher.setSex('女');
teacher.setSalary(12000);
MybatisUtil.save(teacher);
}
}
反射的作用:
- 可以在运行阶段得到一个类的全部成分然后操作
- 可以破坏封装性
- 可以破坏泛型约束
- 更重要的用途是适合:做Java高级框架
注解
注解概述
Java注解(Annotation)又称Java标注,是JDK5.0引入的一种注释机制
Java语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注
例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行
自定义注解
自定义注解格式:
public @interface 注解名称{
public 属性类型 属性名() default 默认值;
}
特殊属性:
- value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写
- 但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的
元注解
元注解就是注解的注解
元注解有两个:
- @Target:约束自定义注解只能在哪些地方使用
- @Retention:声明注解的生命周期
@Target:
- TYPE,类,接口
- FIELD,成员变量
- METHOD,成员方法
- PARAMETER,方法参数
- CONSTRUCTOR,构造器
- LOCAL_VARIABLE,局部变量
@Retention中可使用的值定义在RetentionPolicy枚举类中,常用值如下:
- SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
- CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
- RUNTIME:注释在源码阶段、字节码文件阶段,运行阶段(开发常用)
注解解析
注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容
与注解解析相关的接口:
- Annotation:注解的顶级接口,注解都是Annotation类型的对象
- AnnotatedElement:该接口定义了与注解解析相关的解析方法
注解解析的技巧:
- 注解在哪个成分上,就先拿哪个成分对象
- 比如注解作用成员方法,则要获得该成员方法对应的Method对象,再拿上面的注解
注解的应用场景一:JUnit框架
模拟JUnit框架
定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行
分析:
- 定义一个自定义注解MyTest,只能注解方法,存活范围是一直在
- 定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有该注解则不能执行
package com.shouyeren.annotation;
import java.lang.annotation.*;
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}
package com.shouyeren.annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class AnnotationTest {
@MyTest
public void test1(){
System.out.println("===test1===");
}
public void test2(){
System.out.println("===test2===");
}
@MyTest
public void test3(){
System.out.println("===test3===");
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
AnnotationTest test = new AnnotationTest();
Class c = AnnotationTest.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MyTest.class)){
method.invoke(test);
}
}
}
}
动态代理
动态代理概述、快速入门
如何创建代理对象:
- Java中代理的代表类是:java.lang.reflect.Proxy
- Proxy提供了一个静态方法,用于为对象产生一个代理对象返回
动态代理的应用案例:做性能分析,代理的好处
需求:
模拟某企业用户管理业务,需包含用户登录,用户删除,用户查询功能,并统计每个功能的耗时
分析:
- 定义一个UserService表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能
- 定义一个UserServiceImpl实现UserService,并完成相关功能,且统计每个功能的耗时
- 定义测试类,创建实现类对象,调用方法
package com.shouyeren.proxy;
public interface UserService {
String login(String loginName,String passWord);
void deleteUsers();
String selectUsers();
}
package com.shouyeren.proxy;
public class UserServiceImpl implements UserService{
@Override
public String login(String loginName, String passWord) {
String rs = "登录名和密码错误";
if ("admin".equals(loginName) && "132456".equals(passWord)){
rs = "登录成功";
}
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
return rs;
}
@Override
public void deleteUsers() {
System.out.println("正在删除用户数据。。。");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
public String selectUsers() {
String rs = "正在查询10000个用户数据。。。";
try {
Thread.sleep(2500);
} catch (Exception e) {
e.printStackTrace();
}
return rs;
}
}
package com.shouyeren.proxy;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static UserService getProxy(UserService obj){
return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), (proxy, method, args) -> {
long startTime = System.currentTimeMillis();
Object rs = method.invoke(obj, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "方法耗时:" + (endTime-startTime)/1000 + "S");
return rs;
});
}
}
package com.shouyeren.proxy;
public class Test {
public static void main(String[] args) {
UserService userService = ProxyUtil.getProxy(new UserServiceImpl());
System.out.println(userService.login("admin", "132456"));
System.out.println(userService.selectUsers());
userService.deleteUsers();
}
}
动态代理的优点:
- 可以在不改变方法源码的情况下,实现对方法功能的增加,提高了代码的复用。
- 简化了编程 工作、提高了开发效率、同时提高了软件系统的可扩展性
- 可以为被代理对象的所有方法做代理
- 非常的灵活,支持任意接口类型的实现类对象做代理,也可以直接为接口本身做代理