이 기사는 java에 대한 관련 지식을 주로 소개하며 여러 클래스 획득 방법 및 응용 시나리오의 관련 문제를 소개합니다.
추천 학습: "java 학습 튜토리얼"
Java를 배우는 친구들은 Java 리플렉션 메커니즘
을 들어본 적이 있을 수도 있지만, 익숙하기도 하고 다소 생소하기도 합니다. 인터뷰에 대한 생각에 Java 반영 메커니즘
에 대해 자주 묻는 몇 가지 질문이 이론적 지식
과 코드 예제
및 응용 시나리오
와 결합되어 있습니다. code >Java 반사 메커니즘
에 대한 지식과 이해를 심화하기 위해 설명하고 도움이 필요한 친구들에게 도움이 되기를 바랍니다~Java反射机制
,但是熟悉又有点陌生,本文主要是通过思考面试中经常被问到的几个Java反射机制
的问题,再通过理论知识
结合代码实例
及应用场景
进行讲解,加深自己对Java反射机制
的认知和理解,也希望能帮助到有需要的小伙伴~
(1)Java反射机制(Java Reflection
)是Java语言中一种动态(运行时)访问、检测 & 修改它本身
的能力,主要作用是动态(运行时)获取类的完整结构信息 & 调用对象的方法
~
更简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:
PS:不过说实话,直接看比较官方的定义还是有点难理解,再来更加通俗点的说吧~
(2)一般情况下,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过new
实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射
~
(3)而反射
则是一开始并不知道要初始化的是什么类,无法使用new
来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射
~
代码如下:
package com.justin.java.lang;import java.lang.reflect.Constructor;import java.lang.reflect.Method;/** * @program: Jdk1.8 Test * @description: 正射、反射简单调用示例 * @author: JustinQin * @create: 2021/8/22 13:23 * @version: v1.0.0 **/public class Student { private int id; public void setId(int id) { this.id = id; } public int getId() { return id; } public static void main(String[] args) throws Exception{ //一、正射调用过程 Student student = new Student(); student.setId(1); System.out.println("正射调用过程Student id:" + student.getId()); //二、反射调用过程 Class clz = Class.forName("com.justin.java.lang.Student"); Constructor studentConstructor = clz.getConstructor(); Object studentObj = studentConstructor.newInstance(); Method setIdMethod = clz.getMethod("setId", int.class); setIdMethod.invoke(studentObj, 2); Method getIdMethod = clz.getMethod("getId"); System.out.println("正射调用过程Student id:" + getIdMethod.invoke(studentObj)); }}
输出结果:
正射调用过程Student id:1反射调用过程Student id:2
上述例子反射的调用过程,可以看到获取一个类的反射对象
,主要过程为:
Class
实例对象Class
实例对象获取Constructor
对象Constructor
对象的newInstance
方法获取到类的反射对象
获取到类的反射对象
后,就可以对类进行操作了~ 例如,上述示例中对类的方法进行调用过程为:
Class
实例对象获取到类的Method
对象Method
对象的invoke
方法调用到具体类的方法前面一点也提到了获取到类的Class
实例对象,上面示例反向调用过程
中我们是通过Class.forName("类的全局定名")
这种方式来获取到类的Class
实例对象,除了这种,常用的还有其他两种,往下讲解~
(1)获取类的java.lang.Class
实例对象,常见的三种方式分别为:
MyClass.class
获取,这里的MyClass指具体类~~Class.forName("类的全局定名")
获取,全局定名为包名+类名new MyClass().getClass()
获取,这里的MyClass指具体类~(2)通过MyClass.class
获取,JVM会使用ClassLoader
类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回java.lang.Class
对象
(3)通过Class.forName("类的全局定名")
获取,同样,类会被JVM加载到内存中,并且会进行类的静态初始化工作,返回java.lang.Class
1. Java 반사 메커니즘이란 무엇입니까?
1.1 반사 원리
(1) Java 반사 메커니즘(Java Reflection
)은 Java 언어의 동적(런타임) 액세스입니다. 자신을 감지하고 수정하는 능력
의 주요 기능은 동적으로(런타임) 클래스의 전체 구조 정보를 얻고 객체의 메서드를 호출하는 것입니다
~
더 간단히 말하면 Java 프로그램이 (동적으로) 실행될 때 클래스의 반사 객체를 생성한 다음 클래스에서 다음과 같은 관련 작업을 수행합니다.
🎜PS: 하지만 솔직하게 말하면 공식적인 정의는 아직 좀 이해하기 어렵습니다. 좀 더 대중적인 방식으로 이야기해 보겠습니다~🎜🎜🎜(2 ) 일반적으로 말하면 특정 클래스를 사용할 때 해당 클래스에 대해, 무엇을 사용하는지 알 수 있습니다. 원하는 작업을 수행하려면
new
인스턴스화를 통해 직접 객체를 만든 다음 이것을 사용하면 됩니다. 클래스를 운용하는 객체입니다. 이것은 orthophoto
에 속합니다~🎜🎜🎜(3) 그러나 reflection
은 처음에 어떤 클래스를 초기화할지 모릅니다. new
를 사용하여 객체를 인스턴스화하고 생성할 수 없습니다. 주로 JDK에서 제공하는 리플렉션 API를 통해 구현됩니다. 런타임에만 어떤 클래스를 작동할지 알 수 있으며 전체 구조를 얻을 수 있습니다. Reflection
입니다~🎜🎜public class MyClass { private static final String staticStr = "Hi"; private static int staticInt = 2021; private String id; static { System.out.println("静态代码块:staticStr=" + staticStr + ",staticInt=" + staticInt); } { System.out.println("动态代码块~"); } public MyClass() { System.out.println("无参构造方法~"); } public MyClass(String id) { System.out.println("有参构造方法~"); this.id = id; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String toString() { return "MyClass{" + "id='" + id + '\'' + '}'; }}
package com.justin.java.lang;import org.junit.Test;/** * @program: Jdk1.8Test * @description: Java反射机制中获取类的Class实例对象的常见三种方式及区别对比 * @author: JustinQin * @create: 2021/8/22 15:04 * @version: v1.0.0 **/public class MyClassTest { @Test public void test1() { System.out.println("一、MyClass.class方式========="); Class> class1 = MyClass.class; } @Test public void test2() throws ClassNotFoundException { System.out.println("二、Class.forName方式========="); Class class2 = Class.forName("com.justin.java.lang.MyClass"); } @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)); }}
리플렉션 객체
를 얻는 것을 볼 수 있습니다. 주요 프로세스는 :🎜🎜Class
인스턴스 객체 가져오기Class
인스턴스 객체를 기반으로 Constructor
객체 가져오기 Constructor
의 newInstance
메서드를 기반으로 클래스의 반사 개체
를 가져옵니다.리플렉션 개체
를 얻은 후 클래스를 조작할 수 있습니다~ 예를 들어 위 예시에서 클래스 메소드를 호출하는 과정은 다음과 같습니다. 🎜🎜Method
개체를 얻은 다음 Invoke
메서드를 사용합니다. code>Method 객체 특정 클래스의 메소드 호출Class
인스턴스 객체를 획득하는 위의 예 역 호출 프로세스
이 메소드에서는 Class.forName("Global name of the class")
를 통해 클래스의 Class
인스턴스 객체를 얻습니다. . 이 외에도 일반적으로 사용되는 방법이 있습니다. 나머지 두 가지는 아래에서 설명하겠습니다~🎜🎜java.lang.Class
인스턴스 객체를 얻는 세 가지 일반적인 방법은 다음과 같습니다. 🎜🎜MyClass.class
를 통해 가져오세요. 여기서 MyClass는 특정 클래스를 참조합니다~~Class.forName("Global naming of the class") 획득, 전역 이름은 패키지 이름 + 클래스 이름
new MyClass().getClass()
를 통해 획득합니다. 여기서 MyClass는 특정 클래스를 참조합니다~MyClass.class
를 통해 얻은 JVM은 ClassLoader
클래스 로더를 사용하여 클래스를 메모리에 로드하지만 클래스 초기화 작업을 수행하지 않습니다, java.lang.Class
object🎜🎜🎜(3)을 통해 Class.forName("클래스의 전역 이름 지정)을 반환합니다. ")
Get, 마찬가지로 클래스는 JVM에 의해 메모리에 로드되고 는 클래스의 정적 초기화를 수행하여 java.lang.Class를 반환합니다. 코드> 객체 🎜🎜<p><strong>(4)通过<code>new MyClass().getClass()
获取,这种方式使用了new
进行实例化操作,因此静态初始化和非静态初始化工作都会进行,getClass
方法属于顶级Object
类中的方法,任何子类对象都可以调用,哪个子类调用,就返回那个子类的java.lang.Class
对象
PS: 这3种方式,最终在JVM堆区对应类的
java.lang.Class
对象都属于同一个,也就是内存地址相同,进行==
双等号比较结果为true
,原因是JVM类加载过程中使用的是同一个ClassLoader
类加载器加载某个类,不论加载多少次,生成到堆区的java.lang.Class
对象始终只有一个,除非自定义类加载器,破坏JVM的双亲委派机制,使得同一个类被不同类加载器加载,JVM才会把它当做两个不同的java.lang.Class
对象
创建一个实体类,分别在实体类中创建类的静态代码块
、动态代码块
、有参构造方法
、无参构造方法
,方便测试几种方式的区别及内存地址是否相同~
(1)实体类:
public class MyClass { private static final String staticStr = "Hi"; private static int staticInt = 2021; private String id; static { System.out.println("静态代码块:staticStr=" + staticStr + ",staticInt=" + staticInt); } { System.out.println("动态代码块~"); } public MyClass() { System.out.println("无参构造方法~"); } public MyClass(String id) { System.out.println("有参构造方法~"); this.id = id; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String toString() { return "MyClass{" + "id='" + id + '\'' + '}'; }}
(2)单元测试类:
通过@Test
注解对三种方式分别进行单元测试,再对这三种方式的组合进行单元测试~
package com.justin.java.lang;import org.junit.Test;/** * @program: Jdk1.8Test * @description: Java反射机制中获取类的Class实例对象的常见三种方式及区别对比 * @author: JustinQin * @create: 2021/8/22 15:04 * @version: v1.0.0 **/public class MyClassTest { @Test public void test1() { System.out.println("一、MyClass.class方式========="); Class> class1 = MyClass.class; } @Test public void test2() throws ClassNotFoundException { System.out.println("二、Class.forName方式========="); Class class2 = Class.forName("com.justin.java.lang.MyClass"); } @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内存地址比较是否相同:trueMyClass.class和new MyClass().getClass内存地址比较是否相同:trueClass.forName和new MyClass().getClass内存地址比较是否相同:true
通过test1
、test2
、test3
的测试结果验证了2.1 三种方式及区别
中黄色标记部分的区别说明,即:
MyClass.class
不会做任何类的初始化工作Class.forName
会进行类的静态初始化工作new MyClass().getClass
静态初始化和非静态初始化工作都会进行内存地址相同
的而test23
组合得到的测试结果,说明静态代码块只会被加载一次
~
讲了这么多,除了知道基本原理和基本使用之外,更重要的还是要知道它的一些比较实际的应用场景
,往下介绍~
工厂模式
中的简单工厂模式优化代理模式
中的动态代理方式实现Java JDBC
数据库操作Java中主要有23种设计模式,其中工厂模式就是其中一种,而简单工厂模式,顾名思义,也是属于工厂模式中的一种,只不过比较简单。简单工厂模式也可以叫做静态方法模式(因为工厂类一般都是在内部定义了一个静态方法)。
从现实生活角度来理解的话,工厂是专门负责生产产品的,同样在设计模式中,简单工厂模式我们可以理解为专门负责生产对象的一个类,称为“工厂类”。
简单工厂模式通过创建一个对应的工厂类,将
类实例化的操作
与使用对象的操作
进行分开,让使用者不用知道具体参数就可以实例化出所需要的具体产品
类,从而避免了在客户端代码中显式指定,实现了解耦。即使用者可直接消费产品而不需要知道其生产的细节~
实现简单工程模式的核心是创建一个
工厂类
,并且在内部定义了一个静态方法,传入不同的参数标识
通过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这款产品,无法生产~"); } }}
(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反射机制
优化简单工厂模式后,可以看到,不论具体产品类
更新多频繁,都不需要再修改工厂类
,从而解决了普通简单工厂模式操作成本高
和系统复杂性高
的问题~
(1)再次优化背景
简单工厂模式的工厂类采用
Java反射机制
进行优化后,此时的仍然存在这样一个问题,子类的全局定名(包名+类名)
是写死的,但是实际上开发者在写代码时是很难提前预知所有的子类的全局定名(包名+类名)
的,因此需要进行二次优化~
(2)再次优化实现思路
通过
配置文件
方式,统一定义类名对应全局定名(包名+类名)
,将配置文件存放到资源目录下,程序运行时通过ClassLoader
类加载器动态获取到配置文件
中定义的子类的全局定名~
(3)再次优化实现步骤
再次优化步骤1:相关优化与第一次优化保持不变~
再次优化步骤2:配置类名对应全局定名(包名+类名)
创建属性配置文件Product.properties
//产品抽象类Product相关子类的全局定名(包名+类名)定义ProductA = com.justin.java.lang.ProductAProductB = com.justin.java.lang.ProductBProductC = 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
代理(Proxy)模式
是一种设计模式
,通过代理对象
来访问目标对象
,还可以在不修改目标对象
的情况下,对代理对象
进行拓展,增强目标对象
的功能~
什么?还是不太理解?
更通俗一点的说代理模式,就是想做某件事(
买火车票
),自己
能买(直接去火车站
买),却委托别人去买(没空还是代理点
买吧),还可以让别人帮自己做其他事(订好酒店)~
代理模式又分为静态代理、动态代理,往下介绍~
(1)
静态代理
属于代理模式
的一种代理方式,需要代理对象
和目标对象
实现相同的接口
(2)静态代理
的代理类是由程序员编写源码,编译后即可获取到代理类的class字节码文件,也就是在程序运行前
就已经得到实际的代理类class字节码文件了
动态代理
(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原生动态代理
来实现,这里不过多介绍,有兴趣小伙伴可以查阅资料学习下~
JDK原生动态代理中,获取代理示例对象过程中,获取目标对象的类加载器,通过
target.getClass().getClassLoader(
获取到目标对象的类加载器,target.getClass()
方式获取目标对象的Class实例对象使用的就是Java反射机制来实现的~
相信很多小伙伴都知道Java JDBC连接数据库
主要分为七大步骤,其中第一步加载JDBC驱动
,利用Java反射机制通过传入不同的驱动名称,加载不同数据库的驱动~
Class.forName("com.mysql.jdbc.Driver"); //加载MySQL驱动Class.forName("oracle.jdbc.driver.OracleDriver"); //加载Oracle驱动
创建测试库表及数据
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> <!-- 基于dbcp连接池的数据源 <bean id="dataSource" class="com.justin.datasource.DbcpDataSource" destroy-method="close"> --> <!-- 基于阿里druid连接池的数据源 <bean id="dataSource" class="com.justin.datasource.DruidDataSource" destroy-method="close"> --> <property></property> <property></property> <property></property> <property></property> <!-- 初始化连接大小 --> <property></property> <!-- 连接池最大数量 --> <property></property> <!-- 连接池最大空闲 --> <property></property> <!-- 连接池最小空闲 --> <property></property> <!-- 获取连接最大等待时间 --> <property></property> </bean>
数据库配置信息jdbc.propertis
#数据库连接驱动 app-data-source.driverClassName=com.mysql.jdbc.Driver#数据库连接url app-data-source.url=jdbc:mysql://localhost:3306/test?useSSL=false&characterEncoding=UTF-8#数据库用户 app-data-source.username=root #数据库用户密码(加密)app-data-source.password=abc@123456#连接池初始化大小 app-data-source.initialSize=10#连接池最大数量 app-data-source.maxActive=50#连接池最大空闲 app-data-source.maxIdle=20#连接池最小空闲 app-data-source.minIdle=5#获取连接最大等待时间 app-data-source.maxWait=30000
一、Java反射机制是什么?
1、Java反射机制(Java Reflection
)是Java语言中一种动态(运行时)访问、检测 & 修改它本身
的能力,主要作用是动态(运行时)获取类的完整结构信息 & 调用对象的方法
~
更简单点的说就是Java程序在运行时(动态)通过创建一个类的反射对象,再对类进行相关操作,比如:
2、更通俗点的说,我们使用某个类,都会知道这个类,以及要用它来做什么,可以直接通过new
实例化创建对象,然后使用这个对象对类进行操作,这个就属于正射
~
3、而反射
则是一开始并不知道要初始化的是什么类,无法使用new
来实例化创建对象,主要是通过JDK提供的反射API来实现,在运行时才知道要操作的是什么类,并且可以获取到类的完整构造以及调用对应的方法,这就是反射
~
2. Java 리플렉션 메커니즘에서 클래스를 얻는 세 가지 방법과 차이점은 무엇인가요?
1. 클래스의 java.lang.Class
인스턴스 객체를 가져옵니다. 세 가지 일반적인 방법은 다음과 같습니다. java.lang.Class
实例对象,常见的三种方式分别为:
MyClass.class
获取Class.forName("类的全局定名")
获取new MyClass().getClass()
获取2、通过MyClass.class
获取,JVM会使用ClassLoader
类加载器将类加载到内存中,但并不会做任何类的初始化工作,返回java.lang.Class
对象
3、通过Class.forName("类的全局定名")
获取,同样,类会被JVM加载到内存中,并且会进行类的静态初始化工作,返回java.lang.Class
对象
4、通过new MyClass().getClass()
获取,这种方式使用了new
进行实例化操作,因此== 静态初始化和非静态初始化工作都会进行 == ,getClass
方法属于顶级Object
类中的方法,任何子类对象都可以调用,哪个子类调用,就返回那个子类的java.lang.Class
对象
5、这3种方式,最终在JVM堆区对应类的java.lang.Class
对象都属于同一个,也就是内存地址相同,进行==
双等号比较结果为true
,原因是JVM类加载过程中使用的是同一个ClassLoader
类加载器加载某个类,不论加载多少次,生成到堆区的java.lang.Class
对象始终只有一个,除非自定义类加载器,破坏JVM的双亲委派机制,使得同一个类被不同类加载器加载,JVM才会把它当做两个不同的java.lang.Class
对象
三、Java反射机制的应用场景有哪些?
工厂模式
中的简单工厂模式优化代理模式
中的动态代理方式实现Java JDBC
MyClass.class
를 통해 가져옵니다. > Class.forName("클래스의 전역 이름")
을 통해 가져오기 new MyClass().getClass()
를 통해 가져오기 2. MyClass.class
를 통해 얻은 JVM은 ClassLoader
클래스 로더를 사용하여 클래스를 메모리에 로드하지만 클래스 초기화를 수행하지 않습니다. , java.lang.Class
객체를 반환합니다.
Class.forName("Global name of the class")
를 통해 가져옵니다. 클래스는 JVM에 의해 메모리에 로드되고 클래스의 정적 초기화를 수행하고 java.lang.Class
객체를 반환합니다. >new MyClass().getClass() code>가 가져오면 이 메소드는 인스턴스화를 위해 new
를 사용하므로 == 정적 초기화 및 비정적 초기화 작업이 모두 완료되고 == code>getClassMethods in the Object
클래스에 속하며 모든 하위 클래스 객체에서 호출할 수 있습니다. 호출되는 하위 클래스는 java.lang.Classjava.lang.Class
객체가 모두 동일한 클래스에 속합니다. 즉, 메모리 주소가 동일하고 ==
이중 등호의 비교 결과는 true
입니다. 왜냐하면 동일한 ClassLoader이기 때문입니다.
클래스 로더는 JVM 클래스 로딩 프로세스에서 특정 클래스를 로드하는 데 사용됩니다. 로드 횟수에 관계없이 생성된 java.lang.Class
객체는 항상 하나만 있습니다. 사용자 정의 클래스 로더가 JVM의 상위 위임 메커니즘을 파괴하고 동일한 클래스가 다른 클래스 로더에 의해 로드되지 않는 한 JVM은 두 개의 다른 java.lang.Class
객체로 처리됩니다. 🎜🎜🎜3. Java 리플렉션 메커니즘의 적용 시나리오는 무엇입니까? 🎜🎜공장 모드
의 간단한 공장 모드 최적화🎜에이전트 모드
의 동적 프록시 구현🎜Java JDBC 코드> 데이터베이스 작업🎜🎜🎜추천 학습: "🎜java tutorial🎜"🎜
위 내용은 Java 반사 메커니즘의 원리와 클래스를 얻는 여러 가지 방법에 대한 자세한 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!