单元测试、反射、注解、动态代理
一、单元测试
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 + "个人");
}
}