12.注解和反射
单元测试
- 单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对JAVA方法的测试,进而检查方法的正确性。
目前测试方法是怎么进行的,存在什么问题??
- 只有一个main方法,如果一个方法的测试失败了,其他方法测试会受到影响
- 无法得到测试的结果报告,需要程序员自己观察
- 无法实现自动化测试
Junit单元测试框架
- JUnit是使用Java语言实现的单元测试框架,它是开源的
- 几乎所有的IDE工具都集成了JUnit
JUnit的优点
-
JUnit可以灵活的选择执行哪些测试方法,可以一件执行全部测试方法
-
JUnit可以生成全部方法的测试报告
-
某个方法测试失败了,不会影响其他方法的测试
单元测试快速入门
需求:使用单元测试进行业务方法预期结果、正确性测试的快速入门
分析:
- 将JUnit的jar包导入到项目中
- IDEA通常整理好了JUnit框架,一般不需要导入
- 如果IDEA没有整合好,需要自己下载手动导入
- 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法
- 在测试方法上使用@Test注解:标注该方法是一个测试方法
- 在测试方法中完成被测试方法的预期正确性测试
- 选中测试方法,选择”JUnit运行“,成功是绿色,失败是红色
业务:
package d1_junit;
//业务方法
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(10/0);
System.out.println("查询全部用户名成功");
}
}
测试类:
package d1_junit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
//测试类
public class TestUserService {
@Test
public void testLoginName(){
UserService userService = new UserService();
String rs = userService.loginName("admin","123456");
// 进行预期结果的正确性测试:断言
Assertions.assertEquals(rs,"登录成功","您的功能业务可能出现问题");
}
@Test
public void testSelectNames(){
UserService userService = new UserService();
userService.selectNames();
}
}
单元测试常用注解

- 开始执行的方法:初始化资源
- 执行完之后的方法:释放资源
反射概述
- 反射是指对于任何一个class类,在“运行的时候”都可以直接得到这个类的全部成分
- 在运行时,可以直接得到这个类的构造器对象:Constructor
- 在运行时,可以直接得到这个类的成员变量对象:Field
- 在运行时,可以直接得到这个类的成员方法对象:Method
- 这种运行时动态获取类信息以及动态调用类中成分的能力称为JAVA语言的反射机制
反射的关键
- 反射的第一步都是先得到编译后的class类对象,然后就可以得到class的全部成分
HelloWorld.java->javac->HelloWorld.class
Class c = HelloWorld.class
反射获取类对象
反射的第一步:获取Class类的对象

package d2_reflect_class;
//反射的第一步:获取Class对象
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//1.Class类中的静态方法:forName(全限名:包名.类名)
Class c = Class.forName("d2_reflect_class.Student");
System.out.println(c);
//2.类名.class
Class c1 = Student.class;
System.out.println(c1);
//3.对象.getClass()获取对象对应类的Class对象
Student s = new Student();
Class c2 = s.getClass();
System.out.println(c2);
}
}
反射获取构造器对象
第一步:获得class对象
第二步:获得Constructor对象
第三步:创建对象
- Class类中获取构造器的方法

创建构造器:
package d3_reflect_constructor;
public class Student {
private String name;
private int age;
private Student(){
System.out.println("无参数构造器执行");
}
public Student(String name, int age){
System.out.println("有参数构造器执行");
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;}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
获取构造器:
package d3_reflect_constructor;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
public class TestStudent01 {
@Test
//只能取public
public void getConstructors(){
//1.获取类对象
Class c = Student.class;
//2.提取类中的全部的构造器对象
Constructor[] constructors = c.getConstructors();
//3.遍历构造器
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "===>" + constructor.getParameterCount());
}
}
//可以拿全部的构造器
@Test
public void getDeclaredConstructors(){
//1.获取类对象
Class c = Student.class;
//2.提取类中的全部的构造器对象
Constructor[] constructors = c.getDeclaredConstructors();
//3.遍历构造器
for (Constructor constructor : constructors) {
System.out.println(constructor.getName() + "===>" + constructor.getParameterCount());
}
}
//定位某个有参构造器
@Test
public void getDeclaredConstructor() throws Exception {
//1.获取类对象
Class c = Student.class;
//2.提取类中的全部的构造器对象
Constructor cons = c.getDeclaredConstructor(String.class, int.class);
//3.遍历构造器
System.out.println(cons.getName() + "===>" + cons.getParameterCount());
}
}
- Constructor类中用于创建对象的方法

反射获取成员变量对象
第一步:获得class对象
第二步:获得Field对象
第三步:赋值或者获取值
- Class类中获取成员变量的方法

- Field类中用于取值、赋值的方法

创建类:
package d4_reflect_field;
public class Student {
private String name;
private int age;
public static String schoolName;
public static final String COUNTRY = "中国";
public Student(){
System.out.println("无参构造器执行");
}
public Student(String name, int age){
System.out.println("有参构造器执行");
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;}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
获取成员变量:
package d4_reflect_field;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
public class FieldDemo01 {
/*
* 1.获取全部的成员变量
* Field[] getDeclaredFields();
* 获得所有的成员变量对应的Field对象,只要申明了就可以得到*/
@Test
public void getDeclaredFields(){
//1.定位class对象
Class c = Student.class;
//2.定位全部成员变量
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName() + "-->" + field.getType());
}
}
/*
* 2.获取某个成员变量
* Field getDeclaredField(String name)
* */
@Test
public void getDeclaredField() throws Exception {
//1.定位class对象
Class c = Student.class;
//2.根据名称定位某个成员变量
Field f = c.getDeclaredField("age");
System.out.println(f.getName() + "-->" + f.getType());
}
}
赋值、取值:
package d4_reflect_field;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
public class FieldDemo02 {
@Test
public void setField() throws Exception {
//1.获取类对象
Class c = Student.class;
//2.提取某个成员变量
Field ageF = c.getDeclaredField("age");
//暴力反射
ageF.setAccessible(true);
//3.赋值
Student s = new Student();
ageF.set(s, 18);
System.out.println(s);
//4.取值
int age = (int) ageF.get(s);
System.out.println(age);
}
}
反射获取方法对象
- 第一步:获得class对象
- 第二部:获得method对象

- 第三步:运行方法

定义类:
package d5_reflect_method;
public class Dog {
private String name;
public Dog(){
}
public Dog(String name) {
this.name = name;
}
public void run(){
System.out.println("狗跑的 很快");
}
private void eat(){
System.out.println("狗吃骨头");
}
private String eat(String name){
System.out.println("狗吃:"+name);
return "狗吃的很开心";
}
public static void inAddr(){
System.out.println("在黑马学习java");
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
package d5_reflect_method;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
public class MethodDemo1 {
/*
* 1.获得类中的所有成员方法对象
* */
@Test
public void getDeclareMethods(){
//a. 获取类对象
Class c = Dog.class;
//b. 获取全部方法,包括私有的
Method[] methods = c.getDeclaredMethods();
//c. 遍历全部方法
for (Method method : methods) {
System.out.println(method.getName() +"--"+ method.getReturnType() + "--" +method.getParameterCount()) ;
}
}
/*
* 2.获取某个方法对象
* */
@Test
public void getDeclardMethod() throws Exception {
//a.获取类对象
Class c = Dog.class;
//b.提取单个对象方法
Method m = c.getDeclaredMethod("eat");
Method m2 = c.getDeclaredMethod("eat",String.class);
m.setAccessible(true);//暴力反射
m2.setAccessible(true);//暴力反射
//c.触发方法执行
Dog d = new Dog();
//如果方法没有返回,接收的是null
Object result = m.invoke(d);
System.out.println(result);
Object result2 = m2.invoke(d,"骨头");
System.out.println(result2);
}
}
反射的作用-绕过编译阶段为集合添加数据
-
反射是作用再运行时的技术,此时集合的泛型将不能产生约束了,此时是可以为集合存入其他任意类型的元素的。

-
泛型知识在编译阶段可以约束集合只能操作某种数据类型,在编译成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,相当于泛型呗擦除了。
package d6_reflect_genericity;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class ReflectDemo {
public static void main(String[] args) throws Exception {
//需求:反射实现泛型擦除后,加入其他类型的元素
ArrayList<String> lists1 = new ArrayList<>();
ArrayList<Integer> lists2 = new ArrayList<>();
System.out.println(lists1.getClass());
System.out.println(lists2.getClass());
System.out.println(lists1.getClass() == lists2.getClass()); //ArrayList.class
System.out.println("----------------------------");
ArrayList<Integer> lists3 = new ArrayList<>();
lists3.add(22);
lists3.add(33);
//list3.add("黑马");
Class c = lists3.getClass(); //ArrayList.class ==> public boolean add(E e)
// 定位c类中的add方法
Method add = c.getDeclaredMethod("add",Object.class);
boolean rs = (boolean) add.invoke(lists3,"黑马");
System.out.println(rs);
System.out.println(lists3);
ArrayList list4 = lists3;
list4.add("白马");
list4.add(false);
System.out.println(lists3);
}
}
反射的作用-通用框架的底层原理
- 需求:给你任意一个对象,在不清楚对象字段的情况,可以把对象的字段名称和对应值存储到文件中去。
- 分析:
- 定义一个方法,可以接收任意类的对象
- 每次收到一个对象后,需要解析这个对象的全部成员变量名称
- 这个对象可能是任意的,那么怎么样才可以知道这个对象的全部成员变量名称呢?-->反射
- 使用反射获取对象的Class类对象,然后获取全部的成员变量信息
- 遍历成员变量信息,然后提取本成员变量在对象中的具体值
- 存入成员变量名称和值到文件中即可
创建类对象:
package d7_reflect_framework;
public class Student {
private String name;
private char sex;
private int age;
private String className;
private String hobby;
public Student(){
}
public Student(String name, char sex, int age, String className, String hobby) {
this.name = name;
this.sex = sex;
this.age = age;
this.className = className;
this.hobby = hobby;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public char getSex() {return sex;}
public void setSex(char sex) {this.sex = sex;}
public int getAge() {return age;}
public void setAge(int age) {this.age = age;}
public String getClassName() {return className;}
public void setClassName(String className) {this.className = className;}
public String getHobby() {return hobby;}
public void setHobby(String hobby) {this.hobby = hobby;}
}
package d7_reflect_framework;
public class Teacher {
private String name;
private char sex;
private double salary;
public Teacher(){
}
public Teacher(String name, char sex, double salary) {
this.name = name;
this.sex = sex;
this.salary = salary;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public char getSex() {return sex;}
public void setSex(char sex) {this.sex = sex;}
public double getSalary() {return salary;}
public void setSalary(double salary) {this.salary = salary;}}
创建保存任意类型的对象:
package d7_reflect_framework;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Field;
/*
* 保存任意类型的对象
* */
public class MybatisUtil {
public static void save(Object obj) throws FileNotFoundException {
PrintStream ps = new PrintStream(new FileOutputStream("junit-reflect-annotation-proxy-app/src/data.txt"),true);
//1.提取这个对象的全部成员变量,只有反射可以解决
Class c = obj.getClass(); // c.getSimpleName()获取当前类名, c.getName获取全限名:包名+类名
ps.println("------------" + c.getSimpleName() + "-----------");
//2.提取它的全部成员变量
Field[] fields = c.getDeclaredFields();
//3.获取成员变量的信息
for (Field field : fields) {
String name = field.getName();
//提取本成员变量在obj对象中的值(取值)
field.setAccessible(true);
try {
String value = field.get(obj)+"";
ps.println(name + "=" + value);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
使用:
package d7_reflect_framework;
/*
* 目标:提供一个通用框架,支持保存所有对象的具体值
* */
public class ReflectDemo {
public static void main(String[] args) throws Exception{
Student s = new Student();
s.setName("猪八戒");
s.setClassName("西天");
s.setAge(1000);
s.setHobby("吃");
s.setSex('男');
MybatisUtil.save(s);
Teacher t = new Teacher();
t.setName("波仔");
t.setSex('男');
t.setSalary(6000);
MybatisUtil.save(t);
}
}
注解概述
- 对java中类、方法、成员变量做标记,然后进行特殊处理
自定义注解

public @interface MyBook {
String name();
String[] authors();
double price();
}
注意:使用注解进行标注的时候,要先把代码写了,再标注,不然会报错
特殊属性:
- value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写
- 但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省略的
public @interface Book {
String value(); //特殊属性
double price();
int year() default 2008; //有默认值可以省略
}
/*
* 目标:学会自定义注解,掌握其定义格式和语法*/
@MyBook(name = "精通javaSE" , authors = {"黑马","地雷"} , price = 199.5)
//@Book("/delete") //没写value,也没报错
@Book(value = "/delete", price = 135.5)
public class AnnotitionDemo1 {
@MyBook(name = "精通javaSE" , authors = {"黑马","地雷"} , price = 199.5)
private AnnotitionDemo1(){
}
@MyBook(name = "精通javaSE" , authors = {"黑马","地雷"} , price = 199.5)
public static void main(String[] args) {
@MyBook(name = "精通javaSE" , authors = {"黑马","地雷"} , price = 199.5)
int age = 21;
}
}
元注解
- 元注解:就是注解注解的注解
常见的元注解:
- @Target:约束自定义注解只能在哪些地方使用
- @Retention:申明注解的生命周期
@Target({ElementType.METHOD,ElementType.FIELD}) //元注解
@Retention(RetentionPolicy.RUNTIME) //一直活着
public @interface MyTest {
}
/*
* 目标:认识元注解*/
//@MyTest //我设置的只能注解方法和成员变量
public class AnnotitionDemo2 {
@MyTest
private String name;
@MyTest
public void test(){
}
}
注解的解析
- 注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容
与注解解析相关的接口
- Annotation:注解的顶级接口,注解都是Annotation类型的对象
- AnnotatedElement:该接口定义了与注解解析相关的解析方法

- 所有的类成分Class,Method,Field,Constructor,都实现了AnnotatedElement接口,他们都有解析注解的能力
解析注解的技巧
- 注解在哪个成分上,我们就先拿哪个成分对象
- 比如注解作用成员方法,则要获得该成员方法对应的Method对象,再来拿上面的注解
- 比如注解作用再类上,则要该类的Class对象,再来拿上面的注解
- 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象,再来拿上面的注解
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Bookk {
String value();
double price() default 100;
String[] authors();
}
/*
* 目标:完成注解的解析
* */
public class AnnotitionDemo3 {
@Test
public void parseClass() throws Exception {
// a.先得到类对象
Class c = BookStore.class;
// b.判断类上是否存在这个注解
if(c.isAnnotationPresent(Bookk.class)){
// c.直接获取该注解对象
Bookk book = (Bookk) c.getDeclaredAnnotation(Bookk.class);
System.out.println(book.value());
System.out.println(book.price());
System.out.println(Arrays.toString(book.authors()));
}
// Method m = c.getDeclaredMethod("test");
// if(m.isAnnotationPresent(Bookk.class)){
// Bookk book = (Bookk) m.getDeclaredAnnotation(Bookk.class);
// System.out.println(book.value());
// System.out.println(book.price());
// System.out.println(Arrays.toString(book.authors()));
}
}
@Bookk(value = "情深深雨蒙蒙" , price = 99.9 , authors = {"琼瑶" , "地雷"})
class BookStore{
@Bookk(value = "三少爷的剑" , price = 399.9 , authors = {"古龙" , "熊耀华"})
public void test(){
}
}
案例:模拟Junit框架
需求
- 定义若干个方法,只要加了MyTest注解,就可以在启动时被触发执行
分析
- 定义一个自定义注解MyTest,只能注解方法,存活范围是一直都在
- 定义若干个方法,只要有@MyTest注解的方法就能在启动时被触发执行,没有这个注解的方法不能执行
@Target({ElementType.METHOD}) //元注解
@Retention(RetentionPolicy.RUNTIME) //一直活着
public @interface MyTest {
}
public class AnnotitionDemo4 {
@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 {
AnnotitionDemo4 t = new AnnotitionDemo4();
// a.获取类对象
Class c = AnnotitionDemo4.class;
// b.提取全部方法
Method[] methods = c.getDeclaredMethods();
// c.遍历方法,看是否有MyTest注解,有就执行
for (Method method : methods) {
if (method.isAnnotationPresent(MyTest.class)){
//执行
method.invoke(t);
}
}
}
}
动态代理
什么是代理?
- 某些场景下,对象会找一个代理对象,来辅助自己完成一些工作,如歌星(经纪人)
代理主要干什么,它是如何工作的?
- 代理主要是对对象的行为额外做一些辅助操作
注意:动态代理一定要基于接口设计
被代理的对象类:
public class Star implements Skill{
private String name;
public Star(String name) {
this.name = name;
}
@Override
public void sing() {
System.out.println(name + "唱:鸡你太美");
}
@Override
public void jump() {
System.out.println(name + "顶跨");
}
}
接口:
public interface Skill {
void sing();
void jump();
}
代理:
public class StarAgentProxy {
/**
* 设计一个方法来返回一个明显对象的代理对象*/
public static Skill getProxy(Star obj){
//为蔡徐坤这个对象,生成一个代理对象
return (Skill) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("收定金");
// 真正让蔡徐坤唱跳
// method 正在调用的方法对象 args代表这个方法的参数
Object rs = method.invoke(obj , args);
System.out.println("收尾款");
return rs;
}
});
}
}
执行:
public class Test {
public static void main(String[] args) {
// 目标:学习开发出一个动态代理的对象出来,理解动态代理的执行流程
// 1.创建一个对象(蔡徐坤)。对象的类必须实现接口
Star s = new Star("蔡徐坤");
// 为蔡徐坤对象生成一个代理对象
Skill s2 = StarAgentProxy.getProxy(s);
s2.sing(); // 走代理的
s2.jump();
}
}
案例-模拟企业业务功能开发,并完成每个功能的性能设计
需求
- 模拟企业用户管理业务,需包含用户登录,用户删除,用户查询功能,并统计每个功能的耗时
分析
- 定义一个UserService表示用户业务接口,规定必须完成用户登录,用户删除,用户查询功能
- 定义一个实现类UserServicelmpl实现UserService,并完成相关功能,且统计每个功能耗时
- 定义测试类,创建实现类对象,调用方法。
业务接口:
public interface UserService {
String login(String loginName , String passWord);
void deleteUsers();
String selectUsers();
}
实现类:
public class UserServiceImpl implements UserService{
@Override
public String login(String loginName, String passWord) {
String rs = "登录名称或密码错误";
if("admin".equals(loginName) && "123456".equals(passWord)){
rs = "登录成功";
}
try {
Thread.sleep(1000);
} catch (Exception e) {
throw new RuntimeException(e);
}
return rs;
}
@Override
public void deleteUsers() {
try {
System.out.println("正在删除用户数据中...");
Thread.sleep(2500);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String selectUsers() {
String rs = "查询了10000个用户数据";
try {
Thread.sleep(3000);
} catch (Exception e) {
throw new RuntimeException(e);
}
return rs;
}
}
创建代理:
public class ProxyUtil {
/**通过一个静态方法,为用户业务对象返回一个代理对象*/
public static UserService getProxy(UserService obj){
return (UserService) Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
//真正触发对象的行为执行的
Object rs = method.invoke(obj, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName() + "方法耗时:" + (endTime - startTime) / 1000.0 + "s");
return rs;
}
});
}
}
调用:
public class Test {
public static void main(String[] args) {
//目标:掌握使用动态代理解决问题,理解使用动态代理的优势
UserService userService = ProxyUtil.getProxy(new UserServiceImpl());
userService.login("admin", "123456");
userService.deleteUsers();
}
}

浙公网安备 33010602011771号