@Test
public void test3() {
System.out.println("三、new MyClass().getClass方式=========");
Class class3 = new MyClass().getClass();
}
@Test
public void test12() throws ClassNotFoundException {
System.out.println("一、MyClass.class方式=========");
Class<?> class1 = MyClass.class;
System.out.println("二、Class.forName方式=========");
Class class2 = Class.forName("com.justin.java.lang.MyClass");
}
@Test
public void test13() {
System.out.println("一、MyClass.class方式=========");
Class<?> class1 = MyClass.class;
System.out.println("三、new MyClass().getClass方式=========");
Class class3 = new MyClass().getClass();
}
@Test
public void test23() throws ClassNotFoundException {
System.out.println("二、Class.forName方式=========");
Class class2 = Class.forName("com.justin.java.lang.MyClass");
System.out.println("三、new MyClass().getClass方式=========");
Class class3 = new MyClass().getClass();
}
@Test
public void test() throws ClassNotFoundException {
System.out.println("四、三种方式内存地址比较=========");
Class<?> class1 = MyClass.class;
Class class2 = Class.forName("com.justin.java.lang.MyClass");
Class class3 = new MyClass().getClass();
System.out.println("比较结果=========");
System.out.println("MyClass.class和Class.forName内存地址比较是否相同:" + (class1 == class2));
System.out.println("MyClass.class和new MyClass().getClass内存地址比较是否相同:" + (class1 == class3));
System.out.println("Class.forName和new MyClass().getClass内存地址比较是否相同:" + (class2 == class3));
}
}
**逐个执行单元,得出测试结果为:**
* test1()方法
一、MyClass.class方式=========
* test2()方法
二、Class.forName方式=========
静态代码块:staticStr=Hi,staticInt=2021
* test3()方法
三、new MyClass().getClass方式=========
静态代码块:staticStr=Hi,staticInt=2021
动态代码块~
无参构造方法~
* test12()方法
一、MyClass.class方式=========
二、Class.forName方式=========
静态代码块:staticStr=Hi,staticInt=2021
* test13()方法
一、MyClass.class方式=========
三、new MyClass().getClass方式=========
静态代码块:staticStr=Hi,staticInt=2021
动态代码块~
无参构造方法~
* test23()方法
二、Class.forName方式=========
静态代码块:staticStr=Hi,staticInt=2021
三、new MyClass().getClass方式=========
动态代码块~
无参构造方法~
* test()方法
四、三种方式内存地址比较=========
静态代码块:staticStr=Hi,staticInt=2021
动态代码块~
无参构造方法~
比较结果=========
MyClass.class和Class.forName内存地址比较是否相同:true
MyClass.class和new MyClass().getClass内存地址比较是否相同:true
Class.forName和new MyClass().getClass内存地址比较是否相同:true
**通过`test1`、`test2`、`test3`的测试结果验证了`2.1 三种方式及区别`中黄色标记部分的区别说明,即:**
* `MyClass.class`不会做任何类的初始化工作
* `Class.forName`会进行类的静态初始化工作
* `new MyClass().getClass`静态初始化和非静态初始化工作都会进行
* 使用这三种方式任意一种最终在JVM加载到内存中都会是`内存地址相同`的
**而`test23`组合得到的测试结果,说明`静态代码块只会被加载一次`~**
**讲了这么多,除了知道基本原理和基本使用之外,更重要的还是要知道它的一些比较实际的`应用场景`,往下介绍~**
## 💥三、Java反射机制的应用场景有哪些?
![在这里插入图片描述](https://img-blog.csdnimg.cn/3a790d5d55bf44b1854eedb953c583d3.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0p1c3RpblFpbg==,size_16,color_FFFFFF,t_70)
### 🎶3.1 应用场景
* `工厂模式`中的简单工厂模式优化
* `代理模式`中的动态代理方式实现
* `Java JDBC`数据库操作
### 🎧3.2 简单工厂模式优化
#### 📢3.2.1 什么是简单工厂模式?
>
> Java中主要有23种设计模式,其中工厂模式就是其中一种,而简单工厂模式,顾名思义,也是属于工厂模式中的一种,只不过比较简单。简单工厂模式也可以叫做静态方法模式(因为工厂类一般都是在内部定义了一个静态方法)。
> 从现实生活角度来理解的话,工厂是专门负责生产产品的,同样在设计模式中,简单工厂模式我们可以理解为专门负责生产对象的一个类,称为“工厂类”。
>
>
>
#### 🎹3.2.2 简单工厂模式有什么用?
>
> 简单工厂模式通过创建一个对应的工厂类,将`类实例化的操作`与`使用对象的操作`进行分开,让使用者不用知道具体参数就可以实例化出所需要的`具体产品`类,从而避免了在客户端代码中显式指定,实现了解耦。即使用者可直接消费产品而不需要知道其生产的细节~
>
>
>
#### 🎸3.2.3 如何实现简单工程模式?
>
> 实现简单工程模式的核心是创建一个`工厂类`,并且在内部定义了一个静态方法,传入不同的`参数标识`通过`switch`进行分组,通过`new`实例化创建不同的子类对象返回~
>
>
>
**实现例子:**
**步骤1:创建抽象产品类**
public interface Product {
public abstract void show();
}
**步骤2:创建具体产品类:**
public class ProductA implements Product {
@Override
public void show() {
System.out.println(“生产了产品A”);
}
}
public class ProductB implements Product {
@Override
public void show() {
System.out.println(“生产了产品B”);
}
}
public class ProductC implements Product {
@Override
public void show() {
System.out.println(“生产了产品C”);
}
}
**步骤3:创建简单工厂类**
public class SimpleFactory {
/**
* 实现简单工厂模式
* @param pName 产品标识
* @return 返回具体的产品
*/
public static Product createProduct(String pName){
switch (pName){
case “A”:
return new ProductA();
case “B”:
return new ProductB();
case “C”:
return new ProductC();
default:
return null;
}
}
}
**步骤4:调用简单工厂类**
public class SimpleFactoryTest {
public static void main(String[] args) {
try {
SimpleFactory.createProduct(“A”).show();
} catch (NullPointerException e) {
System.out.println(“没有A这款产品,无法生产~”);
}
try {
SimpleFactory.createProduct(“B”).show();
} catch (NullPointerException e) {
System.out.println(“没有B这款产品,无法生产~”);
}
try {
SimpleFactory.createProduct(“C”).show();
} catch (NullPointerException e) {
System.out.println(“没有C这款产品,无法生产~”);
}
try {
SimpleFactory.createProduct(“D”).show();
} catch (NullPointerException e) {
System.out.println(“没有D这款产品,无法生产~”);
}
}
}
#### 📣3.2.4 简单工厂模式优化
**(1)简单工厂模式弊端**
* 操作成本高:每增加一个接口的子类,必须修改工厂类的逻辑
* 系统复杂性提高:每增加一个接口的子类,都必须向工厂类添加逻辑
这两点弊端从前面的例子`SimpleFactory`工厂类的实现,可以看出`简单工厂模式`中对工厂类`SimpleFactory`的维护成本有点大,因为实际中可能会很频繁的去更新`具体产品类`,每一次变更都需要去修改工厂类,此时就可以利用`Java反射机制`对简单工厂模式进行优化~
**(2)简单工厂模式的优化思路**
采用Java反射机制,通过传入`子类全局定名(包名+类名)` 动态的创建不同的`子类对象实例`,从而使得在不增加产品接口子类和修改工厂类的逻辑的情况下还能实现了工厂类对子类实例对象的统一创建~
**(3)简单工厂模式的优化步骤**
**步骤1:创建工厂类**
采用Java反射机制对工厂类进行优化,主要是将`className`即`子类全局定名(包名+类名)`作为入参,通过`Class.forName`方式获取类的`java.lang.Class`实例对象,再通过`Class`实例对象的`getInstance`方法获取到具体子类的实例对象~
public class Factory {
public static Product getInstance(String className) {
Product realProduct = null;
try {
Class pClass = Class.forName(className);
realProduct = (Product) pClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return realProduct;
}
}
**步骤2:调用工厂类**
public class FactoryTest {
public static void main(String[] args) {
try {
Product productA = Factory.getInstance(“com.justin.java.lang.ProductA”);
productA.show();
} catch (NullPointerException e) {
System.out.println(“没有A这款产品,无法生产~”);
}
try {
Product productB = Factory.getInstance("com.justin.java.lang.ProductB");
productB.show();
} catch (NullPointerException e) {
System.out.println("没有B这款产品,无法生产~");
}
try {
Product productC = Factory.getInstance("com.justin.java.lang.ProductC");
productC.show();
} catch (NullPointerException e) {
System.out.println("没有C这款产品,无法生产~");
}
try {
Product productD = Factory.getInstance("com.justin.java.lang.ProductD");
productD.show();
} catch (Exception e) {
System.out.println("没有D这款产品,无法生产~");
}
}
}
**优化结果:**
>
> 使用`Java反射机制`优化简单工厂模式后,可以看到,不论`具体产品类`更新多频繁,都不需要再修改`工厂类`,从而解决了普通简单工厂模式`操作成本高`和`系统复杂性高`的问题~
>
>
>
#### 🎵3.2.5 简单工厂模式再次优化
**(1)再次优化背景**
>
> 简单工厂模式的工厂类采用`Java反射机制`进行优化后,此时的仍然存在这样一个问题,`子类的全局定名(包名+类名)`是写死的,但是实际上开发者在写代码时是很难提前预知所有的`子类的全局定名(包名+类名)`的,因此需要进行二次优化~
>
>
>
**(2)再次优化实现思路**
>
> 通过`配置文件`方式,统一定义`类名对应全局定名(包名+类名)`,将配置文件存放到资源目录下,程序运行时通过`ClassLoader`类加载器动态获取到`配置文件`中定义的子类的全局定名~
>
>
>
**(3)再次优化实现步骤**
**再次优化步骤1:相关优化与第一次优化保持不变~**
**再次优化步骤2:配置`类名对应全局定名(包名+类名)`**
创建属性配置文件`Product.properties`
//产品抽象类Product相关子类的全局定名(包名+类名)定义
ProductA = com.justin.java.lang.ProductA
ProductB = com.justin.java.lang.ProductB
ProductC = com.justin.java.lang.ProductC
注意:将`Product.properties`需要存放在`src/main/resources`资源目录下,若资源目录不存在则需要手动创建~
**再次优化步骤3:修改调用工厂类**
public class FactoryTest {
@Test
public void test() throws IOException {
ClassLoader classLoader = this.getClass().getClassLoader();
Properties prop = new Properties();
prop.load(classLoader.getResourceAsStream(“Product.properties”));
String className = "";
try {
className = prop.getProperty("ProductA");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("没有A这款产品,无法生产~");
}
try {
className = prop.getProperty("ProductB");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("没有B这款产品,无法生产~");
}
try {
className = prop.getProperty("ProductC");
Product productA = Factory.getInstance(className);
productA.show();
} catch (NullPointerException e) {
System.out.println("没有C这款产品,无法生产~");
}
}
}
**运行结果:**
生产了产品A
生产了产品B
生产了产品C
### 📀3.3 代理模式中的动态代理实现
![在这里插入图片描述](https://img-blog.csdnimg.cn/70d9c6bbc83448aab1a2a755fcc75987.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0p1c3RpblFpbg==,size_16,color_FFFFFF,t_70)
#### 🔊3.3.1 什么是代理模式?
>
> `代理(Proxy)模式`是一种`设计模式`,通过`代理对象`来访问`目标对象`,还可以在不修改`目标对象`的情况下,对`代理对象`进行拓展,增强`目标对象`的功能~
>
>
>
**什么?还是不太理解?**
>
> 更通俗一点的说代理模式,就是想做某件事(`买火车票`),`自己`能买(直接去`火车站`买),却委托别人去买(没空还是`代理点`买吧),还可以让别人帮自己做其他事(订好酒店)~
>
>
>
![在这里插入图片描述](https://img-blog.csdnimg.cn/bf70d190a42f4b74af640c330acbbc9b.png)
**代理模式又分为静态代理、动态代理,往下介绍~**
#### 💥3.3.2 什么是静态代理?
>
> (1)`静态代理`属于`代理模式`的一种代理方式,需要`代理对象`和`目标对象`实现相同的接口
> (2)`静态代理`的代理类是由程序员编写源码,编译后即可获取到代理类的class字节码文件,也就是在`程序运行前`就已经得到实际的代理类class字节码文件了
>
>
>
#### 🎶3.3.2 什么是动态代理?
**动态代理**
>
> (1)`动态代理`也属于`代理模式`的一种代理方式,不过只需要`目标对象`实现接口,`代理对象`不需要实现接口~
> (2)`动态代理`的代理类编译后是没有class字节码文件的,而是在运行时利用`Java反射机制`动态的生成代理类的class字节码文件~
>
>
>
动态代理最常用的是`JDK原生动态代理`和`cglib动态代理`,往下介绍~
**JDK 原生动态代理**
JDK 原生动态代理,主要利用了`JDK API`的
`java.lang.reflect.Proxy`和`java.lang.relfect.InnvocationHandler` 这两个类来实现~
通过`java.lang.reflect.Proxy`代理类的`newProxyInstance`方法,传递3个参数,分别是:
`目标对象的加载器` 通过`MyClass.getClass().getClassLoader`方式获取
`目标对象的实现接口类型` 通过`Object.getClass().getInterfaces()`方式获取
`InnvocationHandler事件处理器` 通过`new`实例化对象并重写`invoke`方法方式获取
**例子:**
**用户接口类`IUserDao`**
public interface IUserDao {
//添加数据
public void insert();
}
**目标对象类`UserDao`**
/**
* @program: DataStructures
* @description:
* @author: JustinQin
* @create: 2021/8/23 23:32
* @version: v1.0.0
**/
public class UserDao implements IUserDao{
@Override
public void insert() {
System.out.println("添加数据");
}
}
**动态代理类`UserProxy`**
/**
* @program: Jdk1.8Test
* @description: 动态代理类
* @author: JustinQin
* @create: 2021/8/23 23:31
* @version: v1.0.0
**/
public class UserProxy {
private Object target; //目标对象
public UserProxy(Object target) {
this.target = target;
}
/\*\*
* 利用JDK API获取到代理对象
* @return
*/
public Object getProxyInstance() {
//目标对象的加载器
ClassLoader loader = target.getClass().getClassLoader();
//目标对象的实现接口类型
Class<?>[] interfaces = target.getClass().getInterfaces();
//InnvocationHandler事件处理器实例对象
InvocationHandler h = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("添加数据前:手动开启事务");
// 执行目标对象方法
Object value = method.invoke(target, args);
System.out.println("添加数据后:手动提交事务");
return null;
}
};
//传入3个参数,创建代理类的实例对象,并返回
return Proxy.newProxyInstance(loader, interfaces,h);
}
}
**动态代理单元测试类**
/**
* @program: 动态代理单元测试类
* @description:
* @author: JustinQin
* @create: 2021/8/23 23:42
* @version: v1.0.0
**/
public class UserProxyTest {
@Test
public void test() {
IUserDao target = new UserDao();
System.out.println(“目标对象信息:” + target.getClass());
//获取代理类实例对象
IUserDao proxy = (IUserDao) new UserProxy(target).getProxyInstance();
System.out.println(“代理对象信息:” + proxy.getClass());
//执行代理方法
proxy.insert();
}
}
**单元测试执行结果**
目标对象信息:class com.justin.java.reflect.UserDao
代理对象信息:class com.sun.proxy.$Proxy2
添加数据前:手动开启事务
添加数据
添加数据后:手动提交事务
**cglib动态代理**
>
> `cglib (Code Generation Library )`是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
>
>
>
`Spring AOP`结合了`cglib动态代理`和`JDK原生动态代理`来实现,这里不过多介绍,有兴趣小伙伴可以查阅资料学习下~
#### 🎧3.3.3 动态代理中如何利用Java反射机制?
>
> JDK原生动态代理中,获取代理示例对象过程中,获取目标对象的类加载器,通过`target.getClass().getClassLoader(`获取到目标对象的类加载器,`target.getClass()`方式获取目标对象的Class实例对象使用的就是Java反射机制来实现的~
>
>
>
### 📢3.4 Java JDBC数据库操作实现
#### 🎹3.4.1 利用反射加载JDBC驱动
相信很多小伙伴都知道`Java JDBC连接数据库`主要分为七大步骤,其中第一步`加载JDBC驱动`,利用Java反射机制通过传入不同的驱动名称,加载不同数据库的驱动~
Class.forName(“com.mysql.jdbc.Driver”); //加载MySQL驱动
Class.forName(“oracle.jdbc.driver.OracleDriver”); //加载Oracle驱动
**链接:**[Mysql驱动架包mysql-connector-java-5.1.30.jar]( ) **提取码:pc63**
**链接:**[Oracle驱动架包ojdbc14-10.2.0.4.0.jar]( ) **免提取码**
#### 🎸3.4.2 Java JDBC连接示例
**创建测试库表及数据**
create DATABASE test;
– DROP TABLE IF EXISTS test.user;
create table test.user(
id int(7) primary key not null auto_increment,
name varchar(255),
sex char(1),
age int(3)
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;
insert into TEST.user(name,sex,age) values(‘张一’,‘男’,21);
insert into TEST.user(name,sex,age) values(‘张二’,‘女’,22);
insert into TEST.user(name,sex,age) values(‘张三’,‘男’,23);
**Java MySQL JDBC连接七大步骤~**
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加载JDBC驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取数据库的连接(Connection)对象
Connection connection = DriverManager.getConnection(
"jdbc:mysql://localhost/test", //mysql连接url,test表示你要连接的数据库名
"root", //数据库用户名
"abc@123456"); //密码
//3.获取数据库的操作(PrepareStatement)对象
PreparedStatement prepareStatement = connection.prepareStatement("select \* from TEST.user where id = ?");
//4.设置传入参数
prepareStatement.setInt(1, 1);
//5.上传sql语句到服务器执行(excute),并返回结果集(ResultSet)
ResultSet result = prepareStatement.executeQuery();
//6.处理返回的ResultSet结果集
while (result.next()) {
System.out.print(result.getInt("id") + ",");
System.out.print(result.getString("name") + ",");
System.out.print(result.getString("sex") + ",");
System.out.print(result.getInt("age"));
System.out.print("\n");
}
//7.释放相关资源:Connection对象、PrepareStatement对象、ResultSet对象。
connection.close();
prepareStatement.close();
result.close();
}
**执行结果:**
1,张一,男,21
**Java Oracle JDBC连接七大步骤~**
public class JdbcOracleTest {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加载JDBC驱动
Class.forName(“oracle.jdbc.driver.OracleDriver”);
//2.获取数据库的连接(Connection)对象
Connection connection = DriverManager.getConnection(
“jdbc:oracle:thin:@127.0.0.1:1521:orcl”, //oracle连接url
“root”, //数据库用户名
“abc@123456”); //密码
//3.获取数据库的操作(PrepareStatement)对象
PreparedStatement prepareStatement = connection.prepareStatement(“select * from TEST.user where id = ?”);
//4.设置传入参数
prepareStatement.setInt(1, 1);
//5.上传sql语句到服务器执行(excute),并返回结果集(ResultSet)
ResultSet result = prepareStatement.executeQuery();
//6.处理返回的ResultSet结果集
while (result.next()) {
System.out.print(result.getInt(“id”)+“,”);
System.out.print(result.getString(“name”)+“,”);
System.out.print(result.getString(“sex”)+“,”);
System.out.print(result.getInt(“age”));
System.out.print(“\n”);
}
//7.释放相关资源:Connection对象、PrepareStatement对象、ResultSet对象。
connection.close();
prepareStatement.close();
result.close();
}
}
>
> PS:上面通过Java JDBC连接数据库并进行操作,这里的连接是单一连接,直接通过DriverManager.getConnection这种Java原生的数据库连接方式建立的连接,现在实际的Java Spring项目当中,都是通过配置mybatis的数据库连接池来实现的,不过原理都是一样的,加载驱动也是利用了`Java反射机制`指定不同的驱动名称,实现不同数据库驱动的加载~
>
>
>
**数据库连接池配置`spring-mybatis.xml`**
<!-- 基于tomcat jdbc连接池的数据源 -->
<bean id="dataSource" class="com.justin.datasource.TomcatDataSource" init-method="createPool">
<!-- 基于dbcp连接池的数据源
-->
<property name="driverClassName" value="${app-data-source.driverClassName}" />
<property name="url" value="${app-data-source.url}" />
<property name="username" value="${app-data-source.username}" />
<property name="password" value="${app-data-source.password}" />
<!-- 初始化连接大小 -->
<property name="initialSize" value="${app-data-source.initialSize}" />
<!-- 连接池最大数量 -->
<property name="maxActive" value="${app-data-source.maxActive}" />
<!-- 连接池最大空闲 -->
<property name="maxIdle" value="${app-data-source.maxIdle}" />
<!-- 连接池最小空闲 -->
<property name="minIdle" value="${app-data-source.minIdle}" />