Annotation是从JDK5.0开始引入的新技术。
Annotation的作用 :
Annotation的格式:
注解是以"@注释名"在代码中存在的, 还可以添加一些参数值,例如:@SuppressWarnings(value=“unchecked”).
Annotation在哪里使用 ?
@Override : 检查该方法是否是重写方法。
@Deprecated :用于修辞方法,属性,类,标记已过时。如果使用该方法,会报编译警告。
@SuppressWarnings : 指示编译器去忽略注解中声明的警告。
@SafeVarargs : Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。对于非static或非final声明的方法,不适用,会编译不通过。
@Functionallnterface :
注解只能标记在"有且仅有一个抽象方法"的接口上。
接口体内只能声明常量字段和抽象方法,并且被隐式声明为public,static,final。
接口里面不能有私有的方法或变量。
public class Demo extends Object{
@Override//重写方法
public String toString() {
return super.toString();
}
@Deprecated//表示弃用方法
public static void test(){
}
@SuppressWarnings("all")//抑制警告
public static void test01(){
int age;
}
private S[] args;
//构造函数可以使用@SafeVarargs标记
@SafeVarargs
public Demo(S... args){
this.args = args;
}
public static void main(String[] args) {
test();
test01();
}
}
@FunctionalInterface
class interfase Demo2{
public void add();
}
取值 | 注解使用范围 |
---|---|
METHOD | 可用于方法上 |
TYPE | 可用于类或者接口上 |
ANNOTATION_TYPE | 可用于注解类型上(被@interface修饰的类型) |
CONSTRUCTOR | 可用于构造方法上 |
FIELD | 可用于域上 |
LOCAL_VARIABLE | 可用于局部变量上 |
PACKAGE | 用于记录java文件的package信息 |
PARAMETER | 可用于参数上 |
import java.lang.annotation.*;
@MyAnnotation
public class Demo02 {
void test(){
}
}
//定义一个注解
//Target 表示我们的注解可以用在哪些地方.
@Target(value = {
ElementType.METHOD, ElementType.TYPE})
//Retention表示我们的注解在什么地方还有效。
// runtime>class>sources
@Retention(value = RetentionPolicy.RUNTIME)
//Documented表示是否将我们的注解生成在Javadoc中
@Documented
//Inherited子类可以继承父类的注解
@Inherited
@interface MyAnnotation{
}
import java.lang.annotation.*;
public class Demo03 {
public static void main(String[] args) {
}
//注解可以显示赋值,如果没有默认值 ,我们就必须给注解赋值
@MyAnnotation2(name = "赵大宝",age = 12)
public void test(){
}
@MyAnnotation3("baobao")//参数只有一个,且参数名为value
public void test1(){
}
}
@Target({
ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//注解的参数:参数类型+参数名();
String name() default "";
int age() default 0;
int id() default -1;// 如果默认值为-1,代表不存在。
String[] schools() default {
"清华大学,辽宁大学"}
}
@Target({
ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
//只有一个参数时,参数名为value时,使用时不需参数名
String value();
}
动态语言
静态语言
Class C= Class.forName("java.lang String");
public class Demo {
public static void main(String[] args) throws ClassNotFoundException {
//方式一:实例化
Demo demo = new Demo();
Class c1 = demo.getClass();
Demo demo2 = new Demo();
Class c2 = demo2.getClass();
//方式一:根据包名路径
Class c3 = Class.forName("com.Demo");
//方式三:通过类名.class
Class c4=Demo.class;
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
/*通过反射获取类的Class对象,
*一个类只有一个Class对象,所以c1,c2,c3,c4的hashcode相同
*一个类被加载后,整个类的结构都会被封装在Class对象中
*/
}
}
java.lang.Class:代表一个类
java.lang.reflect.Method :代表类的方法
java.lang.reflect.Field :代表类的成员变量
java.lang.reflect.Constructor :代表类的构造器
public final Class getClass()
Class本身也是一个类
Class 对象只能由系统建立对象
一个加载的类在JVM中只会有一 个Class实例
一个Class对象对应的是一个加载到JVM中的一个.class文件
每个类的实例都会记得自己是由哪个Class实例所生成
通过Class可以完整地得到一个类中的所有被加载的结构
Class类 是Reflection的根源,针对任何你想动态加载、运行的类) 唯有先获得相应的Class对象
方法名 | 说明 |
---|---|
static Class forName(String name) | 返回指定类名name对应的Class对象 |
Object newInstance() | 调用缺省构造函数,返回Class对象的一个实例 |
String getName() | 返回此Class对象所表示的实体(类、接口、数组类或者void)的名称 |
Class getSuperClass | 返回当前Class对象的父类Class对象 |
Class[] getinterfaces() | 获取当前Class对象的接口 |
ClassLoader getClassLoader() | 返回该类的加载器 |
Constructor[] getConstructors() | 返回一个包含某些Constructor对象的数组 |
Method getMothed(String name,Class… T) | 返回一个Method对象,此对象形参类型为param Type |
Fied[] getDeclaredFields() | 返回Field对象的一个数组 |
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Person s1 = new Student();
//方式一:通过对象获取
Class c1 = s1.getClass();
System.out.println(c1.hashCode());
//方式二:通过forname获取
Class c2 = Class.forName("com.Student");
System.out.println(c2.hashCode());
//通过类名.class获得
Class c3 = Student.class;
System.out.println(c3.hashCode());
//方式四:基本内置类型的包装类都有一个TYPE属性
Class c4 = Integer.TYPE;
System.out.println(c4);
//获得父类类型
Class c5 = c1.getSuperclass();
System.out.println(c5);
}
}
class Person{
...}
class Student extends Person{
...}
import java.lang.annotation.ElementType;
public class Demo {
public static void main(String[] args) {
Class c1 = Object.class; //类 .
Class c2 = Comparable.class; //接口
Class c3 = String[].class; //- -维数组
Class c4 = int[][].class; //二维数组
Class c5 = ElementType.class; //枚举
Class c6 = Override.class; //注解
Class c7 = Integer.class; //基本数据类型
Class c8 = void.class; //void
Class c9 = Class.class; //Class
System.out.println(c1);//class java.lang.Object
System.out.println(c2);//interface java.lang.Comparable
System.out.println(c3);//class [Ljava.lang.String;
System.out.println(c4);//class [[I
System.out.println(c5);//class java.lang.annotation.ElementType
System.out.println(c6);//interface java.lang.Override
System.out.println(c7);//class java.lang.Integer
System.out.println(c8);//void
System.out.println(c9);//class java.lang.Class
//只要元素类型与维度一样,就是同一个Class.
int[] a = new int[10];
int[] b = new int[100];
System.out.println(a.getClass().hashCode());//1163157884
System.out.println(b.getClass().hashCode());//1163157884
}
}
注意:只要元素类型与维度一样,就是同一个Class.
将class字节码文件加载到内存中,并将这些数据转换成方法区中的运行时数据(静态变量、静态代码块、常量池等),在堆中生成一个Class类对象代表这个类(反射原理),作为方法区类数据的访问入口。
将Java类的二进制代码合并到JVM的运行状态之中。
验证
确保加载的类信息符合JVM规范,没有安全方面的问题。
准备
正式为类变量(static变量)分配内存并设置类变量初始值的阶段**(发生在初始化前,基本数据类型为0 引用类型为null,类变量常量的初值即为代码中设置的值,final static tmp = 456, 那么该阶段tmp的初值就是456)**,这些内存都将在方法区中进行分配。注意此时的设置初始值为默认值,具体赋值在初始化阶段完成。
解析
虚拟机常量池内的符号引用替换为直接引用(地址引用)的过程。
两个重点:
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
这个阶段主要是对类变量初始化,是执行类构造器的过程。换句话说,只对static修饰的变量或语句进行初始化。
初始化阶段是执行类构造器< clinit>()方法的过程。类构造器< clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和**静态语句块(static块)**中的语句合并产生的。
当初始化一个类的时候,如果发现其父类还没有进行过初始化、则需要先初始化其父类。
虚拟机会保证一个类的< clinit>()方法在多线程环境中被正确加锁和同步。
package com;
public class Demo {
public static void main(String[] args) {
A a=new A();
System.out.println("初始化完 m 打印的值"+A.m);//100
System.out.println("初始化完 n 打印的值"+A.n);//300
/*
1.加载到内存,产生一个类对应Class对象
2.链接,链接结束后m=0
3.初始化
(){
按顺序收集所有静态语句
System. out . println( "A类静态代码块初始化") ;
m = 300;
m=100;
}
m=100
*/
}
}
class A{
static int m ;
static{
System.out.println("静态代码块初始化");
System.out.println("类变量 m 分配的初始值"+m);
m=100;
System.out.println(m);
n=100;
}
static int n = 300;
public A(){
System.out.println("无参构造初始化");
}
}
静态代码块初始化
类变量 m 分配的初始值0
100
无参构造初始化
初始化完 m 打印的值100
初始化完 n 打印的值300
static变量的初始化是从上往下
public class Demo {
public static void main(String[] args) {
B b = new B();
}
}
class A{
static String str1 = "父类A的静态变量";
String str2 = "父类A的非静态变量";
static {
System.out.println("执行了父类A的静态代码块");
}
{
System.out.println("执行了父类A的非静态代码块");
}
public A(){
System.out.println("执行了父类A的构造方法");
}
}
class B extends A{
static String str1 = "子类B的静态变量";
String str2 = "子类B的非静态变量";
static {
System.out.println("执行了子类B的静态代码块");
}
{
System.out.println("执行了子类B的非静态代码块");
}
public B(){
System.out.println("执行了子类B的构造方法");
}
}
执行了父类A的静态代码块
执行了子类B的静态代码块
执行了父类A的非静态代码块
执行了父类A的构造方法
执行了子类B的非静态代码块
执行了子类B的构造方法
一定会发生类的初始化
不会发生类的初始化
public class Demo {
static {
System.out.println("main类被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//1.主动引用
//Son son = new Son();
/*
* 结果:
* main类被加载
* 父类被加载
* 子类被加载
* */
//反射也会产生主动引用
//Class.forName ("com.reflection.Son");
/*
* 结果:
* main类被加载
* 父类被加载
* 子类被加载
* */
//不会产生类的引用的方法
//System.out.println(Son.b);
/*
* 结果:
* main类被加载
* 父类被加载
* 2
* */
//Son[] array = new Son[5];
/*结果:main类被加载*/
System.out.println(Son.M) ;
/*
* 结果:
* main类被加载
* 1
* */
}
}
class Father {
static int b = 2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father {
static {
System.out.println("子类被加载");
}
static int m = 100;
static final int M = 1;
}
将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang(Class对象)作为方法区中类数据的访问入口。
标准的JavaSE类加载器可以按要求查找类,但一旦某一类被加载到类加载器中,它将维持加载(缓存) 一段时间。不过JVM垃圾回收机制可以回收这些Class对象
引导类加载器(bootstrap class loader)
扩展类加载器(extensions class loader)
应用程序类加载器(application class loader)
自定义类加载器
public static void main(String[] args) throws ClassNotFoundException {
//获取系统的类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@4554617c
//获取扩展类加载器的父类加载器- ->根加载器(C/c++)
ClassLoader grantparent = parent.getParent();
System.out.println(grantparent);//null
//测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.reflection.Demo06").getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
//测试JDK内置的类是谁加载的
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);//null
//如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
/*
C:\Program Files\Java\jdk1.8.0_181\jre\lib\charsets.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\deploy.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\cldrdata.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\dnsns.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jaccess.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\jfxrt.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\localedata.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\nashorn.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunec.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\ext\zipfs.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\javaws.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jce.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfr.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jfxswt.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\jsse.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\management-agent.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\plugin.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\resources.jar;
C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar;
D:\IT\workspase\Test\target\classes;
D:\IT\software\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar
*/
}
某个特定的类加载器接收到类加载的请求时,会将加载任务委托给自己的父类,直到最高级父类引导类加载器(bootstrap class loader),如果父类能够加载就加载,不能加载则返回到子类进行加载。如果都不能加载则报错。ClassNotFoundException
双亲委托机制是为了保证 Java 核心库的类型安全。这种机制保证不会出现用户自己能定义java.lang.Object类等的情况。例如,用户定义了java.lang.String,那么加载这个类时最高级父类会首先加载,发现核心类中也有这个类,那么就加载了核心类库,而自定义的永远都不会加载。
反射获取运行时类的完整结构Field、Method, Constructor、 Superclass、 Interface、 Annotation
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Demo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.User");
//获得包名+类名
//User user = new User();
//Class c2 = user.getClass();
//获得类的信息
System.out.println("获得包名+类名: "+c1.getName());//获得包名+类名
System.out.println("获得类名: "+c1.getSimpleName());//获得类名
//获得类的属性
System.out.println("===========获取类的公开属性和父类的公开属性============");
Field[] fields = c1.getFields();//获取类的公开属性和父类的公开属性
for (Field field : fields) {
System.out.println(field);
}
System.out.println("===========获取类的任何属性============");
fields = c1.getDeclaredFields();//获取类的任何属性
for (Field field : fields) {
System.out.println(field);
}
System.out.println("===========获得指定属性的值============");
//获得指定属性的值
Field id = c1.getField("id");
System.out.println("类的公开属性: "+id);
//获得指定属性的值
Field name = c1.getDeclaredField("name");
System.out.println("获得指定属性的值: "+name);
//获得类的方法
System.out.println("===========获得本类和父类的所有public方法==============");
Method[] methods = c1.getMethods();//获得本类和父类的所有public方法
for (Method method : methods) {
System.out.println("methods: " + method);
}
System.out.println("============获得本类的所有方法=============");
Method[] decmethods = c1.getDeclaredMethods();//获得本类的所有方法
for (Method method : decmethods) {
System.out.println("decmethods: " + method);
}
//获得指定方法
//需要传参数的原因:存在重载,参数可找到指定的方法
System.out.println("============获得指定方法=============");
Method getName = c1.getMethod("getName", null);
Method setName = c1.getMethod("setName", String.class);
System.out.println("getName: "+getName);
System.out.println("setName: "+setName);
//获得构造器
System.out.println("============获得public构造器=============");
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("getConstructors: " + constructor);
}
System.out.println("===========获得所有构造器==============");
Constructor[] constructors1 = c1.getDeclaredConstructors();
for (Constructor constructor : constructors1) {
System.out.println("getDeclaredConstructors: " + constructor);
}
System.out.println("==============获得指定的构造器===========");
//获得指定的构造器
Constructor getDeclaredConstructor = c1.getDeclaredConstructor(String.class, String.class);
System.out.println("指定构造器: " + getDeclaredConstructor);
}
}
class User {
public String id;
private String name;
public User() {
}
private User(String id) {
this.id = id;
}
public User(String id, String name) {
this.id = id;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
protected void add(){
}
}
无参数的构造器
有参数的构造器
通过反射,调用类中的方法,通过Method类完成。
通过Class类的getMethod(String name,Clas…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。
之后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。
Object invoke(Object obj, Object ... args)
public static void main(String[] args) throws IllegalAccessException, InstantiationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得class对象
Class c1 = Class.forName("com.User");
//创建一个对象
System.out.println("==============无参构造器创建一个对象==============");
User user = (User)c1.newInstance();//本质是调用了类的无参构造器
System.out.println(user);
//通过构造器创建对象
System.out.println("=============有参构造器创建一个对象===============");
Constructor constructor = c1.getDeclaredConstructor(String.class,String.class);
User user2 = (User)constructor.newInstance("1","张三");
System.out.println(user2);
System.out.println("id: "+user2.id+" , name: "+user2.getName());
//通过反射调用普通方法
//通过反射获取一个方法
System.out.println("============通过反射调用普通方法================");
//Method getName = c1.getDeclaredMethod("getName",null);
Method setName = c1.getDeclaredMethod("setName",String.class);
//invoke:激活的意思
//参数:对象,方法参数的值
setName.invoke(user,"立良");
System.out.println(user.getName());
System.out.println("============通过反射调用private方法================");
//通过反射操作属性
User user3 = (User)c1.newInstance();
//通过Class对象获取对应属性信息
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性,我们需要关闭程序的安全监测,属性或方法的setAccessible(true)
name.setAccessible(true);
name.set(user3,"小宝");//找到实例变量属性并设置值 静态属性时第一个参数Object为null
System.out.println(user3.getName());
}
Java采用泛型擦除的机制来引入泛型, Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但是, 一旦编译完成,所有和泛型有关的类型全部擦除
为了通过反射操作这些类型, Java新增了ParameterizedType , GenericArrayType ,TypeVariable和WildcardType几种类型来代表不能被归一-到Class类中的类型但是又和原始类型齐名的类型.
参数范型 : getGenericParameterTypes
public void test01(Map<String, User> map, List<User> list) {
System.out.println("test01");
}
public Map<String, User> test02() {
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
System.out.println("----------参数范型-----------");
Method method = Demo.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("参数范型" + genericParameterType);
if (genericParameterType instanceof ParameterizedType) {
Type[] actualTypeAnguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeAngument : actualTypeAnguments) {
System.out.println("实际参数范型" + actualTypeAngument);
}
}
}
System.out.println("----------返回值范型-----------");
Method method1 = Demo.class.getMethod("test02", null);
Type getGenericReturnType = method1.getGenericReturnType();
if (getGenericReturnType instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) getGenericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println("返回值范型" + actualTypeArgument);
}
}
}
----------参数范型-----------
参数范型java.util.Map<java.lang.String, com.User>
实际参数范型class java.lang.String
实际参数范型class com.User
参数范型java.util.List<com.User>
实际参数范型class com.User
----------返回值范型-----------
返回值范型class java.lang.String
返回值范型class com.User
public class Demo {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.User");
//通过反射获取注解
System.out.println("---------通过反射获取注解----------");
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);//@com.reflection.Table(value=db_user)
}
System.out.println("---------获得注解中的value值----------");
//获得注解中的value值
Table table = (Table)c1.getAnnotation(Table.class);
String value = table.value();
System.out.println(value);//db_user
System.out.println("---------获得类指定的注解----------");
//获得类指定的注解
Field f = c1.getDeclaredField("name");
annotations = f.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);//@com.Filed(columnName=db_name, type=varchar, length=3)
}
Filed annotation=f.getAnnotation(Filed.class);
System.out.println(annotation.columnName());//db_name
System.out.println(annotation.length());//3
System.out.println(annotation.type());//varchar
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Filed{
String columnName();
String type();
int length();
}
@Table("db_user")
class User {
@Filed(columnName = "db_id",type = "int",length = 10)
private int id;
@Filed(columnName = "db_age",type = "int",length = 10)
private int age;
@Filed(columnName = "db_name",type = "varchar",length = 3)
private String name;
public User(){
}
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
这个类加载器可以通过java.lang.Thread类的setContextClassLoaser()方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
有了线程上下文类加载器,也就是父类加载器请求子类加载器去完成类加载的动作(即,父类加载器加载的类,使用线程上下文加载器去加载其无法加载的类),这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,实际上已经违背了双亲委派模型的一般性原则。
//mysql获取数据库连接的:
// 加载Class到AppClassLoader(系统类加载器),然后注册驱动类
// Class.forName("com.mysql.jdbc.Driver").newInstance();
String url = "jdbc:mysql://localhost:3306/testdb";
// 通过java库获取数据库连接
Connection conn = java.sql.DriverManager.getConnection(url, "name", "password");
视频教程[狂神说B站]: https://www.bilibili.com/video/BV1p4411P7V3