反射

反射的初步理解和利用:每一个类在进行类加载是,都会有一个属于他的 Class 对象(包含各种信息)。利用反射获取到这个对象,然后利用它来创建相应的对象
反射相关的主要类,这些类在 java.lang.reflection ∶
java.lang.Class ∶代表一个类,Class 对象表示某个类加载后在堆中的对象
java.lang.reflect.Method ∶代表类的方法,Method 对象表示某个类的方法
java.lang.reflect.Field ∶代表类的成员变量,Field 对象表示某个类的成员变量
java.lang.reflect.Constructor ∶代表类的构造方法,Constructor 对象表示构造器
基本用法
先定义一个 Cat 类
public class Cat {
public String name = "我是猫";
public int age = 10;
// 无参构造方法
public Cat() {
}
// 有参构造方法
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void hello() {
System.out.println("你好" + name);
}
public void text(){
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
反射的基本用法
String className = "com.xjj.Cat";
String method = "hello";
// 利用反射获取Class对象,调用方法
Class<?> cls = Class.forName(className);
/*
class.newInstance()
会直接调用该类的无参构造函数进行实例化
class.getDeclaredConstructor().newInstance()
getDeclaredConstructor()方法会根据他的参数对该类的构造函数进行搜索并返回对应的构造函数,没有参数就返回该类的无参构造函数,详情请看暴力反射
然后再通过newInstance进行实例化。
*/
// Object o = cls.newInstance(); // 过时,jdk9之后
Object o = cls.getDeclaredConstructor().newInstance(); // 根据Cat的Class对象,创建一个Cat对象,相当于new Cat()
System.out.println("o的类型=" + o.getClass());
// 获取方法
Method method1 = cls.getMethod(method);
method1.invoke(o);
System.out.println("---------------------------------");
// 获取变量
Field nameFiled = cls.getField("name");
System.out.println(nameFiled.get(o));
Field ageFiled = cls.getField("age");
System.out.println(ageFiled.get(o));
// 获取构造方法
Constructor<?> constructor = cls.getConstructor(); //返回无参数的构造方法
System.out.println(constructor);
Constructor<?> constructor1 = cls.getConstructor(String.class, int.class); // 传入参数的class
System.out.println(constructor1);
反射和正常的速度对比
public static void main(String[] args) throws Exception {
m1();
m2();
m3();
}
public static void m1() {
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (long i = 0; i < 900000000; i++) {
cat.text();
}
long end = System.currentTimeMillis();
System.out.println("m1时间" + (end - start));
}
public static void m2() throws Exception {
Class<?> cls = Class.forName("com.xjj.Cat");
Object o = cls.getDeclaredConstructor().newInstance();
Method method = cls.getMethod("text");
long start = System.currentTimeMillis();
for (long i = 0; i < 900000000; i++) {
method.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("m2时间" + (end - start));
}
public static void m3() throws Exception {
Class<?> cls = Class.forName("com.xjj.Cat");
Object o = cls.getDeclaredConstructor().newInstance();
Method method = cls.getMethod("text");
method.setAccessible(true); // 取消访问检查,提高速度
long start = System.currentTimeMillis();
for (long i = 0; i < 900000000; i++) {
method.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("m3时间" + (end - start));
}
/* 输出
m1时间542
m2时间1750
m3时间960
*/
一些常用方法
Class<?> cls = Class.forName("com.xjj.Cat"); // 全路径类名
System.out.println(cls); // 显示是哪个类的Class对象。是cat的类对象
System.out.println(cls.getClass()); // 输出cls的运行类型
// 1、得到包名
System.out.println(cls.getPackageName());
// 得到全类名
System.out.println(cls.getName());
// 创建对象实例
Object cat1 = cls.getDeclaredConstructor().newInstance();
System.out.println(cat1);
// 通过反射获取属性
Field age = cls.getField("age");
System.out.println(age.get(cat1));
// 通过反射给属性赋值
age.set(cat1,1000000);
System.out.println(age.get(cat1));
// 获取所有属性字段
System.out.println("======所有属性字段============");
Field[] allfields = cls.getFields();
for (Field f : allfields){
System.out.println(f.getName());
}
获取 Class 对象的六种方式
1)编译阶段:Class.forName()
2)类加载阶段:类.class 多用于参数传递,构造器
3)运行阶段:对象.getClass() 已经知道有这个对象实例了
4)类加载器得到 Class 对象
5)基本数据类型.class
6)包装类.TYPE
Class<?> cls1 = Class.forName("com.xjj.Cat");
System.out.println(cls1);
Class<?> cls2 = Cat.class;
System.out.println(cls2);
Cat cat = new Cat();
Class<? extends Cat> cls3 = cat.getClass();
System.out.println(cls3);
// 先得到类加载器 carClassLoader classLoader = cat.getClass().getClassLoader();
// 通过类加载器得到Class对象
Class<?> cls4 = classLoader.loadClass("com.xjj.Cat");
System.out.println(cls4);
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
System.out.println(cls3.hashCode());
System.out.println(cls4.hashCode());
/* 输出
class com.xjj.Cat
class com.xjj.Cat
class com.xjj.Cat
class com.xjj.Cat
509886383
509886383
509886383
509886383
*/
暴力反射
创建一个 User 类
class User {
private static int age = 120;
private String name = "蓝狐狸";
public User() {
} //无参构造方法
public User(String name) {
this.name = name;
}
private User(String name, int age) {
this.name = name;
this.age = age;
}
private void sayHello() {
System.out.println("你好" + name);
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
使用暴力反射
Class<?> userClass = Class.forName("com.xjj.User");
// 访问私有构造方法
Constructor<?> constructor1 = userClass.getDeclaredConstructor(String.class, int.class);
constructor1.setAccessible(true); // 可以访问私有构造方法
Object o = constructor1.newInstance("xjj", 23);
// 访问私有的方法
// Method sayHello = userClass.getMethod("sayHello"); // 获取公有方法
Method sayHello = userClass.getDeclaredMethod("sayHello"); // 获取所有的方法,包括私有
sayHello.setAccessible(true); // 可以访问私有方法的开关
sayHello.invoke(o);
// 访问私有属性
Field age = userClass.getDeclaredField("age");
Field name = userClass.getDeclaredField("name");
age.setAccessible(true);
age.set(null,188); // 静态,可以设置为空
name.setAccessible(true);
name.set(o,"私有属性");
System.out.println(age.get(o));
System.out.println(name.get(o));
/* 输出
你好xjj
188
私有属性
*/
类加载
参考: https://zhuanlan.zhihu.com/p/376934655 , https://zhuanlan.zhihu.com/p/43845064
类加载的过程分为三个步骤 (五个阶段) :加载 - 连接(验证、准备、解析)- 初始化。

类加载器
启动类加载器(Bootstrap ClassLoader):负责加载 <JAVA_HOME>\lib 目录中并且能被虚拟机识别的类库到 JVM 内存中,如果名称不符合的类库即使放在 lib 目录中也不会被加载。该类加载器无法被 Java 程序直接引用。
扩展类加载器(Extension ClassLoader):该加载器主要是负责加载 <JAVA_HOME>\lib\ext 目录中或 java.ext.dirs 指定目录下的 jar 包,该加载器可以被开发者直接使用。
应用程序类加载器(Application ClassLoader):该类加载器也称为系统类加载器。我们自己开发的应用程序,就是由它进行加载的,负责加载 ClassPath 路径下所有 jar 包。
自定义类加载器:如果自定义的方法不想违背双亲委派模型,则只需要重写 findclass 方法即可,如果想违背双亲委派模型,则还需要重写 loadclass 方法。
双亲委派机制

具体加载过程
1)当 AppClassLoader 加载一个 class 时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器 ExtClassLoader 去完成。
2)当 ExtClassLoader 加载一个 class 时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给 BootStrapClassLoader 去完成。
3)如果 BootStrapClassLoader 加载失败(例如在 %JAVA_HOME%/jre/lib 里未查找到该 class),会使用 ExtClassLoader 来尝试加载。
4)如果 ExtClassLoader 也加载失败,则会使用 AppClassLoader 来加载,如果 AppClassLoader 也加载失败,则会报出异常 ClassNotFoundException。
ClassLoader
所有的类加载器 (除了根类加载器) 都必须继承 java.lang.ClassLoader。它是一个抽象类。方法如下
loadClass:通过指定类的全限定名称,由类加载器检测、装载、创建并返回该类的 java.lang.Class 对象。
findClass:用于查找具有给定二进制类名称的类。是一个空方法,返回内容为 class,方法其中没有任何内容,只抛出了个异常,说明这个方法需要开发者自己去实现。
defineClass:是 ClassLoader 向子类提供的方法,它可以将 .class 文件的二进制数据转换为合法的 java.lang.Class 对象。
Class.forName() 和 ClassLoader.loadClass() 的比较
Class.forName():把类的. class 文件加载到 JVM 中,对类进行解释的同时执行类中的 ==static 静态代码块==;
ClassLoader.loadClass():只是把. class 文件加载到 JVM 中,不会执行 static 代码块中的内容,只有在 newInstance 才会去执行。
package top.lanhuli;
public class CL01 {
public static void main(String[] args) throws Exception {
Class<?> loadClass = ClassLoader.getSystemClassLoader().loadClass("top.lanhuli.CL");
System.out.println(loadClass);
System.out.println("---------------------");
Class<?> aClass = Class.forName("top.lanhuli.CL");
System.out.println(aClass);
}
}
class CL {
static {
System.out.println("我是CL");
}
}
/*输出
class top.lanhuli.CL
---------------------
我是CL
class top.lanhuli.CL
*/
URLClassLoader
安装和配置
Maven 配置
1、配置本地仓库位置:在 settings.xml 文件中 localRepository 标签中设置
<!-- localRepository
| The path to the local repository maven will use to store artifacts.
|
| Default: ${user.home}/.m2/repository
<localRepository>/path/to/local/repo</localRepository>
-->
<!-- 配置本地仓库位置-->
<localRepository>D:\program\java\apache-maven-rep-3.8.4\repository</localRepository>
2、修改 maven 镜像地址:在 settings.xml 文件中 mirrors 标签中添加阿里源
<mirrors>
<mirror>
<id>aliyunmaven</id>
<mirrorOf>*</mirrorOf>
<name>阿里云公共仓库</name>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
3、maven 配置指定的 jdk 编译。通过 maven.compiler.source , maven.compiler.target 这两个属性值来指定 jdk 版本
在 pom.xml 中配置 jdk 版本
<project>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
</project>
在 settings.xml 中的位置
<settings>
<profiles>
<profile>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <!-- JRE System Liblary 的版本和这句相同 -->
</properties>
</profile>
</profiles>
</settings>
在spring项目中, 用 java.version 来统一设置
注意:maven的settings.xm和pom.xml也可以通过设定 maven-compiler-plugin 这个插件来指定 jdk 版本
参考: https://juejin.cn/post/7311603506217418786
4、idea 中 maven 设置

不联网时使用本地仓库:-DarchetypeCatalog=internal

pom.xml 文件配置
<!-- 本项目目前所处的版本号 -->
<version>1.0.0-SNAPSHOT</version>
<!-- 打包的机制,如pom,jar, maven-plugin, ejb, war, ear, rar, par,默认为jar -->
<packaging>jar</packaging>
<!--配置 jdk 版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
Grandle 配置
参考: https://blog.csdn.net/iot_ai/article/details/106617626

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
idea 创建 web 项目
参考: https://blog.csdn.net/stony3/article/details/129293286
新建 web 项目
配置 tomcat
实现 servlet
注解 WebServlet 的使用: Servlet 3.0 提供了注解 (annotation),我们可以不用再 web. xml 里面配置 servlet,只需要加上@WebServlet 注解就可以修改该 servlet 的属性了。web. xml 可以配置的 servlet 属性,在@WebServlet 中都可以配置。
@WebServlet(name = "Servlet01", value = "/Servlet01")
注意:导入 lib 的时候要把整个 tomcat 的 lib 目录导入,不要只导入 servlet-api.jar 这一个
java web
servlet 配置
文件配置:web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!--servlet与servlet-mapping标签需同时使用-->
<!--创建servlet标签-->
<servlet>
<!--给指定的servlet类起一个名字,多个servlet-name不可以有重复-->
<servlet-name>demo01</servlet-name>
<!--servlet类的全限定路径-->
<servlet-class>com.demo.servlet.DemoServlet</servlet-class>
<!--servlet初始化加载优先级(0~10)-->
<load-on-startup>0</load-on-startup>
</servlet>
<!--创建servlet映射标签-->
<servlet-mapping>
<!--映射到哪个servlet,与上面一致-->
<servlet-name>demo01</servlet-name>
<!--客户端访问路径 localhost:8080/项目名称/demo-->
<url-pattern>/demo</url-pattern>
</servlet-mapping>
<!--全局初始化参数:整个web应用-->
<context-param>
<param-name>username</param-name>
<param-value>root</param-value>
</context-param>
<context-param>
<param-name>password</param-name>
<param-value>1234</param-value>
</context-param>
<!--默认显示页面,如有404则会继续查找下一个welcome-file标签-->
<welcome-file-list>
<welcome-file>demo.html</welcome-file>
<welcome-file>demo.htm</welcome-file>
<welcome-file>demo.jsp</welcome-file>
</welcome-file-list>
<!--当页面未找到报404时显示 location 标签指定的页面-->
<error-page>
<error-code>404</error-code>
<!--需要web项目下新建/error/404.html页面-->
<location>/error/404.html</location>
</error-page>
</web-app>
注解配置:@WebServlet

pom.xml 导入 servlet 包
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>