反射
1类加载
1类的加载,连接,初始化
系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类
JVM和类
当调用java命令运行某个Java程序时,该命令会启动一个Java虚拟机进程,不管
该Java程序有多么复杂,给程序启动了多少个线程,他们都处于Java虚拟机进程
同一个JVM的所有线程、所有变量都处于同一个进程里,他们都是用该JVM进程
的内存区,当系统出现以下几种情况时,JVM进程将被终止
1.程序运行到最后正常结束
2.程序运行到System.exit()或者Runtime.getRuntime().exit()代码结束程序
3.程序执行过程中遇到未捕获的异常或错误而结束
4.程序所在的平台强制结束了JVM进程
JVM进程结束,该进程在内存中的状态将会丢失
2类加载
【类加载】
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化
三个步骤来对该类进行初始化,如果没有意外,JVM会连续完成这三个步骤,所以有的时候把这三个
步骤统称为类加载或者类初始化
Class类
类是对一批对象的抽象,但是类也是一种对象,就例如概念是用来描述事物的,但是概念本身也是一种事物
所以概念本身也需要被描述
所以描述类的类在Java中就叫做Class,在java.lang.Class包下
Class是对类的一种描述在Java中的一种对象体现
** 类加载器**
类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,
JVM提供的这些类加载器统称成为系统类加载器,除此之外,开发者可以通过继承ClassLoader基类
来创建自己的类加载
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源
1.从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式
2.从JAR包加载class文件,这种方式也是很常见的,DOM4J编程时用到的数据库驱动类就放在
JAR文件中,JVM可以从JAR文件中直接加载该class文件
3.通过网络加载class文件
4.把一个Java件动态编译,并执行加载
类加载器通常无须等到"首次使用"该类时才加载该类,Java虚拟机规范允许系统预先加载某些类
【类连接】
当类被加载之后,系统为之生成一个Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制
数据合并到JRE中,类连接又可分为如下三个阶段
1.验证: 验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
2.准备: 类准备阶段则负责为类的类变量分配内存,并设置默认值
3.解析: 将类的二进制数据中的符号引用替换成直接引用
【类的初始化】
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化
对类变量进行初始化的方式有两种:
1.声明类变量时指定初始值
2.使用静态代码块为类变量指定初始化值
注意: 声明类变量时指定初始值以及使用静态代码块为类变量指定初始化值时,JVM会按照语句在程序中的排列顺序依次执行它们.
JVM初始化一个类包含如下几个步骤
1.假如这个类还没有被加载和连接,则程序先加载并连接该类
2.假如该类的直接父类还没有被初始化,则先初始化其直接父类
3.假如类中有初始化语句,则系统依次按照顺序执行这些初始化语句
4.当执行第二步骤时,系统对直接父类的初始化步骤也遵循1~3,如果直接父类又有直接父类,
则系统再次重复这三个步骤来初始化父类…依次类推,所以JVM最先初始化的总是Object类
注意: 当程序主动使用任何一个类时,系统会保证该类或者该类的所有的父类都被初始化
类的初始化时机
当Java程序首次通过下面6种方式来使用某个类或者接口的时候,系统就会初始化该类或接口
1.创建类的实例,为某个类创建实例的方式包括: 使用new操作符来创建实例,通过反射来创建实例,通过反序列化来创建实例
2.调用某个类的类方法
3.访问某个类的类变量或者为该类变量赋值
4.使用反射方式来强制创建某个类或者接口对应的Class对象 Class c = Class.forName(“com.sxt.Student”);
5.初始化某个类的子类,当初始化某个类的子类时,该子类的所有父类都会被初始化
6.直接使用java.exe命令来运行某个主类,当运行某个主类时,程序会先初始化该主类
注意:
1.final修饰的类变量,如果该类变量在编译时期就可以确定下来,那么这个类变量相当于"宏变量",Java编译器会在编译时
直接把这个类变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类被初始化
2.当使用ClassLoader类的loadClass方法来加载某个类时,该方法只是加载该类,并不会执行类的初始化,使用
Class.forName()静态方法才会导致强制初始化该类
public class ReflectDemo02 {
public static void main(String[] args) throws ClassNotFoundException {
TestInit.a = 20;
System.out.println(TestInit2.a);
System.out.println(TestInit2.b);
System.out.println("--------------------");
System.out.println(TestInitSon.a);
System.out.println(TestInitSon.b);
System.out.println("--------------------");
System.out.println(TestInit3.CONSTANT);
System.out.println(TestInit3.B);
}
}
class TestInit {
// 1.声明类变量时指定初始值
static int a = 10;
static int b;
static int c; // 0
static {
// 2.使用静态代码块为类变量指定初始化值
b = 20;
}
}
class TestInit2 {
static {
// 2.使用静态代码块为类变量指定初始化值
b = 5;
System.out.println("静态代码块");
}
// 1.声明类变量时指定初始值
static int a = 10;
static int b = 20;
static int c; // 0
}
class TestInitFather {
static {
// 2.使用静态代码块为类变量指定初始化值
b = 5;
System.out.println("静态代码块 Father");
}
// 1.声明类变量时指定初始值
static int a = 10;
static int b = 20;
static int c; // 0
}
class TestInitSon extends TestInitFather{
// 1.声明类变量时指定初始值
static int e = 10;
static int f;
static int g; // 0
static {
// 2.使用静态代码块为类变量指定初始化值
f = 20;
System.out.println("静态代码块 Son");
}
}
class TestInit3 {
static {
System.out.println("TestInit3 静态代码块");
}
public static final String CONSTANT = "常量测试";
static int A = 10;
public static final int B = A + 10;
}
class TestInit4 {
static {
System.out.println("TestInit4 类的静态代码块");
}
}
3 类加载器
类加载器是用来将.class文件(可能是磁盘上,也可能是网络上)加载到内存中,并为之生成对应的Class对象
1.类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的实例放到堆(Heap)中,
作为开发人员访问方法区中类定义的入口和切入点。
2.在堆区也创建了所有加载成功的类所对应的类加载器,也可以获取
如下代码
class Test {
public void method() {
// 获取当前对象的类加载器
ClassLoader c = this.getClass().getClassLoader();
System.out.println(c);
// 获取执行类的类加载器
System.out.println(Integer.class.getClassLoader());
1 类加载机制
类加载器负责载入所有的类,系统为所有被载入内存中的类生成一个Class实例
在Java中,一个类使用其全限定类名(包括包名和类名)作为唯一标识
在JVM中,一个类使用其全限定类名(包括包名和类名)和类加载器对象作为唯一标识
JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构
Bootstrap ClassLoader 根类加载器
Extension ClassLoader 扩展类加载器
System ClassLoader 系统类加载器
用户类加载器
用户类加载器 --> 系统类加载器 --> 扩展类加载器 --> 根类加载器
自定义类加载器通过继承ClassLoader类来实现
Bootstrap ClassLoader称为根类加载器/引导类加载器/原始类加载器,负责加载Java的核心类.
JVM的类加载机制有如下三种情况
1.父类委托: 尝试先让父类加载器加载,父类加载器无法加载,尝试自己的类路径下加载 【双亲委派模型】
2.全盘负责: 加载某个Class的时候,该Class所依赖的和引用的其他Class都由该类加载器载入
3.缓存机制: 所有加载过的Class都会被缓存,当程序需要用到某个Class的时候,先在缓存中搜索
是否存在,不存在就读取并且转换成Class对象保存到缓冲区中,这就是为什么Class文件修改后需要
重新启动JVM,程序修改才会生效
2 获取JVM的类加载器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
Enumeration resources = systemLoader.getResources("");
ClassLoader parent = systemLoader.getParent();
System.getProperty(“java.ext.dirs”);
注意: 扩展类加载器的父类是null,并不是根类加载器,这是因为根类加载器并没有继承 ClassLoader
抽象类,所以扩展类加载器的getParent方法返回null,但是实际上扩展类加载器的父类加载器是
根类加载器,只是根类加载器并不是Java实现的,Java程序员也无需访问根类加载器
3【类加载器过程】加载Student类
1.检测此Class是否载入过(即在缓存区中是否有Class),如果有则直接进入第8步,否则执行第2步
2.如果父类加载器不存在(如果没有父类加载器,要么parent是根类加载器,要么自己本身就是根类加载器)
则进入第4步执行,如果父类加载器存在,则执行第3步
3.请求使用父类加载器去载入目标类,如果成功载入,则进入第8步执行,否则接着执行第5步
4.查找根类加载器是否载入,如果成功载入就进入第8步执行,否则跳到第7步
5.当前类加载器尝试寻找Class文件(从此ClassLoader相关的类路径中寻找),如果找到
执行第6步,如果找不到则跳到第7步
6.从文件中载入Class,载入成功后跳到第8步
7.抛出ClassNotFoundException异常
8.返回对应的java.lang.Class对象
注意: 其中第5,6步允许重写 ClassLoader 的findClass方法来实现自己的载入策略,甚至可以重写
loadClass方法来实现自己的载入过程
public class ReflectDemo03 {
public static void main(String[] args) throws IOException {
// 获取系统类加载器
ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); // AppClassLoader
System.out.println("系统类加载器: " + systemLoader);
Enumeration<URL> resources = systemLoader.getResources("");
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
System.out.println(url);
}
ClassLoader parent = systemLoader.getParent(); // ExtClassLoader
System.out.println(parent);
System.out.println("扩展类加载器的加载路径: " + System.getProperty("java.ext.dirs"));
System.out.println("根类加载器: " + parent.getParent()); // null
}
}
4 自定义类加载器
【自定义类加载器】
JVM中除了根类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以
扩展ClassLoader的子类,并且重写ClassLoader所包含的方法来实现自定义的类
加载器。
类加载器关键方法
loadClass:根据指定名称来加载类,系统就是调用此方法来获取指定类对应的Class对象
findClass:根据指定名称来查找类
defindClass(String,byte[],int,len): 该方法负责将指定类的字节码文件
读入字节数组byte[]内,并把它转换为Class对象,该字节码文件可以来自于文件/网络中
find.SystemClass(String): 从本地文件系统装入文件。它在本地系统中寻找类文件,
如果存在,就使用defineClass方法将原始字节转换成Class对象,以将改文件转换成类
static getSystemClassLoader(): 返回系统类加载器对象
getParent: 获取该类加载器的父类加载器
resolveClass(Class): 链接指定的类,类加载器可以使用该方法链接类文件
indLoadedClas(String): 来检查是否已经加载类,如果已经加载则直接返回
通常都是重写findClass方法
loadClass方法的执行步骤如下:
1.使用findLoadedClas(String)来检查是否已经加载类,如果已经加载则直接返回
2.在父类加载器上调用loadClass方法,如果父类加载器为null,则使用根类加载器加载
3.调用findClass(String)方法查找类
从上述步骤可以看出,重写findClass方法可以避免覆盖类加载的双亲委派机制和缓冲机制
两种方法,如果想要自己重写loadClass方法,则实现逻辑非常复杂
/*public class ReflectDemo02 {
public static void main(String[] args) {
System.out.format("%sabc%s", "张三", "李四");
}
}*/
public class CompileClassLoader extends ClassLoader {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (args.length < 1) {
System.out.println("缺少目标类 请按照如下格式运行Java文件");
System.out.println("java compileClassLoader ClassName");
}
String proClass = args[0];
String[] progArgs = new String[args.length - 1];
System.arraycopy(args, 1, progArgs, 0, progArgs.length);
CompileClassLoader ccl = new CompileClassLoader();
Class<?> clazz = ccl.loadClass(proClass);
Method main = clazz.getMethod("main", (new String[0]).getClass());
Object argsArray[] = {progArgs};
main.invoke(null, argsArray);
}
// 读取文件内容
private byte[] getBytes(String fileName) {
File file = new File(fileName);
long len = file.length();
byte[] bys = new byte[(int) len];
try {
FileInputStream fis = new FileInputStream(file);
int r = fis.read(bys);
if (r != len) {
throw new IOException("无法读取全部文件");
}
return bys;
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
// 定义编译指定Java文件的方法
private boolean compile(String javaFile) {
System.out.format("正在编译%s文件,请稍后...", javaFile);
// 调用javac命令
try {
Process process = Runtime.getRuntime().exec("javac " + javaFile);
process.waitFor();
int ret = process.exitValue();
return ret == 0;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
// 重写ClassLoader的findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> clazz = null;
String fileStub = name.replace(".", "/");
String javaFileName = fileStub + ".java";
String classFileName = fileStub + ".class";
File javaFile = new File(javaFileName);
File classFile = new File(classFileName);
if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) {
try {
if (!compile(javaFileName) || !classFile.exists()) {
throw new ClassNotFoundException("ClassNotFoundException:" + javaFileName);
}
} catch (Exception e) {
// TODO: handle exception
}
}
if (classFile.exists()) {
byte[] raw = getBytes(classFileName);
clazz = defineClass(name, raw, 0, raw.length);
}
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
}
2 反射
1 通过反射获取堆区中对应的字节码文件对象 Class
反射:
1.通过字节码文件对象,使用该对象来获取一个类的所有的信息
(成员方法 成员变量 构造方法 静态方法 常量 访问权限修饰符 泛型 …包括私有)
2.Java是面向对象语言,Java编译生成的字节码文件应该也是一个对象,而这个对象不需要我们定义,
3.Java已经事先定义好了一个类叫做Class
4.Class类是用来描述现实事物的,是Java语言的最基本单位,类作为一个概念存在,概念本身也是一个对象
而我们使用Class类来描述这个概念类
反射之前的做法:
Student s = new Student();
反射之后:
1.获取到学生类对应的字节码文件对象
2.通过字节码文件对象获取构造方法对象
3.通过构造方法对象创建对象
4可以动态创建对象,动态赋值,动态调用方法
反射的好处:
1.通过反射技术可以获取到字节码文件对象,
2.使用这个字节码文件对象就可以获取到一个类的所有信息,包括私有
【构造方法对象,成员变量对象,成员方法对象,访问权限修饰符对象…】
3.使得代码的可维护和可扩展大大提高
是框架的核心
学习反射相关的类 java.lang.reflect包下的相关API
相关对象
Class 类对象
Constructor 构造方法对象
Method 成员方法对象
Field 成员变量对象
Modifier 访问权限修饰符对象
Array 数组对象
1.如何来获取字节码文件对象呢?
方式一: 通过Class类中getClass()方法
缺点: 必须创建对象才能够调用getClass()方法
方式二: 通过数据类型的 class属性创建
缺点: 需要显示地声明Student类
优点: 创建方便
注意: 以后凡是一个方法的形参希望我们传入的是 Class类型,我们选择方式二
public void method(Class c){
}
method(Student.class);
当一个方法的形参希望我们传入的是 Class类型时,该方法体中很有可能需要用到反射技术
方式三: 通过Class类中的一个方法 Class.forName(类名的全路径)
优点: 不需要显示编写学生类
满足了开闭原则,提高了代码的扩展性和可维护性
public class ReflectDemo02 {
public static void main(String[] args) throws ClassNotFoundException, FileNotFoundException, IOException {
// 方式一: 通过Class类中getClass()方法
Student s = new Student();
Class<?> c = s.getClass();
// 方式二: 通过数据类型的 class属性创建
Class<Student> c2 = Student.class;
System.out.println(c == c2);
// 方式三: 通过Class类中的一个方法 Class.forName(类名的全路径)
Properties prop = new Properties();
prop.load(new FileReader("config/classInfo.properties"));
String className = prop.getProperty("className");
Class<?> c3 = Class.forName(className);
System.out.println(c3);
System.out.println(c == c3);
}
}
2通过反射获取构造方法对象
通过反射获取构造方法对象
Constructor
掌握程度: 能够随心所欲操作对象,获取到一个类的所有成员信息
1 Constructor<?>[] getConstructors() 获取到公有修饰的构造方法对象数组
2 Constructor<?>[] getDeclaredConstructors() 获取所有构造方法对象数组
3 Constructor getConstructor(Class<?>… parameterTypes) 获取公有修
饰的单个构造方法对象
4 Constructor getDeclaredConstructor(Class<?>… parameterTypes) 获取单个构造方法对象,包括私有
public class ReflectDemo01 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
Class<?> c = Class.forName("com.sxt.reflectdemo03.Student");
Constructor<?>[] constructors = c.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println("======================");
Constructor<?>[] declaredConstructors = c.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
System.out.println(constructor);
}
System.out.println("=======================");
// 获取所有构造方法对象无意义,一般获取指定构造方法对象
// 获取无参构造方法对象
Constructor<?> con = c.getConstructor();
System.out.println(con);
System.out.println("=======================");
// 获取所有参数的构造方法对象
Constructor<?> con2 = c.getConstructor(String.class, int.class, String.class);
System.out.println(con2);
System.out.println("=======================");
// 获取私有修饰的构造方法对象
// Constructor<?> con3 = c.getConstructor(String.class);
// System.out.println(con3);
// java.lang.NoSuchMethodException
Constructor<?> con4 = c.getDeclaredConstructor(String.class);
System.out.println(con4);
}
}
3 通过反射创建对象
通过反射创建对象
T newInstance(Object… initargs)
反射之前:
Student s = new Student();
通过抛出异常获取错误来防止外界访问本类成员
如下代码创建对象
public class ReflectDemo02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, FileNotFoundException, IOException {
Properties properties = new Properties();
properties.load(new FileReader("config/classinfo.properties"));
String classPath = properties.getProperty("className");
// 通过反射获取字节码文件对象
Class<?> c = Class.forName(classPath);
// 通过字节码文件对象获取构造方法对象
Constructor<?> con = c.getConstructor();
// 通过构造方法对象创建对象
Object instance = con.newInstance();
System.out.println(instance);
// 获取全参构造方法对象
Constructor<?> con2 = c.getConstructor(String.class, int.class, String.class, double.class);
Object instance2 = con2.newInstance("老王", 20, "北京西路", 1.70);
System.out.println(instance2);
// 获取私有构造方法对象并且创建对象
Constructor<?> con3 = c.getDeclaredConstructor(String.class, int.class);
// java.lang.IllegalAccessException
// 打开访问权限
con3.setAccessible(true);
Object instance3 = con3.newInstance("老李", 20);
System.out.println(instance3);
}
}
4 通过反射获取成员变量对象并且赋值
防止反射访问
private Student(String name, int age) {
this.name = name;
this.age = age;
throw new Error("无法访问私有成员");
}
获取成员变量对象
Field[] getFields() 获取所有公有修饰的成员变量对象数组,包括父类公有成员
Field[] getDeclaredFields() 获取本类所有成员数组
Field getField(String name) 获取单个父类或者本类公有成员变量对象
Field getDeclaredField(String name) 获取单个本类所有成员变量对象
通过反射获取成员变量对象并且赋值
Object get(Object obj)
int getModifiers()
String getName()
void set(Object obj, Object value)
练习一: 书写一个方法能够返回所有对象 String 类路径 返回结果 Object
练习二: 书写一个方法能够对任意对象的任意属性设置任意值 Object String Object
public class ReflectDemo {
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("com.sxt.reflectdemo04.Student");
Field[] fields = c.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("=======================");
Field[] declaredFields = c.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
System.out.println("=======================");
// 获取单个hobby属性
Field hobbyField = c.getField("hobby");
System.out.println(hobbyField);
// protected int age;
Field ageField = c.getDeclaredField("age");
System.out.println(ageField);
System.out.println("=======================");
/*
* public String name;
* protected int age;
* private String address;
* 分别给三个参数设置初始值
*
* 反射之前
* Student s = new Student();
* s.name = "张三";
*
* 反射之后
*/
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
Field nameField = c.getField("name");
nameField.set(obj, "隔壁老王");
// System.out.println(nameField.get(obj));
Field agesField = c.getDeclaredField("age");
agesField.set(obj, 20);
// System.out.println(agesField.get(obj));
Field addressField = c.getDeclaredField("address");
// 打开访问权限
addressField.setAccessible(true);
addressField.set(obj, "南京西路");
System.out.println(obj);
}
}
5 通过反射获取成员方法对象
Method[] getMethods() 获取所有公有的成员方法,包括父类公有
Method[] getDeclaredMethods() 获取本类的所有成员方法,包括私有
Method getMethod(String name, Class<?>… parameterTypes) 获取单个公有构造方法
Method getDeclaredMethod(String name, Class<?>… parameterTypes) 获取单个所有构造方法对象
public class ReflectDemo01 {
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("com.sxt.reflectdemo04.Student");
Object instance = c.getConstructor().newInstance();
// 通过字节码文件对象获取成员方法对象
Method[] methods = c.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("=================");
Method[] declaredMethods = c.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println(method);
}
System.out.println("==================");
// 获取method方法
// Method getMethod(String name, Class<?>... parameterTypes)
Method method = c.getMethod("method");
System.out.println(method);
Object invoke = method.invoke(instance);
System.out.println(invoke);
System.out.println("==================");
// 获取setName方法并且赋值
Method setNameMethod = c.getMethod("setName", String.class);
setNameMethod.invoke(instance, "隔壁老周");
// 获取getName方法
Method getNameMethod = c.getMethod("getName");
Object result = getNameMethod.invoke(instance);
System.out.println(result);
// 获取add方法并且调用
Method addMethod = c.getMethod("add", int.class, int.class);
Object result3 = addMethod.invoke(instance, 10, 20);
System.out.println(result3);
// 获取私有方法并且调用
Method showMethod = c.getDeclaredMethod("show");
showMethod.setAccessible(true);
showMethod.invoke(instance);
}
}
6 设计一个对象池,用于我们方便获取对象
能够加载配置文件中的所有对象放到池子中,我们只需要通过池子获取对象
public class ReflectDemo03 {
public static void main(String[] args) throws Exception {
InstancePoolFactory factory = InstancePoolFactory.getInstance();
Object obj = factory.getObject("Teacher");
System.out.println(obj);
Student s = InstancePoolFactory.createObject(Student.class);
System.out.println(s);
}
}
class InstancePoolFactory {
// 容器【数组,Map】
private static Map<String, Object> instancePool = new HashMap<>();
public static final String DEFAULT_PROP_FILENAME = "config/classinfo.properties";
private InstancePoolFactory() {
}
/*
* 外界获取工厂对象的方法
*/
public static InstancePoolFactory getInstance() {
return new InstancePoolFactory();
}
/*
* 动态创建对象的方法
*/
public static Object createObject(String className) throws Exception {
Class<?> c = Class.forName(className);
return c.getConstructor().newInstance();
}
public static <T> T createObject(Class<T> c) throws Exception {
T t = c.newInstance();
return t;
}
// 通过指定配置文件来初始化对象池
static {
Properties prop = new Properties();
try {
prop.load(new FileReader(DEFAULT_PROP_FILENAME));
Set<String> keys = prop.stringPropertyNames();
for (String className : keys) {
String classPath = prop.getProperty(className);
Object instance = createObject(classPath);
instancePool.put(className, instance);
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 通过对象池获取对象
public Object getObject(String name) {
return instancePool.get(name);
}
}
7 JavaBean
一个JavaBean最基本的组成单位
成员变量
无参构造方法
get/set方法
在JavaBean称为 成员变量
在数据库称为 字段
JavaWeb中成为属性
public class Student extends Person {
public String name;
protected int age;
private String address;
public Student() {
super();
}
protected Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
private Student(String name) {
this.name = name;
// 防止外界通过反射访问
throw new Error("无法访问本类的构造方法!!!");
}
public Student(String name, int age, String address) {
super();
this.name = name;
this.age = age;
this.address = address;
}
// 无参无返回值方法
public void show() {
System.out.println("Student.show()");
}
// 有参有返回值方法
public String concact(int a, String b, double c) {
return a + b + c;
}
// 私有方法
private void method() {
System.out.println("method");
}
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 + ", address=" + address + "]";
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public double getSalary() {
return 100.0;
}
}
3 代理模式
代理模式: 23种设计模式的一种
静态代理
动态代理
通过代理,控制对对象的访问
可以详细控制访问某个对象的方法,在调用这个方法之前做前置处理,在调用这个方法之后做后置处理 AOP的核心实现机制
例如 租房中介,经理人
代理模式的作用: 可以通过代理对象去控制真实对象中的方法,代理对象就可以具备调用真实对象的所有方法的调用控制权
我们就可以在调用方法之前做一些前置处理,之后做一些后置处理
动态代理:可以在不在修改源代码的基础动态地加入代码
1 静态代理
为什么称为静态代理?
这里的代理对象由我们自己来实现的,而JDK中提供了一个类Proxy类能够动态地帮助我们创建代理对象或者代理类
Proxy.getProxyClass(loader, interfaces) 动态获取代理类
Proxy.newProxyInstance(loader, interfaces, h) 动态获取代理对象
public class StaticProxyDemo {
public static void main(String[] args) {
// 创建真实对象
IStar ldh = new LdhStar();
IStar zbz = new ZbzStart();
// 创建静态代理对象
ProxyManager proxy = new ProxyManager(zbz);
proxy.confer();
proxy.signContact();
proxy.bookTicket();
proxy.sing();
proxy.collectMoney();
}
}
// 公共接口: 真实对象和代理对象都能够实现的方法
interface IStar {
void confer();
void signContact();
void bookTicket();
void sing();
void collectMoney();
}
// 真实对象
class LdhStar implements IStar {
@Override
public void confer() {
System.out.println("刘德华面谈");
}
@Override
public void signContact() {
System.out.println("刘德华签合同");
}
@Override
public void bookTicket() {
System.out.println("刘德华订机票");
}
@Override
public void sing() {
System.out.println("刘德华唱歌");
}
@Override
public void collectMoney() {
System.out.println("刘德华收钱");
}
}
class ZbzStart implements IStar {
@Override
public void confer() {
System.out.println("张柏芝面谈");
}
@Override
public void signContact() {
System.out.println("张柏芝签合同");
}
@Override
public void bookTicket() {
System.out.println("张柏芝订票");
}
@Override
public void sing() {
System.out.println("张柏芝唱歌");
}
@Override
public void collectMoney() {
System.out.println("张柏芝收钱");
}
}
// 静态代理类
class ProxyManager implements IStar {
// 真实对象的引用
private IStar star;
public ProxyManager() {
super();
}
public ProxyManager(IStar star) {
super();
this.setStar(star);
}
@Override
public void confer() {
System.out.println("经纪人面谈");
}
@Override
public void signContact() {
System.out.println("经纪人签合同");
}
@Override
public void bookTicket() {
System.out.println("经纪人订票");
}
@Override
public void sing() {
star.sing();
}
@Override
public void collectMoney() {
System.out.println("经纪人收钱");
}
public IStar getStar() {
return star;
}
public void setStar(IStar star) {
this.star = star;
}
}
2 动态代理
动态代理的本质就是在代理对象方法调用之前或者之后加入一些通用的方法
面向切面编程就是代理模式的应用,也是AOP的基础
面向切面编程是面向对象的一种方式,在代码的执行过程当中,动态嵌入其他代码,成为面向切面编程
常见的应用场景:
1.日志
2.事物
3.数据库操作
面向切面编程的几个概念:
AOP本质就是动态代理
1.切点(PointCut): 要对哪些连接点进行拦截的定义,即要添加代码的地方 例如info作为切点
2.连接点(JointPoint): 类里面可以被增强的方法,这些方法称为连接点 (例如: info run add 三个连接点)
3.增强(通知)(Advice): 指的是拦截到JoinPoint之后所要做的事情就是通知,即向切点插入的代码片段称为通知
通知的分类: 前置通知,后置通知,异常通知,最终通知,环绕通知(后面讲解)
4.目标对象(Target): 代理的目标对象,这里就是target
5.织入(Weaving): 是把增强应用到目标的过程,即Advice应用到Target的过程
6.代理(Proxy): 一个类被AOP织入增强后,就会产生一个结果代理类
7.引介: 引介是一种特殊的通知在不修改源代码的前提下,可以在运行时期为类动态地加入一些方法或者字段
课堂案例:
设计一个排序算法工具类(快速排序 选择排序 冒泡排序),使用动态代理来统计排序的执行时间
public class DynamicProxyDemo {
public static void main(String[] args) {
test1();
System.out.println("=============");
test2();
}
// 使用动态代理之前的做法
public static void test1() {
IDog dog = new GunDog();
DogUtils.method1();
dog.info();
DogUtils.method2();
DogUtils.method1();
dog.run();
DogUtils.method2();
}
// 代理之后的做法
public static void test2() {
IDog dog = new GunDog();
IDog proxy = (IDog) MyProxyFactory.getProxy(dog);
proxy.info();
proxy.run();
int result = proxy.add(10, 20);
System.out.println("result: " + result);
}
}
// 代理工厂
class MyProxyFactory {
public static Object getProxy(Object target) {
MyInvocationHandler handler = new MyInvocationHandler();
handler.setTarget(target);
// 通过真实对象实现的接口反推代理对象实现的接口
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), handler);
return proxy;
}
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
/**
* proxy: 代理对象
* method: 方法对象,具体调用了哪个方法,就会将这个方法对象传递给我们
* args: 调用者调用的方法的参数也会传递给我们
*
* 代理对象调用任何一个方法,都会让invoke方法被回调
* 并且将调用的方法对象传递给method,将方法的参数传递给args
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName(); // info
Object result = null;
switch (methodName) {
case "info":
DogUtils.method1();
method.invoke(target, args);
DogUtils.method2();
break;
case "run":
DogUtils.method1();
method.invoke(target, args);
DogUtils.method2();
break;
case "add":
result = method.invoke(target, args);
if (args != null) {
for (Object obj : args) {
System.out.println("args: " + obj);
}
}
break;
default:
break;
}
// 如果一个方法的返回值为void,这里返回void,如果返回值不为void,就返回对应结果
return result;
}
}
// 创建一个公共接口
interface IDog {
void info();
void run();
int add(int a, int b);
}
// 猎狗
class GunDog implements IDog {
@Override
public void info() {
System.out.println("一只猎狗");
}
@Override
public void run() {
System.out.println("猎狗在跑");
}
@Override
public int add(int a, int b) {
return a + b;
}
}
class DogUtils {
public static void method1() {
System.out.println("========通用方法一=========");
}
public static void method2() {
System.out.println("========通用方法二=========");
}
}