JavaSE高级 JUnit单元测试、反射、注解、动态代理
学习目标
- 能够使用Junit进行单元测试
- 能够通过反射技术获取Class字节码对象
- 能够通过反射技术获取构造方法对象,并创建对象。
- 能够通过反射获取成员方法对象,并且调用方法。
- 能够通过反射获取属性对象,并且能够给对象的属性赋值和取值。
- 能够说出注解的作用
- 能够自定义注解和使用注解
- 能够说出常用的元注解及其作用
- 能够解析注解并获取注解中的数据
- 能够完成注解的MyTest案例
- 能够说出动态代理模式的作用
- 能够使用Proxy的方法生成代理对象
- 能够使用Base64对基本数据、URL和MIME类型进行编解码
第一章 Junit单元测试
1.1 Junit概念
单元测试是指程序员写的测试代码给自己的类中的方法进行预期正确性的验证。 单元测试一旦写好了这些测试代码,就可以一直使用,可以实现一定程度上的自动化测试。 单元测试已经上过了 单元测试一般要使用框架进行。 什么是框架? 框架是前人或者一些牛逼的技术公司在实战或者研发中设计的一些优良的设计方案,或者成型的代码功能,作为一个完整的技术体系发行出来成为框架。 框架可以让程序员快速拥有一个强大的解决方案,可以快速的开发功能,提高效率,并且直接就有了很好的性能。
单元测试经典框架:Junit Junit是什么?
* Junit是Java语言编写的第三方单元测试框架(工具类)
* 类库 ==> 类 junit.jar
* Junit框架的方案可以帮助我们方便且快速的测试我们代码的正确性。
单元测试概念
* 单元:在Java中,一个类就是一个单元
* 单元测试:程序猿编写的一小段代码,用来对某个类中的某个方法进行功能测试或业务逻辑测试。
Junit单元测试框架的作用
* 用来对类中的方法功能进行有目的的测试,以保证程序的正确性和稳定性。
* 能够让方法独立运行起来。
Junit单元测试框架的使用步骤 (1) 下载Junit框架
* 框架一般是jar包的形式,jar包里面都是class文件(Java工程的最终形式)。
class文件就是我们调用的核心代码。
(2) 新建测试类
* 编写业务类,在业务类中编写业务方法。比如增删改查的方法
* 编写测试类,在测试类中编写测试方法,在测试方法中编写测试代码来测试。
* 测试类的命名规范:以Test开头,以业务类类名结尾,使用驼峰命名法
* 每一个单词首字母大写,称为大驼峰命名法,比如类名,接口名...
* 从第二单词开始首字母大写,称为小驼峰命名法,比如方法命名
* 比如业务类类名:ProductDao,那么测试类类名就应该叫:TestProductDao
* 测试方法的命名规则:以test开头,以业务方法名结尾
* 比如业务方法名为:save,那么测试方法名就应该叫:testSave
测试方法注意事项
* 必须是public修饰的,没有返回值,没有参数
* 必须使用注解@Test修饰
如何运行测试方法
* 选中方法名 --> 右键 --> Run '测试方法名' 运行选中的测试方法
* 选中测试类类名 --> 右键 --> Run '测试类类名' 运行测试类中所有测试方法
* 选中模块名 --> 右键 --> Run 'All Tests' 运行模块中的所有测试类的所有测试方法
如何查看测试结果
* 绿色:表示测试通过
* 红色:表示测试失败,有问题
Junit常用注解(Junit4.xxxx版本)
//调用每个方法前 初始化
* @Before:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
//调用每个方法之后 释放资源
* @After:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
//断言怎么用
三个参数
1 与预期的结果不一致,异常信息
2 期望值
3 实际值
Assert.assertErquals(String,Object ,Object);
* @BeforeClass:用来修饰静态方法,该方法会在所有测试方法之前执行一次。
* @AfterClass:用来修饰静态方法,该方法会在所有测试方法之后执行一次。
方法上必须加上static
Junit常用注解(Junit5.xxxx版本)
* @BeforeEach:用来修饰方法,该方法会在每一个测试方法执行之前执行一次。
* @AfterEach:用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
* @BeforeAll:用来静态修饰方法,该方法会在所有测试方法之前执行一次。
* @AfterAll:用来静态修饰方法,该方法会在所有测试方法之后执行一次。
1.2 示例代码
/* 业务类:实现加减乘除运算 / public class Cacluate { / 业务方法1:求a和b之和 / public int sum(int a,int b){ return a + b + 10; }
/*
业务方法2:求a和b之差
*/
public int sub(int a,int b){
return a - b;
}
}
public class TestCacluate {
static Cacluate c = null;
@BeforeClass // 用来静态修饰方法,该方法会在所有测试方法之前执行一次。
public static void init(){
System.out.println("初始化操作");
// 创建Cacluate对象
c = new Cacluate();
}
@AfterClass // 用来静态修饰方法,该方法会在所有测试方法之后执行一次。
public static void close(){
System.out.println("释放资源");
c = null;
}
/* @Before // 用来修饰方法,该方法会在每一个测试方法执行之前执行一次。 public void init(){ System.out.println(“初始化操作”); // 创建Cacluate对象 c = new Cacluate(); }
@After // 用来修饰方法,该方法会在每一个测试方法执行之后执行一次。
public void close(){
System.out.println("释放资源");
c = null;
}*/
@Test
public void testSum(){
int result = c.sum(1,1);
/*
断言:预习判断某个条件一定成立,如果条件不成立,则直接奔溃。
assertEquals方法的参数
(String message, double expected, double actual)
message: 消息字符串
expected: 期望值
actual: 实际值
*/
// 如果期望值和实际值一致,则什么也不发生,否则会直接奔溃。
Assert.assertEquals("期望值和实际值不一致",12,result);
System.out.println(result);
}
@Test
public void testSub(){
// 创建Cacluate对象
// Cacluate c = new Cacluate();
int result = c.sub(1,1);
// 如果期望值和实际值一致,则什么也不发生,否则会直接奔溃。
Assert.assertEquals("期望值和实际值不一致",0,result);
System.out.println(result);
}
}
第二章 反射
1,new Student s = new Student(); 在堆储存区开辟了一块空间,其对象的引用存储在栈存储区上。 2,反射 reflect java的反射机制是指,在运行状态中,对于任意一个类,我们可以获取这个类的属性和方法,对于任意一个对象,我们可以调用这个对象的方法和属性。这种动态获取信息和动态调用对象的方法就是java 的反射机制。 Class 类,每个class 都会有一个Class对象,当我们完成一个类通过编译之后,就会生成一个.class 文件,在生成的.class 文件中,就会有个Class 对象,用于表示这个类的类型信息。 获得类的Class 对象的3中方法: 1,类名.class (任意数据类型,都会有一个class 属性) 2,Class.forName(“java.lang.String”); 类的全路径 3,类的实例化对象下,有getClass() 方法。 反射创建对象: 不存在有参的构造函数: 方法1: 类名.class.newInstance(); 就算没有构造方法,也会调用默认的无参构造方法 Demo newInstance = Demo.class.newInstance(); newInstance.setUserName(“chris”); newInstance.setPassword(“12345”); System.out.println(newInstance.getUserName()+ newInstance.getPassword()); 方法2: 和方法1本质相同,一个是使用类名.class ,一个是使用Class.forName(“类的全路劲”)来获取类的类对象,再通过newInstance()方法创建对象。 Class clazz = (Class) Class.forName(“Demo”); Demo newInstance2 = clazz.newInstance(); newInstance2.setPassword(“12345”); newInstance2.setUserName(“Sarah”); System.out.println(newInstance2.getUserName()+ newInstance2.getPassword()); 存在有参的构造函数: 先获取类的Class 对象,通过类对象获取到指定的构造器,可以是有参,可以是无参,通过指定的构造器,创建对象 Class clazz3 = (Class) Class.forName(“Demo”); Constructor con = clazz3.getDeclaredConstructor(String.class,String.class); Demo newInstance3 = con.newInstance(“Mike”,”12345”); //newInstance3.setUserName(“vincent”);可以通过反射是可以改变对象的值 //newInstance3.setPassword(“12345”); System.out.println(newInstance3.getUserName()+ newInstance3.getPassword()); Class对象获取构造器比较: Constructor con = clazz3.getDeclaredConstructor(String.class,String.class); //能获取到指定参数的构造器,和访问修饰符无关,private,public 所有的可以获取到 Constructor<?>[] conn = clazz4.getConstructors();//所有public 修饰的构造器 Constructor connn = clazz5.getConstructor(String.class,String.class);//所有public 修饰的带参数的指定构造器 反射的应用场景: jdbc的连接 Class.forName(“com.mysql.jdbc.Driver”) springIOC反射创建对象 mybatis 3,clone 调用clone,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。 前提,必须要实现Cloneable 接口,本地实现 protected native Object clone() throws CloneNotSupportedException; Demo clone = (Demo) newInstance.clone(); 4,反序列化 序列化:将堆内存中的java 对象通过某种方式,存储到磁盘上或者传输给其他网络节点,也就是java对象转成二进制。 反序列化:与序列化相反,再将序列化之后的二进制串再转成数据结构或对象。 为什么需要做序列化? 1,网络节点的传输,java 对象需要转成二进制串。 2,服务器钝化:如果在内存中,一个对象长时间没有被调用,就会将其序列化存储在本地磁盘上,有需要活动的时候,就会现在内存中寻找,找不到,会将磁盘上的二进制再次反序列化成java 对象。可以节省服务器内存。 实现序列化? 1,需要做序列化的对象的类,必须实现序列化接口:Java.lang.Serializable 接口 通过反序列化创建对象。 //本地创建a.txt 文件,并且把对象序列化转成二进制写进文件中 File f = new File(String path) OutputStream op = new FileOutputStream(file); ObjectOutputStream ops = new ObjectOutputStream(op); ops.writeObject(new Demo(“Chris”, “12345”)); ops.close(); //从写进的文件中,读取二进制串,用对象流将其读出 InputStream in = new FileInputStream(file); ObjectInputStream os = new ObjectInputStream(in); Demo d = (Demo) os.readObject(); System.out.println(d.getUserName()+ d.getPassword()); os.close(); 反序列化 请注意深复制和浅复制 https://www.cnblogs.com/pickKnow/p/11104193.html
2.1 反射的概述
2.1.1 反射的引入
- 问题:IDEA中的对象是怎么知道类有哪些属性,哪些方法的呢?
通过反射技术对象类进行了解剖得到了类的所有成员。 //反射机制 在创建类的同时 可以在运行时候 可以得到类的全部信息
2.1.2 反射的概念
反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法) 反射是指对于任何一个类,在”运行的时候”都可以直接得到这个类全部成分。
- 在运行时,可以直接得到这个类的构造器对象。(Constructor)
- 在运行时,可以直接得到这个类的成员变量对象。(Field)
在运行时,可以直接得到这个类的成员方法对象。(Method)
内部类 inner class
2.1.3 使用反射操作类成员的前提
要获得该类字节码文件对象,就是Class对象 //怎么获得?
2.1.4 反射在实际开发中的应用
- 开发IDE(集成开发环境),比如IDEA,Eclipse
- 各种框架的设计和学习 比如Spring,Hibernate,SpringMVC,Mybaits….
总结: 1.反射是工作在运行时的技术,因为只有运行之后才会有class类对象。 2.反射的核心思想和关键就是得到,编译以后的class对象。 3.反射是在运行时获取类的字节码文件对象,然后可以解析类中的全部成分。
2.2 Class对象的获取方式
2.2.1 三种获取方法
- 方式1: 通过类名.class获得 已知这个类
- 方式2:通过对象名.getClass()方法获得 已知对象
- 方式3:通过Class类的静态方法获得: Class.forName(“类全名”) 已知路径名,全类名
- 每一个类的Class对象都只有一个,是一个字节码文件对象。
- 示例代码
public class ReflectDemo01 { public static void main(String[] args) throws ClassNotFoundException { // 获得Student类对应的Class对象 Class c1 = Student.class;
// 创建学生对象
Student stu = new Student();
// 通过getClass方法
Class c2 = stu.getClass();
System.out.println(c1 == c2);
// 通过Class类的静态方法获得: static Class forName("类全名")
Class c3 = Class.forName("com.entity.Student");
System.out.println(c1 == c3);
System.out.println(c2 == c3);
}
} 运行结果: 三个都是true
2.2.2 Class类常用方法
2个对象 一个是实例对象 另一个是字节码对象(.class文件) String getSimpleName(); 获得类名字符串:类名 String getName(); 获得类全名:包名+类名 T newInstance() ; 创建Class对象关联类的对象
- 示例代码
public class ReflectDemo02 { public static void main(String[] args) throws Exception { // 获得Class对象 Class c = Student.class; // 获得类名字符串:类名 System.out.println(c.getSimpleName()); // 获得类全名:包名+类名 System.out.println(c.getName()); // 创建对象 Student stu = (Student) c.newInstance(); System.out.println(stu); } } 结果: User com.igeek.javase.test.User com.igeek.javase.test.User@4554617c
2.3 反射之操作构造方法
2.3.1 Constructor类概述
反射之操作构造方法的目的
* 获得Constructor对象来创建类的对象。
Constructor类概述
* 类中的每一个构造方法都是一个Constructor类的对象
2.3.2 Class类中与Constructor相关的方法
Constructor getConstructor(Class… parameterTypes)
* 根据参数类型获得对应的Constructor对象。
* 只能获得public修饰的构造方法
//获得有参构造器 可变参数,其类类型是Class类型
Constructor<Person> constructor2 = clazz.getConstructor(String.class, String.class ,int.class);
System.out.println("name = "+constructor2.getName()+" : "+"parameterCount = "+constructor2.getParameterCount());
- Constructor getDeclaredConstructor(Class… parameterTypes)//对应.clss
//易错点 String.class int.class
- 根据参数类型获得对应的Constructor对象,包括private
- Constructor[] getConstructors() 获得类中的所有构造方法对象,只能获得public的
- Constructor[] getDeclaredConstructors() 获得类中的所有构造方法对象,包括private修饰的
2.3.3 Constructor对象常用方法
- T newInstance(Object… initargs) 根据指定的参数创建对象
- void setAccessible(true) 对于获取私有成员变量 私有成员方法 私有构造器用到暴力反射 byli 设置是否取消权限检查,true取消权限检查,false表示不取消(暴力反射)
2.3.4 示例代码
public class ReflectDemo03 {
/*
Constructor[] getConstructors()
获得类中的所有构造方法对象,只能获得public的
Constructor[] getDeclaredConstructors()
获得类中的所有构造方法对象,包括private修饰的
*/
@Test
public void test03() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得类中的所有构造方法对象,只能获得public的
// Constructor[] cons = c.getConstructors();
Constructor[] cons = c.getDeclaredConstructors();
for (Constructor con:cons) {
System.out.println(con);
}
}
/*
Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数类型获得对应的Constructor对象
*/
@Test
public void test02() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得两个参数构造方法对象
Constructor con = c.getDeclaredConstructor(String.class,String.class);
// 取消权限检查(暴力反射)
con.setAccessible(true);
// 根据构造方法创建对象
Object obj = con.newInstance("rose","女");//使用有参构造函数,给对象初始化数据
System.out.println(obj);
}
/*
Constructor getConstructor(Class... parameterTypes)
根据参数类型获得对应的Constructor对象
*/
@Test
public void test01() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得无参数构造方法对象
Constructor con = c.getConstructor();
// 根据构造方法创建对象
Object obj = con.newInstance();
System.out.println(obj);
// 获得有参数的构造方法对象
Constructor con2 = c.getConstructor(String.class, String.class,int.class);
// 创建对象
Object obj2 = con2.newInstance("jack", "男",18);
System.out.println(obj2);
}
}
2.4 反射之操作成员方法
2.4.1 Method类概述
反射之操作成员方法的目的
* 操作Method对象来调用成员方法
Method类概述
* 每一个成员方法都是一个Method类的对象。
2.4.2 Class类中与Method相关的方法
Method getMethod(String name,Class…args);
- 根据方法名和参数类型获得对应的构造方法对象,只能获得public的
Method getDeclaredMethod(String name,Class…args);
- 根据方法名和参数类型获得对应的构造方法对象,包括private的
Method[] getMethods();
- 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
Method[] getDeclaredMethods();
- 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的 package com.igeek.javase.method;
import org.junit.Test;
import java.lang.reflect.Method;
/**
- @version 1.0
- @Description Class类中,提供的获得Method对象的方法
- @Author chenmin
- @Date 2021/4/22 16:11 *
- 如何获得Method对象?
- 1.Method getMethod(String name,Class…args);
- 根据方法名和参数类型获得对应的构造方法对象,只能获得public的 *
- 2.Method getDeclaredMethod(String name,Class…args); —- 使用较多
- 根据方法名和参数类型获得对应的构造方法对象,包括private的 *
- 3.Method[] getMethods();
- 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的 *
- 4.Method[] getDeclaredMethods();
- 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的 / public class MethodDemo1 {
//3.Method[] getMethods() 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的(也包含Object中的方法) @Test public void test1(){ //1.获得Student类的类类型 Class
clazz = Student.class; //2.通过Student的类类型,获得其所有公开的方法对象(包括父类的公开的方法) Method[] methods = clazz.getMethods(); //3.迭代Method对象 for (Method method : methods) { //method.getName()获得方法名称
//method.getParameterCount 获得形参的个数
System.out.println(method+" : "+method.getName()+ " : "+method.getParameterCount());
} }
//4.Method[] getDeclaredMethods() 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的 @Test public void test2(){ //1.获得Student类的类类型 Class
clazz = Student.class; //2.通过Student的类类型,获得其所有公开的方法对象(包括父类的公开的方法) Method[] methods = clazz.getDeclaredMethods(); //3.迭代Method对象 for (Method method : methods) { //method.getName()获得方法名称
//method.getParameterCount 获得形参的个数
System.out.println(method+" : "+method.getName()+ " : "+method.getParameterCount());
} }
//1.Method getMethod(String name,Class…args) 根据方法名和参数类型获得对应的方法对象,只能获得public的 @Test public void test3() throws NoSuchMethodException { //1.获得Student类的类类型 Class
clazz = Student.class; //2.通过Student类的类类型,获得指定的公开的方法对象 /** - clazz.getMethod(String name,Class…parameterType) 返回Method对应
- 1.第一个参数:获得对应的方法的名称
- 2.第二个参数:可变参数,获得对应的方法的形参的类类型 */ Method studyMethod = clazz.getMethod(“study”); //public void com.igeek.javase.method.Student.study() System.out.println(studyMethod); }
//2.Method getDeclaredMethod(String name,Class…args) 根据方法名和参数类型获得对应的构造方法对象,包括private的 @Test public void test4() throws NoSuchMethodException { //1.获得Student类的类类型 Class
clazz = Student.class; //2.通过Student类的类类型,获得指定的公开的方法对象 /** - clazz.getMethod(String name,Class…parameterType) 返回Method对应
- 1.第一个参数:获得对应的方法的名称
- 2.第二个参数:可变参数,获得对应的方法的形参的类类型 */ Method playMethod = clazz.getDeclaredMethod(“play”, int.class, int.class, int.class); //private int com.igeek.javase.method.Student.play(int,int,int) System.out.println(playMethod); } } package com.igeek.javase.method;
import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
/**
- @version 1.0
- @Description Method中的常用方法
- @Author chenmin
- @Date 2021/4/22 16:40 *
- Object invoke(Object obj, Object… args)
- 调用指定对象obj的该方法
- args:调用方法时传递的参数
- void setAccessible(true)
设置是否取消权限检查,true取消权限检查,false表示不取消(暴力反射) */ public class MethodDemo2 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//1.获得Student的class对象 Class
clazz = Student.class; //2.获得Student中的private int play(int,int,int) Method playMethod = clazz.getDeclaredMethod(“play”, int.class, int.class, int.class);
//3.暴力反射,设置为true,取消权限检查 playMethod.setAccessible(true);
//4.执行play方法 //创建Student实例,等价于 Student student = new Student(); Constructor
c = clazz.getDeclaredConstructor(); Student student = c.newInstance(); /**
- 通过Object invoke(Object obj, Object… args)执行play方法
- 第一个参数:指定哪个对象
- 第二个参数:当前执行的方法,所需要的形参的具体值 可变参数(可填多个,也可不填)
- 返回方法执行的返回值(若方法时void,则没有返回值) */ Object result = playMethod.invoke(student, 10, 20, 30); System.out.println(“result = “+result); }
} //反思:执行Method.invoke(instence) 里面是具体的实例化对象 两种方式 1,通过反射获取类类型对象 再获取构造器,通过构造器的newInstence实例化 2,直接通过类名 new出来一个对象
2.4.3 Method对象常用方法
- Object invoke(Object obj, Object… args)
- 调用指定对象obj的该方法
- args:调用方法时传递的参数
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
- void setAccessible(true) 设置是否取消权限检查,true取消权限检查,false表示不取消(暴力反射)
2.4.4 示例代码
public class ReflectDemo04 {
// 反射操作静态方法
@Test
public void test04() throws Exception {
// 获得Class对象
Class c = Student.class;
// 根据方法名获得对应的成员方法对象
Method method = c.getDeclaredMethod("eat",String.class);
// 通过method执行对应的方法
method.invoke(null,"蛋炒饭");
}
/*
* Method[] getMethods();
* 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
* Method[] getDeclaredMethods();
* 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
*/ @Test public void test03() throws Exception { // 获得Class对象 Class c = Student.class; // 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的 // Method[] methods = c.getMethods(); // 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的 Method[] methods = c.getDeclaredMethods(); for (Method m: methods) { System.out.println(m); }
}
/*
Method getDeclaredMethod(String name,Class...args);
* 根据方法名和参数类型获得对应的构造方法对象,
*/
@Test
public void test02() throws Exception {
// 获得Class对象
Class c = Student.class;
// 根据Class对象创建学生对象
Student stu = (Student) c.newInstance();
// 获得sleep方法对应的Method对象
Method m = c.getDeclaredMethod("sleep");
// 暴力反射
m.setAccessible(true);
// 通过m对象执行stuy方法
m.invoke(stu);
}
/*
Method getMethod(String name,Class...args);
* 根据方法名和参数类型获得对应的构造方法对象,
*/
@Test
public void test01() throws Exception {
// 获得Class对象
Class c = Student.class;
// 根据Class对象创建学生对象
Student stu = (Student) c.newInstance();
// 获得study方法对应的Method对象
Method m = c.getMethod("study");
// 通过m对象执行stuy方法
m.invoke(stu);
/// 获得study方法对应的Method对象
Method m2 = c.getMethod("study", int.class);
// 通过m2对象执行stuy方法
m2.invoke(stu,8);
}
}
2.5 反射之操作成员变量
2.5.1 Field类概述
反射之操作成员变量的目的
* 通过Field对象给对应的成员变量赋值和取值
Field类概述
* 每一个成员变量都是一个Field类的对象。
2.5.2 Class类中与Field相关的方法
- Field getField(String name);
- 根据成员变量名获得对应Field对象,只能获得public修饰
- Field getDeclaredField(String name);
- 根据成员变量名获得对应Field对象,包含private修饰的
- Field[] getFields();
- 获得所有的成员变量对应的Field对象,只能获得public的
- Field[] getDeclaredFields();
- 获得所有的成员变量对应的Field对象,包含private的
2.5.3 Field对象常用方法
void set(Object obj, Object value)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z)
void setDouble(Object obj, double d)
Object get(Object obj)
int getInt(Object obj)
long getLong(Object obj)
boolean getBoolean(Object ob)
double getDouble(Object obj)
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。 Class getType(); 获取属性的类型,返回Class对象。 setXxx方法都是给对象obj的属性设置使用,针对不同的类型选取不同的方法。 getXxx方法是获取对象obj对应的属性值的,针对不同的类型选取不同的方法。
2.5.4 示例代码
public class ReflectDemo05 { /* Field[] getFields();
* 获得所有的成员变量对应的Field对象,只能获得public的
Field[] getDeclaredFields();
* 获得所有的成员变量对应的Field对象,包含private的
*/
@Test
public void test02() throws Exception {
// 获得Class对象
Class c = Student.class;
// 获得所有的成员变量对应的Field对象
// Field[] fields = c.getFields();
// 获得所有的成员变量对应的Field对象,包括private
Field[] fields = c.getDeclaredFields();
for (Field f: fields) {
System.out.println(f);
}
}
/*
Field getField(String name);
根据成员变量名获得对应Field对象,只能获得public修饰
Field getDeclaredField(String name);
* 根据成员变量名获得对应Field对象,包含private修饰的
*/
@Test
public void test01() throws Exception {
// 获得Class对象
Class c = Student.class;
// 创建对象
Object obj = c.newInstance();
// 获得成员变量name对应的Field对象
Field f = c.getField("name");
// 给成员变量name赋值
// 给指定对象obj的name属性赋值为jack
f.set(obj,"jack");
// 获得指定对象obj成员变量name的值
System.out.println(f.get(obj)); // jack
// 获得成员变量的名字
System.out.println(f.getName()); // name
// 给成员变量gender赋值
// 获得成员变量gender对应的Field对象
Field f1 = c.getDeclaredField("gender");
// 暴力反射
f1.setAccessible(true);
// 给指定对象obj的gender属性赋值为男
f1.set(obj,"男");
System.out.println(obj);
}
}
package com.igeek.javase.field;
import org.junit.Test;
import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException;
/**
- @version 1.0
- @Description Class类中,提供获得Field对象的方法
- @Author chenmin
- @Date 2021/4/23 9:23 *
- 获得Field对象的方法:
- 1.Field getField(String name);
- 根据成员变量名获得对应Field对象,只能获得public修饰
- 2.Field getDeclaredField(String name);
- 根据成员变量名获得对应Field对象,包含private修饰的
- 3.Field[] getFields();
- 获得所有的成员变量对应的Field对象,只能获得public的
- 4.Field[] getDeclaredFields();
- 获得所有的成员变量对应的Field对象,包含private的 */ public class FieldDemo1 {
//3.Field[] getFields() 获得所有的成员变量对应的Field对象,只能获得public的 @Test public void test1() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { //1.获得Person的类类型 Class
clazz = Person.class; //2.通过Class类类型,获得所有公开的属性 Field[] fields = clazz.getFields(); //通过Class类类型,获得构造器对象 Constructor
c = clazz.getDeclaredConstructor(); //实例化 Person person = c.newInstance(); //3.迭代 for (Field field : fields) {
System.out.println(field +" : "+field.getName());
//匹配是否是age
if(field.getName().equals("age")){
/**
* 设置值
* field.set(Object obj,Object value)
* 第一个参数:当前属性所属对象
* 第二个参数:给当前对象的指定属性,赋予的值
*/
field.set(person,20);
/**
* 获得值
* field.get(Object obj)
* 第一个参数:当前属性所属对象
*/
System.out.println("age = "+field.get(person));
}
}
System.out.println(person); }
//4.Field[] getDeclaredFields() 获得所有的成员变量对应的Field对象,包含private的 @Test public void test2() { //1.获得Person的类类型 Class
clazz = Person.class; //2.通过Class类类型,获得所有的属性(包括私有的) Field[] fields = clazz.getDeclaredFields(); //3.迭代 for (Field field : fields) { System.out.println(field + " : " + field.getName());
} }
//1.Field getField(String name) 根据成员变量名获得对应Field对象,只能获得public修饰 @Test public void test3() throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class
clazz = Person.class; //通过Class对象,获得公开的age属性对象 Field ageField = clazz.getField(“age”); System.out.println(“ageField = “+ageField); //实例化 Person person = clazz.getDeclaredConstructor().newInstance(); //设置值 ageField.set(person,22); //获取值 Object o = ageField.get(person); System.out.println(“age = “+o); //获得person System.out.println(“person = “+person); } //2.Field getDeclaredField(String name) 根据成员变量名获得对应Field对象,包含private修饰的 @Test public void test4() throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { Class
clazz = Person.class; //通过Class对象,获得公开的age属性对象 Field nameField = clazz.getDeclaredField(“name”); System.out.println(“nameField = “+nameField); //实例化 Person person = clazz.getDeclaredConstructor().newInstance(); //暴力反射,取消权限修饰符的检查 nameField.setAccessible(true);//方法或者属性用到 就在其上面加上setaccesible,调用即可 //设置值 nameField.set(person,”张三”); //获取值 String name = nameField.get(person).toString(); System.out.println(“name = “+name); //获得person System.out.println(“person = “+person); } } 反射解除泛型 JAVA泛型是在预编译时候和编译时候有效的,运行时候,是没有泛型限制的。 JAVA反射是在运行时获取类的信息,比如ArrayList list = new ArrayList(); 在编写list.add方法代码的时候,只能放string类型的参数。 但是使用反射,调用add.invoke方法的时候,可以放任何类型的对象。 这样就绕过的泛型的限制。 package com.igeek.javase.list;
import org.junit.Test;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List;
/**
- @version 1.0
- @Description
- @Author chenmin
- @Date 2021/4/23 9:59 *
- 反射:
- 1.通过反射破坏封装性
2.通过反射破坏泛型 */ public class ListDemo {
@Test public void test() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//此时集合的泛型为Integer,在编译期间要求添加进来的元素类型,只能是Integer
List<Integer> list = new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
//list.add("str");
/**
* 反射:运行期机制,通过反射破坏泛型
* 1.获取添加add方法,关注点add方法的形参
* 2.执行add方法,关注点invoke方法
*/
//第一步:获得集合的Class对象
Class<? extends List> clazz = list.getClass();
//第二步:通过Class对象,获得add的Method对象
Method addMethod = clazz.getDeclaredMethod("add", Object.class);
//第三步:执行add方法
Object o = addMethod.invoke(list, "str");
System.out.println("是否添加成功:o = "+o);
//输出集合信息
System.out.println(list);
}
}
作业 模拟Hbinate 写代码先整理思路 不要上来就写 否则一团浆糊 难搞哦! silu package com.igeek.javase.hibernate;
import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException;
/**
- @version 1.0
- @Description TODO
- @Author chenmin
- @Date 2021/4/23 10:12 *
- 需求:模拟Hibernate的操作,已知对象,要求通过对象获得其属性,
- 并完成将值存储进文件中 *
- 分析:
- 1.通过已知对象获得Class对象
- 2.通过Class对象,获得其属性对象Field
- 3.通过Field对象,来完成设置值
4.存储进文件的过程:从内存—->磁盘,选择输出流;选择高效流PrintWriter/PrintStream */ public class HibernateDemo {
public static void storage(Object obj) throws Exception {
/**
* JDK1.7之后 创建流对象之后,使用,使用完毕即释放资源
* 注意:要求资源必须实现 implements java.io.Closeable接口
*/
//从内存--->磁盘,选择输出流;选择高效流PrintWriter/PrintStream
try (
PrintWriter pw = new PrintWriter(new FileWriter("obj.txt",true));
){
//1.通过已知对象获得Class对象
Class<?> clazz = obj.getClass();
//2.通过Class对象,获得其属性对象Field
Field[] fields = clazz.getDeclaredFields();
pw.println("----------"+clazz.getSimpleName()+"----------");
for (Field field : fields) {
//3.通过Field对象,来完成设置值
field.setAccessible(true);
//4.存储进文件的过程
pw.println(field.getName() +" = "+field.get(obj));
}
}/*finally {
//JDK1.8之前的写法:释放流对象
pw.flush();
pw.close();
}*/
}
public static void main(String[] args) throws Exception {
Teacher teacher = new Teacher("张三","中级","数学");
storage(teacher);
Student student = new Student("学生",22,"001");
storage(student);
}
}
Student.java private String name; private int age; private String num; Teacher.java private String name; private String title; private String major;
打印流
/如果使用OutputStream输出数据,需要将数据变为字节数组输出,使用起来不是很方便; 为了解决使用OutputStream输出数据的不足,java提供了一套专门输出数据的类PrintStream(打印字节流)和PrintWriter(打印字符流); public class PrintStream extends FilterOutputStream implements Appendable, Closeable{} public class PrintWriter extends Writer {} 在PrintWriter类中提供了一系列的print()和println();支持各种类型的输出,不再使用write(); 在整个的操作过程中,虽然操作的形式不同,但是本质上仍然是基于Output类的方法完成的; 这种设计模式,java中叫装饰模式;相当于将一个功能不足的操作类,通过某些类的包装,形成更好用的工具类; 在实际的开发中,只要是由程序输出内容,都会采用打印流模式完成,但是需要明确的是打印流仍然需要OutputStream的支持;/ PrintStream ps1=new PrintStream(new FileOutputStream(new File(“D:\testio\a.txt”))); ps1.println(true); ps1.println(“hello”); ps1.print(1.2); ps1.println(1); ps1.println(“我是文件的结尾”); ps1.close();
第三章 注解
3.1 注解的概述
3.1.1 注解的概念
- 注解是JDK1.5的新特性。
- 注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
- 标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
- 注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
- 注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序 打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。
注解的具体实现 package com.igeek.javase.annotation;
/**
- @version 1.0
- @Description 注解
- @Author chenmin
- @Date 2021/4/23 15:59 *
- 一.注解的概念:
- 1.注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
- 2.标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
- 3.注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。
- 4。javac编译器、开发工具和其他程序可以通过 反射来了解你的类及各种元素上有无何种标记(注解)。 *
- 二.自定义注解的语法:
- 1.声明自定义的注解
- 语法:@interface 注解名{}
- 2.使用自定义的注解
- 语法:@注解名 *
- 注意:若声明注解时,未指定标注的位置,则可以在类及类的任意成员上使用
- 三.常见注解:
- @Override 作用在方法上,标注当前方法重写父类的方法
- @Test 作用在方法上,标注当前方法可以直接进行测试
- @Autowired 作用在属性上,属性的自动装配
- @FunctionalInterface 作用在接口上
@Nullable 作用在方法的形参、局部变量等上 */ @MyAnno1 @MyAnno2 public class AnnotationDemo1 {
@MyAnno1 private String name;
@MyAnno1 public void eat(){
} }
//自定义的注解 @interface MyAnno1{
} @interface MyAnno2{
} 会自定义注解 然后用
3.1.2 注解的作用 帮助文档 编译检查 框架配置
1.生成文档。这是最常见的,也是java 最早提供的注解,常用的有@param @return 等; 2.跟踪代码依赖性,实现替代配置文件功能。比较常见的是spring 2.5 开始的基于注解配置,作用就是减少配置,现在的框架基本都使用了这种配置来减少配置文件的数量; 3.在编译时进行格式检查。如@Override放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。 注解的作用就是给程序带入参数。 以下几个常用操作中都使用到了注解:
生成帮助文档:@author和@version
- @author:用来标识作者姓名。
- @version:用于标识对象的版本号,适用范围:文件、类、方法。
- 使用@author和@version注解就是告诉Javadoc工具在生成帮助文档时把作者姓名和版本号也标记在文档中。如下图:
编译检查:@Override
- @Override:用来修饰方法声明。
- 用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。如下图
框架的配置(框架=代码+配置)
- 具体使用请关注框架课程的内容的学习。
3.1.3 常见注解
- @author:用来标识作者名,eclipse开发工具默认的是系统用户名。
- @version:用于标识对象的版本号,适用范围:文件、类、方法。
- @Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。
3.2 自定义注解
3.2.1 定义格式
public @interface 注解名{
}
//如:定义一个名为Student的注解 public @interface Student {
}
3.2.2 注解的属性
- 属性的格式
- 格式1:数据类型 属性名();
- 格式2:数据类型 属性名() default 默认值;
- 属性定义示例
- // 姓名 String name(); // 年龄 int age() default 18; // 爱好 String[] hobby();
- 属性适用的数据类型
- 八种数据数据类型(int,short,long,double,byte,char,boolean,float)
- String,Class,注解类型,枚举类
- 以上类型的数组形式
package com.igeek.javase.annotation;
/**
- @version 1.0
- @Description 自定义注解(属性)
- @Author chenmin
- @Date 2021/4/23 16:18 *
- 自定义注解的语法:
- 1.声明时
- @interface 注解名{
- //属性没有默认值,一旦使用则必须提供属性值
- 数据类型 属性名(); *
- //属性一旦有默认值,则使用注解时,可以不提供
- 数据类型 属性名() default 默认值; *
- //属性value,一旦属性中只有value属性则可以省略属性名,直接写属性值
- String value();
- } *
- 2.使用时,所有未提供默认值的属性,皆需要提供值 都写上键=值的时候是不会错的 by李俊
- @注解名(属性名=”值” , 属性名={“值”,”值”,”值”}) *
- //属性名就是value,可以省略value不写;
- //有其他属性,但是有默认值,也可以不写value;
- //有其他属性,但是没有默认值,该咋写咋写
- @注解名(值) */ @MyAnno3(name=”zs” , strs={“aaa”,”bbb”,”ccc”}) public class AnnotationDemo2 { @MyAnno4(“111”) private String test;
} //自定义的注解 @interface MyAnno3{ //属性 String name(); int age() default 10; String[] strs(); }
@interface MyAnno4{//这种可以直接写上值 而不用键值对的形式 //属性 String value(); int count() default 10; } @Retention(RetentionPolicy.CLASS) @Target({ElementType.FIELD,ElementType.METHOD}) 和正常属性一样用 唯一不同的就是加上小括号 value值是必须要给的 只是要不要写value=罢了
多注解同时存在 package com.igeek.javase.annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
/**
- @version 1.0
- @Description 元注解
- @Author chenmin
- @Date 2021/4/23 16:33 *
- 元注解:
- 1.标注在自定义的注解之上的注解
- 2.任何官方提供的非元注解的定义都使用到了元注解
- 3.@Retention(RetentionPolicy.RUNTIME) 元注解
- @Retention
- 作用:用来标识注解的生命周期(有效范围)
- 可使用的值定义在RetentionPolicy枚举类中,常用值如下
- SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
- CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
- RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段 *
- 4.@Target({ElementType.METHOD}) 元注解
- @Target
- 作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。
- 可使用的值定义在ElementType枚举类中,常用值如下
- TYPE,类,接口
- FIELD, 成员变量
- METHOD, 成员方法
- PARAMETER, 方法参数
- CONSTRUCTOR, 构造方法
LOCAL_VARIABLE, 局部变量 */ //@MyAnno5 public class AnnotationDemo3 {
//自定义的注解,支持形参上使用 public void test(@MyAnno5({“110”,”120”,”119”}) String name){
}
}
@Retention(RetentionPolicy.RUNTIME)// 前面我为什么加上 @Target({ElementType.FIELD , ElementType.METHOD , ElementType.PARAMETER}) @interface MyAnno5{ String[] value(); }
3.3 使用自定义注解
3.3.1 定义和注解
- 定义一个注解:Book
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 代码实现
@Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Book { String value(); double price() default 100; String[] authros(); }
3.3.2 使用注解
- 定义类在成员方法上使用Book注解
- 使用注意事项
- 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
- 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
3.3.3 特殊属性value
/** 特殊属性value
* 如果注解中只有一个属性且名字叫value,则在使用该注解时可以直接给该属性赋值,而不需要给出属性名。
* 如果注解中除了value属性之外还有其他属性且只要有一个属性没有默认值,则在给属性赋值时
value属性名也不能省略了。
小结:如果注解中只有一个属性时,一般都会将该属性名命名为value
*/ @interface TestA{ String[] value(); int age() default 100; String name(); }
@interface TestB{ String name(); }
@TestB(name = “zzz”) @TestA(name = “yyy”,value = {“xxx”,”xxx”}) public class AnnotationDemo02 {
}
3.4 注解之元注解
元注解概述
* Java官方提供的注解
* 用来定义注解的注解
* 任何官方提供的非元注解的定义都使用到了元注解。
常用的元注解
* @Target
* 作用:用来标识注解使用的位置,如果没有使用该注解标识,则自定义的注解可以使用在任意位置。
* 可使用的值定义在ElementType枚举类中,常用值如下
TYPE,类,接口
FIELD, 成员变量
METHOD, 成员方法
PARAMETER, 方法参数
CONSTRUCTOR, 构造方法
LOCAL_VARIABLE, 局部变量
public enum ElementType { /*用于描述类、接口(包括注解类型) 或enum声明 Class, interface (including annotation type), or enum declaration / TYPE,
/** 用于描述域 Field declaration (includes enum constants) */
FIELD,
/**用于描述方法 Method declaration */
METHOD,
/**用于描述参数 Formal parameter declaration */
PARAMETER,
/**用于描述构造器 Constructor declaration */
CONSTRUCTOR,
/**用于描述局部变量 Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/**用于描述包 Package declaration */
PACKAGE,
/**
* 用来标注类型参数 Type parameter declaration
* @since 1.8
*/
TYPE_PARAMETER,
/**
*能标注任何类型名称 Use of a type
* @since 1.8
*/
TYPE_USE
}
* @Retention
* 作用:用来标识注解的生命周期(有效范围)
* 可使用的值定义在RetentionPolicy枚举类中,常用值如下
* SOURCE:注解只作用在源码阶段,生成的字节码文件中不存在
* CLASS:注解作用在源码阶段,字节码文件阶段,运行阶段不存在,默认值
* RUNTIME:注解作用在源码阶段,字节码文件阶段,运行阶段
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
} 元注解:标注使用位置和生命周期
3.5 注解解析
什么是注解解析
* 使用Java技术获得注解上数据的过程则称为注解解析。
与注解解析相关的接口
* Annotation: 注解类,该类是所有注解的父类。
* AnnotatedElement:该接口定义了与注解解析相关的方法
T getAnnotation(Class<T> annotationClass) 根据注解类型获得对应注解对象
Annotation[] getAnnotations()
* 获得当前对象上使用的所有注解,返回注解数组,包含父类继承的
Annotation[] getDeclaredAnnotations()
* 获得当前对象上使用的所有注解,返回注解数组,只包含本类的
boolean isAnnotationPresent(Class<Annotation> annotationClass)//里面是注解的类对象 即注解名.class
* 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
获取注解数据的原理
* 注解作用在哪个成员上就会得该成员对应的对象来获得注解
* 比如注解作用成员方法,则要获得该成员方法对应的Method对象
* 比如注解作用在类上,则要该类的Class对象
* 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象。
* Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口
3.5.1 需求说明
- 定义注解Book,要求如下:
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 限制注解使用的位置:类和成员方法上
- 指定注解的有效范围:RUNTIME
- 定义BookStore类,在类和成员方法上使用Book注解
- 定义TestAnnotation测试类获取Book注解上的数据
3.5.2 代码实现
1.注解Book @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Book { String value(); double price() default 100; String[] authros(); }
2.BookShelf类 @Book(value = “红楼梦”,authros = {“曹雪芹”}) public class BookShelf {
@Book(value = "西游记",authros = {"吴承恩","白求恩"},price = 200)
public void showBook(){
}
}
3.TestAnnotation类 /** 什么是注解解析
* 使用Java技术获得注解上数据的过程则称为注解解析。
与注解解析相关的接口
* Annotation: 注解类,该类是所有注解的父类。
* AnnotatedElement:该接口定义了与注解解析相关的方法
T getAnnotation(Class<T> annotationClass) 根据注解类型获得对应注解对象
Annotation[] getAnnotations()
* 获得当前对象上使用的所有注解,返回注解数组,包含父类继承的
Annotation[] getDeclaredAnnotations()
* 获得当前对象上使用的所有注解,返回注解数组,只包含本类的
boolean isAnnotationPresent(Class<Annotation> annotationClass)
* 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
获取注解数据的原理
* 注解作用在哪个成员上就会得该成员对应的对象来获得注解
* 比如注解作用成员方法,则要获得该成员方法对应的Method对象
* 比如注解作用在类上,则要该类的Class对象
* 比如注解作用在成员变量上,则要获得该成员变量对应的Field对象。
* Field,Method,Constructor,Class等类都是实现了AnnotatedElement接口
/ public class AnnotationDemo04 { / 获得类上使用的注解数据 */ @Test public void test02() throws Exception { // 获得Class对象 Class c = BookShelf.class; // 判断类上是否使用Book注解 if(c.isAnnotationPresent(Book.class)){ // 根据注解的Class对象获得对应的注解对象 Book annotation = (Book) c.getAnnotation(Book.class); // 获得书名 System.out.println(annotation.value()); // 获得作者 System.out.println(Arrays.toString(annotation.authros())); // 获得价格 System.out.println(annotation.price()); } // 获得当前对象上使用的所有注解,返回注解数组 // Annotation[] annotations = c.getAnnotations(); Annotation[] annotations = c.getDeclaredAnnotations(); System.out.println(Arrays.toString(annotations)); }
/*
获得成员方法上注解的数据
*/
@Test
public void test01() throws Exception {
// 获得Class对象
Class c = BookShelf.class;
// 获得成员方法对应的Method对象
Method m = c.getMethod("showBook");
// 根据注解的Class对象获得对应的注解对象
Book annotation = m.getAnnotation(Book.class);
// 获得书名
System.out.println(annotation.value());
// 获得作者
System.out.println(Arrays.toString(annotation.authros()));
// 获得价格
System.out.println(annotation.price());
}
}
3.6 注解案例
3.6.1 案例说明
- 模拟Junit测试的@Test
3.6.2 案例分析
- 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰方法,且在运行时可以获得。
- 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加上@MyTest注解。
- 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。
3.6.3 案例代码
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MyTest {
}
public class TestMyTest {
@MyTest
public void tests01(){
System.out.println("test01");
}
public void tests02(){
System.out.println("test02");
}
@MyTest
public void tests03(){
System.out.println("test03");
}
}
/**
- @author cp
- @version 1.0
- @Package com.igeek
- @date 2018/6/23 下午9:10
*/
public class AnnotationDemo05 {
public static void main(String[] args) throws Exception {
} }// 获得Class对象
Class c = TestMyTest.class;
Object obj = c.newInstance();
// 获得所有成员方法
Method[] methods = c.getMethods();
for(Method m:methods){
// 判断m方法是否使用了MyTest注解
if(m.isAnnotationPresent(MyTest.class)){
// 调用方法
m.invoke(obj);
}
}
课后作业 作业1: /**
- 需求:
- 定义注解Book,要求如下:
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 限制注解使用的位置:类和成员方法上
- 指定注解的有效范围:RUNTIME
- 定义BookStore类,在类和成员方法上使用Book注解
- 定义TestAnnotation测试类获取Book注解上的数据 */
package com.igeek.javase.annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /*
- /**
- 需求:
- 定义注解Book,要求如下:
- 包含属性:String value() 书名
- 包含属性:double price() 价格,默认值为 100
- 包含属性:String[] authors() 多位作者
- 限制注解使用的位置:类和成员方法上
- 指定注解的有效范围:RUNTIME
- 定义BookStore类,在类和成员方法上使用Book注解
- 定义TestAnnotation测试类获取Book注解上的数据 */ @Explain(value = “wangbanianing”,authors = {“junjun”,”lili”}) public class BookStore { @Explain(value = “老人图还”,price = 11,authors = {“junjun”,”lili”}) public void getBook() {}
}
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) @interface Explain{ String value(); double price() default 100; String[] authors(); }
@Test
public void test1() {
Class<BookStore> bookStoreClass = BookStore.class;
if (bookStoreClass.isAnnotationPresent(Explain.class)) {
// Annotation[] annotations = bookStoreClass.getAnnotations();
//根据Class对象获得注释的对象
Explain e = bookStoreClass.getAnnotation(Explain.class);
System.out.println(e.value());
System.out.println(e.price());
System.out.println(Arrays.toString(e.authors()));
}
}
//测试方法method
@Test
public void test2() throws NoSuchMethodException {
/*
* 1 先获得类对象
* 2 判断是否使用了指定的对象
* 3 通过类对象获得注释对象
* 4 通过注释对象获得注释的内容*/
Class<BookStore> bookStoreClass = BookStore.class;
//获得类的方法的对象
Method getBook = bookStoreClass.getDeclaredMethod("getBook");
Explain e = getBook.getAnnotation(Explain.class);
System.out.println(e.value());
System.out.println(e.price());
System.out.println(Arrays.toString(e.authors()));
}
}
/*
- jieguo
- wangbanianing 100.0 [junjun, lili]
作业2: /**
- 需求:
- 模拟junit的@Test注解:
- 1.此注解只能添加在方法上
- 2.存活直至运行期 *
- 期望效果:只有含@MyTest注解的方法,才可以运行 */ package com.igeek.javase.annotation;
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
public class AnnotationDemo4 { /**
* 需求:
* 模拟junit的@Test注解:
* 1.此注解只能添加在方法上
* 2.存活直至运行期
*
* 期望效果:只有含@MyTest注解的方法,才可以运行
*
*/
@MyTest
private void test1()
{
System.out.println("我用了注解1");
}
@MyTest
public void test2()
{
System.out.println("我用了注解2");
}
@MyTest
public void test3()
{
System.out.println("我用了注解3");
}
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException {
Class<AnnotationDemo4> a4class = AnnotationDemo4.class;//只是类的对象
AnnotationDemo4 instance = a4class.getDeclaredConstructor().newInstance();//调用方法需要实例化对象
Method[] declaredMethods = a4class.getDeclaredMethods();
for (Method method : declaredMethods) {
/* boolean isAnnotationPresent(Class<Annotation> annotationClass)
* 判断当前对象是否使用了指定的注解,如果使用了则返回true,否则false
*注意是两个对象之间 */
method.setAccessible(true);
if (method.isAnnotationPresent(MyTest.class))//
{
method.invoke(instance);//方法调用invoke方法 动态调用
}
}
}
} @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface MyTest{}
第四章 代理模式【Proxy Pattern】
为什么要有“代理”?生活中就有很多例子,例如委托业务等等,代理就是被代理者没有能力或者不愿意去完成某件事情,需要找个人代替自己去完成这件事,这才是“代理”存在的原因。例如,我现在需要出国,但是我不愿意自己去办签证、预定机票和酒店(觉得麻烦 ,那么就可以找旅行社去帮我办,这时候旅行社就是代理,而我自己就是被代理了。 目标对象 target 代理对象 proxy 代理模式有不同的形式, 主要有三种 静态代理、动态代理 (JDK代理、接口代理)和 Cglib代理 (可以在内存动态的创建对象,而不需要实现接口, 他是属于动态代理的范畴) 。静动草
4.1 JDK动态代理
4.1.1 动态代理概述
动态代理简单来说是:拦截对真实对象方法的直接访问,增强真实对象方法的功能 动态代理详细来说是:代理类在程序运行时创建的代理对象被称为动态代理,也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。也就是说你想获取哪个对象的代理,动态代理就会动态的为你生成这个对象的代理对象。动态代理可以对被代理对象的方法进行增强,可以在不修改方法源码的情况下,增强被代理对象方法的功能,在方法执行前后做任何你想做的事情。动态代理技术都是在框架中使用居多,例如:Struts2、Spring和Hibernate等后期学的一些主流框架技术中都使用了动态代理技术。 注意: 1.代理对象,不需要实现接口,但是目标对象要实现接口,否则不能用动态代理 2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象 3.动态代理也叫做:JDK代理、接口代理
4.1.2 案例引出
现在,假设我们要实现这样的需求:在企业的大型系统中,每个业务层方法的执行,都需要有对应的日志记录,比如这个方法什么时候调用完成的,耗时多久等信息,根据这些日志信息,我们可以看到系统执行的情况,尤其是在系统出现错误的时候,这些日志信息就显得尤为重要了。 现在有一种实现思路是这样的,业务层实现类代码如下: public interface SchoolService{ String login(String loginName, String passWord); String getAllClazzs(); }
public class SchoolServiceImpl implements SchoolService {
@Override
public String login(String loginName, String passWord) {
// 方法执行的开始时间点
long startTimer = System.currentTimeMillis();
try {
Thread.sleep(500);
if("admin".equals(loginName) && "123456".equals(passWord)){
return "success";
}
} catch (Exception e) {
throw new RuntimeException("登录异常");
}
long endTimer = System.currentTimeMillis();
// 在什么时刻执行完,花费了多长时间完成
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("login方法执行->"+sdf.format(endTimer)+",耗时:"+(endTimer - startTimer));
return "登录名称或者密码不正确";
}
@Override
public String getAllClazzs() {
// 时间点
long startTimer = System.currentTimeMillis();
try {
Thread.sleep(1000);
return "返回了所有的班级(1班,2班,3班)";
} catch (Exception e) {
throw new RuntimeException("查询班级异常");
}finally{
long endTimer = System.currentTimeMillis();
// 在什么时刻执行完,花费了多长时间完成
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("getAllClazzs方法执行->"+sdf.format(endTimer)+",耗时:"+(endTimer - startTimer));
}
}
} . 我们在业务层的方法每次执行的时候都去记录了开始时间和结束时间,这样虽然可以记录每个方法运行的截止时间和耗时情况,但是代码显得很臃肿,这些日记记录代码本身也是与业务功能无关的代码。
4.1.3 使用动态代理优化代码
那么有没有一种方式可以解决这个问题呢? 动态代理就是解决此类问题非常好的实现手段,通过动态代理我们可以为该业务层实现类对象提供一个动态的代理对象。该代理对象,可以为实现类的所有方法进行代理,并对代理的方法功能进行增强。也就是说只要调用了被代理实现类对象的方法,该方法的执行会先进入到代理对象中去,代理对象可以在该方法执行前记录开始时间,然后去触发该方法的执行,在方法执行完成以后再由代理对象去记录结束时间然后计算时间差作为日志记录,因为方法的日记记录由代理完成了,所以被代理对象的方法就无需自己单独记录日志操作了。这样就产生了一种非常好的设计模型。
现在我们来使用动态代理写一个日志记录的代理类: 代码如下: public class LogProxy { // 提供一个方法,用于生产需要被代理对象的代理对象。 public static Object getProxy(Object obj) { return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj .getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// 先记录开始时间点
long startTimer = System.currentTimeMillis();
try {
// 真正去触发被代理对象中该方法的执行
return method.invoke(obj, args);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
long endTimer = System.currentTimeMillis();
// 在什么时刻执行完,花费了多长时间完成
SimpleDateFormat sdf = new SimpleDateFormat(
"yyyy-MM-dd HH:mm:ss");
System.out.println(method.getName() + "方法执行->"
+ sdf.format(endTimer) + ",耗时:"
+ (endTimer - startTimer));
}
}
});
}
}
获得系统时间毫秒数的三种方式 1, System.currentTimeMillis(); 2, new Date().getTime(); 3, Calender.getInstance().getTimeMillis();
System 工具类
public static long currentTimeMillis() // 获得当前时间的毫秒值 long start = System. currentTimeMillis ();
Date 类
//创建时间对象,获得当前时间 Date d = new Date(); //获得当前时间的毫秒值 System. out .println(d.getTime());
Calendar 类
// 获得日历对象 Calendar c = Calendar. getInstance (); // 获得当前时间的毫秒值 long todayTime = c.getTimeInMillis();
4.1.4 重点类和方法
在上述代码中 getProxy 方法即是用于获取某个实现类对象的一个代理对象。在该代码中,如果要了解 Java 动态代理的机制,首先需要了解以下相关的类或接口: java.lang.reflect.Proxy:这是 Java 动态代理机制的主类,它提供了一个静态方法来为一组接口的实现类动态地生成代理类及其对象。
newProxyInstance方法的三个参数的详解: 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 public static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
1、obj.getClass().getClassLoader()目标对象通过getClass方法获取类的所有信息后,调用getClassLoader() 方法来获取类加载器。获取类加载器后,可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即Java虚拟机中,以便运行时需要! //类加载器
2、obj.getClass().getInterfaces()获取被代理类的所有接口信息,以便于生成的代理类可以具有代理类接口中的所有方法。 //目标对象实现接口的类类型
3、InvocationHandler 这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类方法的处理以及访问. //执行器
重写invoke方法真正执行
invoke方法的参数 public Object invoke(Object proxy, Method method, Object[] args) 1、Object proxy:生成的代理对象,在这里不是特别的理解这个对象,但是个人认为是已经在内存中生成的proxy对象。 2、Method method:被代理的对象中被代理的方法的一个抽象。 3、Object[] args:被代理方法中的参数。这里因为参数个数不定,所以用一个对象数组来表示。
代理类定义完成以后业务层实现类的方法就无需再自己申明日记记录的代码了,因为代理对象会帮助做日志记录,修改后实现类代码如下: public class SchoolServiceImpl implements SchoolService {
@Override
public String login(String loginName, String passWord) {
try {
Thread.sleep(500);
if("admin".equals(loginName) && "123456".equals(passWord)){
return "success";
}
} catch (Exception e) {
throw new RuntimeException("登录异常");
}
return "登录名称或者密码不正确";
}
@Override
public String getAllClazzs() {
try {
Thread.sleep(1000);
return "返回了所有的班级(1班,2班,3班)";
} catch (Exception e) {
throw new RuntimeException("查询班级异常");
}
}
}
开始使用动态代理去访问方法: public class TestMain { public static void main(String[] args) { // 获取业务层实现类对象的代理对象,那么业务层实现类对象就会被代理了 SchoolService schoolService = (SchoolService) LogProxy.getProxy(new SchoolServiceImpl());
System.out.println(schoolService.login("admin", "1234256"));
System.out.println(schoolService.getAllClazzs());
}
} 此代码中业务层对象已经是被代理的了,那么以后调用业务层对象的方法时,方法的调用会先被代理对象处理,代理会先记录方法执行的开始时间,然后通过method.invoke(obj, args)去真正触发该方法的执行,接下来代理对象进行方法结束时间的记录和日志的输出即可。这样整个过程就通过代理完美的实现了。
4.1.5 总结
动态代理非常的灵活,可以为任意的接口实现类对象做代理 动态代理可以为被代理对象的所有接口的所有方法做代理,动态代理可以在不改变方法源码的情况下,实现对方法功能的增强,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。 动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。 动态代理同时也提高了开发效率。 缺点:只能针对接口的实现类做代理对象,普通类是不能做代理对象的。
4.2 静态代理
4.2.1 概念
静态代理在使用时,需要定义接口或者父类,被代理对象(即目标对象)与代理对象一 起实现相同的接口或者是继承相同父类。 注意:代理对象与目标对象要实现相同的接口,然后通过调用相同的方法来 调用目标对象的方法。
4.2.2 静态代理优缺点
优点:在不修改目标对象的功能前提下, 能通过代理对象对目标功能扩展 缺点:因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,一旦接口增加方法,目标对象与代理对象都要维护 牵一发而动全身 jdk1.8之后接口里面内容变了 里面有默认方法 实现类可以不实现default
4.3 Cglib代理
https://blog.csdn.net/qq_33661044/article/details/79767596
4.3.1 概念
静态代理和JDK代理模式都要求目标对象是实现一个接口,但是有时候目标对象只 是一个单独的对象,并没有实现任何的接口,这个时候可使用目标对象子类来实现 代理-这就是Cglib代理。 Cglib代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能扩展, 有些书也将Cglib代理归属到动态代理。 Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接 口.它广泛的被许多AOP的框架使用,例如Spring AOP,实现方法拦截。 在AOP编程中如何选择代理模式:若目标对象需要实现接口,用JDK代理;若目标对象不需要实现接口,用Cglib代理。 Cglib包的底层是通过使用字节码处理框架ASM来转换字节码并生成新的类。 CGLib (Code Generation Library) 是一个强大的、高性能、高质量的 Code 生成类库。它可以在运行期扩展 Java 类与实现 Java 接口。Hibernate 用它来实现 PO 字节码的动态生成。CGLib 比 Java 的 java.lang.reflect.Proxy 类更强的在于它不仅可以接管接口类的方法,还可以接管普通类的方法。
CGLib 的底层是Java字节码操作框架 —— ASM。 https://www.oschina.net/p/cglib?hmsr=aladdin1e1
4.3.2 Cglib代理模式实现步骤
1) 需要引入cglib的jar文件 //引入依赖 注意事项: 2) 在内存中动态构建子类,注意代理的类不能为final,否则报错java.lang.IllegalArgumentException 3) 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法. 4) 在内存中动态构建子类,注意代理的类需要提供一个公开的无参的构造方法,否则将无法创建代理对象
4.3.3 相关API
代理对象的类需要实现接口 implements MethodInterceptor,重写方法: public Object intercept( Object proxy, Method method, Object[] args, MethodProxy methodProxy ) 原理:一旦使用Cglib
获得代理对象的方法中,需要使用Enhancer类 public void setSuperclass(Class superclass) {}
public void setCallback(final Callback callback) {}
public Object create() {}
第五章 Base64
5.1 Base64概述
Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。 在Java 8中,Base64编码已经成为Java类库的标准。 Java 8 内置了 Base64 编码的编码器和解码器。 Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
- 基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
- URL:输出映射到一组字符A-Za-z0-9+_,输出是URL和文件。
- MIME:输出隐射到MIME友好格式。输出每行不超过76字符,并且使用’\r’并跟随’\n’作为分割。编码输出最后没有行分割。
5.2 Base64内嵌类和方法描述
内嵌类
序号 | 内嵌类 & 描述 |
---|---|
1 | static class Base64.Decoder该类实现一个解码器用于,使用 Base64 编码来解码字节数据。 |
2 | static class Base64.Encoder该类实现一个编码器,使用 Base64 编码来编码字节数据 |
方法
序号 | 方法名 & 描述 |
---|---|
1 | static Base64.Decoder getDecoder()返回一个 Base64.Decoder ,解码使用基本型 base64 编码方案。 |
2 | static Base64.Encoder getEncoder()返回一个 Base64.Encoder ,编码使用基本型 base64 编码方案。 |
3 | static Base64.Decoder getMimeDecoder()返回一个 Base64.Decoder ,解码使用 MIME 型 base64 编码方案。 |
4 | static Base64.Encoder getMimeEncoder()返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案。 |
5 | static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator)返回一个 Base64.Encoder ,编码使用 MIME 型 base64 编码方案,可以通过参数指定每行的长度及行的分隔符。 |
6 | static Base64.Decoder getUrlDecoder()返回一个 Base64.Decoder ,解码使用 URL 和文件名安全型 base64 编码方案。 |
7 | static Base64.Encoder getUrlEncoder()返回一个 Base64.Encoder ,编码使用 URL 和文件名安全型 base64 编码方案。 |
注意:Base64 类的很多方法从 java.lang.Object 类继承。
5.3 Base64代码演示
public static void main(String args[]) { try {
// 使用基本编码
String base64encodedString = Base64.getEncoder().encodeToString("abc?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (基本) :" + base64encodedString);
// 解码
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8"));
// URL
base64encodedString = Base64.getUrlEncoder().encodeToString("abc?java8".getBytes("utf-8"));
System.out.println("Base64 编码字符串 (URL) :" + base64encodedString);
// MIME
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; ++i) {
stringBuilder.append(UUID.randomUUID().toString());
}
byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 编码字符串 (MIME) :" + mimeEncodedString);
}catch(UnsupportedEncodingException e){
System.out.println("Error :" + e.getMessage());
}
}
运行结果: Base64 编码字符串 (基本) :aXRoZWltYT9qYXZhOA== 原始字符串: abc?java8 Base64 编码字符串 (URL) :aXRoZWltYT9qYXZhOA== Base64 编码字符串 (MIME) :ODM1MWI4MzMtZGZmZi00MDAwLTkwNTAtZjUxMjkzODMwY2E2YTVjZmMwN2QtYzM0My00ZjdhLTll MDktMDFkMWZmZjA0MWRkOTE5Nzk0YzMtNTkyOC00Yjk0LThhYWEtMmIyNmFhN2Y3YzFmY2I2NDNl ZmEtY2ZmNC00NTU4LWIzZDktZjAzMmE1M2FiOWM1ZDAyMzAyYzktZTM3MS00MDk3LWI2YWEtZTMz MzZlMjE4NDdkZmEzYTA0NjktYWFhZC00M2ZiLTkzYTQtYTA0ZDIzMjIxY2RiMTMxYTU1MzgtZjZi OS00NDcyLWJjOTYtZjViODVkNzdkNjMyODNhZTJhNDktZmU0Ni00ZDI5LWI0MDUtZWRkZGFmYjM2 MDliZTcyNWMxY2ItMWE3Ny00NmM4LTk2ZWUtZjBhYjY4YzgwMzU3N2Q3YWFiMTYtNzBjYi00MzNh LTkxN2UtNzJmNWE0MjQzMGUx
第六章 单例模式
package com.igeek.javase.pattern.singleton;
/**
- @version 1.0
- @Description 设计模式之一 - 单例模式 - 饿汉式单例
- @Author chenmin
- @Date 2021/4/23 11:05 *
- 单例模式:整个应用中只存在唯一的实例
- 使用场景:业务场景(ATM银行实例是单例模式)、Spring的IOC容器(容器中所有的实例bean唯一的)等等 *
- 1.饿汉式单例
- 2.懒汉式单例 *
- 线程安全
- 3.静态内部类的单例
4.线程安全的单例 */ public class SingletonDemo1 {
//私有的静态的本类的实例 private static SingletonDemo1 instance = new SingletonDemo1();
//私有化的构造器 private SingletonDemo1(){ }
//公开的静态的获得本类实例的方法 public static SingletonDemo1 getInstance(){
return instance;
} }
public class SingletonDemo2 {
//私有的静态的本类的实例
private static SingletonDemo2 instance;
//私有化的构造器
private SingletonDemo2(){
System.out.println(Thread.currentThread().getName());
}
//公开的静态的获得本类实例的方法
public static SingletonDemo2 getInstance(){
if(instance == null){
instance = new SingletonDemo2();
}
return instance;
}
}
public class SingletonDemo3 {
//私有的静态的本类的实例
private static volatile SingletonDemo3 instance;//清除内存屏障 就是在每个内存空间操作后重新查询主内存
//私有化的构造器
private SingletonDemo3(){
System.out.println(Thread.currentThread().getName());
}
// 公开的静态的获得本类实例的方法
// 不能保证所有操作的原子性 - 加synchronized锁
// 提高效率 - DCL double checked lock 双重检查锁
public static /*synchronized*/ SingletonDemo3 getInstance(){
if(instance==null){
//对象锁 SingletonDemo3.class
synchronized(SingletonDemo3.class){
if(instance == null){
instance = new SingletonDemo3();
/**
* instance = new SingletonDemo3(); 可能会发生指令重排
* 1.创建引用instance
* 2.开辟内存,存储对象
* 3.将引用指向对象
*
* 期望:123
* 可能会发生:132... 直接将实例指向对象,但是对象未实例化
* 解决方案:禁止指令重排,加'内存屏障',volatile
* 并发访问:可见性、原子性、重排性
* volatile支持可见性、禁止重排性
* synchronized 支持原子性
*/
}
}
}
return instance;
}
public class SingletonDemo4 {
//私有化的构造器
private SingletonDemo4(){
System.out.println(Thread.currentThread().getName());
}
//静态的内部类提供唯一实例
static class SingleInnerClass{
//私有的静态的本类的实例
private static SingletonDemo4 instance = new SingletonDemo4();
}
//公开的静态的获得本类实例的方法
public static SingletonDemo4 getInstance(){
return SingleInnerClass.instance;
}
}
DCL 装逼神器 // 公开的静态的获得本类实例的方法 // 不能保证所有操作的原子性 - 加synchronized锁 // 提高效率 - DCL double checked lock 双重检查锁 检查了两次 public static SingletonDemo1 getInstance(){ if(instance==null){ //对象锁 SingletonDemo3.class synchronized(SingletonDemo1.class){ if(instance == null){ instance = new SingletonDemo1(); } } } return instance; }
总结一下:获得所有的私有的东西 并且对其进行相关的操作需要对其进行 暴力反射 具体就是 setAccessible(true);
基本上,把synchronized移动到代码内部是没有什么意义的,每次调用getInstance()还是要进行同步。同步本身没有问题,但是我们只希望在第一次创建instance实例的时候进行同步,因此有了下面的写法——双重锁定检查(DCL,Double Check Lock)。
public class Singleton {
private static Singleton instance = null;
public static Singleton getInstance() {
if(null == instance) { // 线程二检测到instance不为空
synchronized (Singleton.class) {
if(null == instance) {
instance = new Singleton(); // 线程一被指令重排,先执行了赋值,但还没执行完构造函数(即未完成初始化)
}
}
}
return instance; // 后面线程二执行时将引发:对象尚未初始化错误
}
}
看样子已经达到了要求,除了第一次创建对象之外,其它的访问在第一个if中就返回了,因此不会走到同步块中,已经完美了吗?
如上代码段中的注释:假设线程一执行到instance = new Singleton()这句,这里看起来是一句话,但实际上其被编译后在JVM执行的对应会变代码就发现,这句话被编译成8条汇编指令,大致做了三件事情:
1)给instance实例分配内存; 2)初始化instance的构造器; 3)将instance对象指向分配的内存空间(注意到这步时instance就非null了)
如果指令按照顺序执行倒也无妨,但JVM为了优化指令,提高程序运行效率,允许指令重排序。如此,在程序真正运行时以上指令执行顺序可能是这样的:
a)给instance实例分配内存; b)将instance对象指向分配的内存空间; c)初始化instance的构造器;
这时候,当线程一执行b)完毕,在执行c)之前,被切换到线程二上,这时候instance判断为非空,此时线程二直接来到return instance语句,拿走instance然后使用,接着就顺理成章地报错(对象尚未初始化) 具体来说就是synchronized虽然保证了线程的原子性(即synchronized块中的语句要么全部执行,要么一条也不执行),但单条语句编译后形成的指令并不是一个原子操作(即可能该条语句的部分指令未得到执行,就被切换到另一个线程了)。
根据以上分析可知,解决这个问题的方法是:禁止指令重排序优化,即使用volatile变量。
public class Singleton {
private volatile static Singleton instance = null;
public static Singleton getInstance() {
if(null == instance) {
synchronized (Singleton.class) {
if(null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
} 将变量instance使用volatile修饰即可实现单例模式的线程安全。
反编译过程
第七章类类型 class对象 什么是类?可以理解为。class文件 某种意义上来说,java有两种对象:实例对象和Class对象。每个类的运行时的类型信息就是用Class对象表示的。它包含了与类有关的信息。其实我们的实例对象就通过Class对象来创建的。Java使用Class对象执行其RTTI(运行时类型识别,Run-Time Type Identification),多态是基于RTTI实现的 每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class) https://blog.csdn.net/feicongcong/article/details/84823030 Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。一个类被加载到内存并供我们使用需要经历如下三个阶段: 请简述 一个类被加载到内存并供我们使用 的过程:
- 加载,这是由类加载器(ClassLoader)执行的。通过一个类的全限定名来获取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类的java.lang.Class对象。
- 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且如果必需的话,将常量池中的符号引用转化为直接引用。
- 初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。
所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)。当程序创建第一个对类的静态成员的引用时,就会加载这个类。使用new创建类对象的时候也会被当作对类的静态成员的引用。因此java程序程序在它开始运行之前并非被完全加载,其各个类都是在必需时才加载的。这一点与许多传统语言都不同。动态加载使能的行为,在诸如C++这样的静态加载语言中是很难或者根本不可能复制的。 在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。一旦某个类的Class对象被载入内存,我们就可以它来创建这个类的所有对象。
invoke(调用)
就是调用Method类代表的方法。可以实现动态调用,例如可以动态的传入参数,可以把方法参数化。 可以把方法参数化invoke(class, method),比如Test类里有一系列名字相似的方法setValue1、setValue2等等,可以把方法名存进数组v[],然后循环里invoke(test,v[i]),就顺序调用了全部setValue。 此外,invoke()对带有指定参数的指定对象调用,个别参数被自动解包,与基本形参相匹配,基本参数和引用参数都随需服从方法调用转换。如下 public Object invoke(Object obj,Object… args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。 如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。 如果底层方法是实例方法,则使用动态方法查找来调用它,这一点记录在 Java Language Specification, Second Edition 的第 15.12.4.4 节中;在发生基于目标对象的运行时类型的重写时更应该这样做。 如果底层方法是静态的,并且尚未初始化声明此方法的类,则会将其初始化。 如果方法正常完成,则将该方法返回的值返回给调用者;如果该值为基本类型,则首先适当地将其包装在对象中。但是,如果该值的类型为一组基本类型,则数组元素不 被包装在对象中;换句话说,将返回基本类型的数组。如果底层方法返回类型为 void,则该调用返回 null。