Spring知识点总结5 反射与代理
反射
java反射机制提供的功能
在运行期间判断任意一个对象所属的类
在运行期间构造任意一个类的对象
在运行期间判断任意一个类所具有的成员变脸和方法
在运行期间调用任何一个对象的成员变量和方法
生成动态代理
反射相关的主要API
java.lang.Class 代表一个类
java.lang.reflect.Method方法
java.lang.reflect.Filed代表成员的变量
java.lang.reflect.Constructor代表类的构造方法
java.lang.Class:是反射的源头
我们创建了一个类 通过编译javac.exe生成对应的.class文件 之后我们使用java.exe加载(JVM的类加载器完成的)此class文件
加载到内存以后就是一个运行时类 存放在缓存区。那么这个运行时类本身就是一个Class的实例 相当于这个运行实例就是这个Class类本身
注意点:每一个运行时类只加载一次 clazz只是一个对象实例 Class clazz= Person.class;//一个叫Class的类 相当于Person运行时本身这个类充当了Class的实例 与new Person一样直接指向
创建clazz对应的运行时类Person类的对象
Class clazz1=String.class;//运行时类是谁就对应Class是谁
有了Class实例以后我们才能进行如下操作
1}创建对应的运行时类的对象
2}可以获取对应时类的完整结构 属性方法构造器内部类父类所在的包以及异常和注解等等
3}调用对应的运行时的类的指定的结构 属性方法和构造器
4}反射的应用 动态代理
如何获取Class实例
1 调用运行时类本身的class属性
@Test
public void Test3(){
Class clazz=Person.class;
System.out.println(clazz.getName());
Class clazz2=Person.class;
System.out.println(clazz2.getName());
}
Person
person
2 通过运行时类的对象来获取
@Test
public void Test4(){
Person p=new Person();
Class clazz=p.getClass();
System.out.println(clazz.getName());
}
Person
反射的动态性就是根据传入类的路径不同,对象也不同,所调用的方法也不相同
@Test
public void Test5() throws ClassNotFoundException {
String className="com.qijie.Person";
Class clazz=Class.forName(className);//获取报名路径
System.out.println(clazz.getName());
}
Person
有了class对象以后能做什么
可以创建类的对象
调用Class对象的newInstance()方法(java9开始直接调用newinstance过时了)
要求:1类必须要有一个无参数的构造器
2类的构造器的访问权限必须足够
运行时类 创建类的时候 尽量保留一个空参构造器
好处:有可能需要通过反射去创建一个类的对象newINstance需要空构造器
每一个类都有子类 都需要调父类空参构造器
创建实例的前提是要有空参构造器 且构造器的权限修饰符权限要足够
@Test
public void Test6() throws Exception {
String className="Person";
Class clazz=Class.forName(className);
// 想要创建成功 要求对应的运行类要有空参构造器 构造器的权限修饰符权限要足够
Object object = clazz.getConstructor().newInstance();//默认返回Object类型 调用空参构造器
Person p=(Person) object;
System.out.println(p);
}
通过反射我们可以获取类的完整结构
实现全部的接口
所继承的父类
全部的构造器
全部的方法
全部的Field(属性)
@Test
// 获取实现的接口
public void Test(){
Class clazz=Person.class;
Class[] interfaces= clazz.getInterfaces();
for(Class i:interfaces){
System.out.println(i);
}
}
// 获取注解
@Test
public void test(){
Class clazz=Person.class;
Annotation[] annotations=clazz.getAnnotations();
for(Annotation annotation:annotations){
}
获取运行时类的属性
public void Test(){
Class clazz=Person.class;
//getFiled只能获取到运行时类及其父类中的声明的为public的属性
Field[] fields= clazz.getFields();//返回的是field类型的数组
for(int i=0;i<fields.length;i++){
System.out.println(fields[i]);
}
// 2getDeclaredFields();获取运行时类本身声明的所有属性
Field[] fields1 =clazz.getDeclaredFields();
for(Field f:fields1){
System.out.println(f.getName());
}
}
获取运行时类的方法
// 获取运行时类的方法
@Test
public void test1() {
Class clazz = Person.class;
// getMethod 获取运行时类及其父类中所有的声明的public的方法
Method[] m1 = clazz.getMethods();
for (Method m : m1) {
System.out.println(m);
}
System.out.println();
// getdeclaredMethod 获取运行时类本身所有的方法
Method[] m2 = clazz.getDeclaredMethods();
for (Method m : m2) {
System.out.println(m);
}
}
通过反射调用类中指定方法和指定属性
1调用指定方法
通过反射调用类中的方法,通过Method类完成
通过Class类的getMethod()方法取得一个Method对象,并设置此方法操作时需要的参数类型
2之后使用objectinvoke()进行调用 并向方法中传递要设置的obj对象的参数信息
为属性赋值需要先获取属性
Accessable属性是继承自AccessibleObject 类. 功能是启用或禁用安全检查
setAccessible()
允许访问私有属性
// 调用运行时类中指定的某个属性
@Test
public void Test3() throws Exception {
Class clazz=Person.class;
// 获取指定的属性
// getfield(String fieldName);获取运行时类中声明为public的指定属性名为fieldName的属性
// getDeclaredfield(String fieldName)获取运行时类中指定为名为fieldName的属性 如果是私有的需要使用或者超出本身修饰符范围的类 需要使用setAccessible
Field field1= clazz.getField("name");
// 想要调用属性必须创建运行时类的对象
Person p= (Person) clazz.newInstance();//此时调用的是空参构造器
System.out.println(p);
//将运行时类指定的属性赋值
field1.set(p,"Jerrry");
System.out.println(p);
Field field2=clazz.getDeclaredField("age");
field2.setAccessible(true);
field2.set(p,22);
System.out.println(p);
}
// 调用运行时类中指定的方法
@Test
public void Test3() throws Exception {
Class clazz=Person.class;
// getMethod运行时声明为public的指定的方法
Method method1=clazz.getMethod("show");//有形参需要写
Method method2=clazz.getMethod("toString");
Person p= (Person) clazz.newInstance();
// 调用指定的方法是invoke 有形参的话需要些具体参数 否则包异常 方法是有返回值的 如果没有返回值的话需要 使用Object
Object returnValue1= method1.invoke(p);//我是一个人
Object returnVulue2=method2.invoke(p);
System.out.println(returnValue1);//因为没有返回值所以是null
System.out.println(returnVulue2);//Perosn name=null age=0
// 对于运行类静态方法的调用
Method method3=clazz.getMethod("info");
method3.invoke(Person.class);
//getDeclaredMehtod(String。。。)获取运行时类中声明了的指定的方法
Method method4= clazz.getDeclaredMethod("display", String.class,Integer.class);//首先获取方法
//其次调用方法 由于此方法时私有属性苏哦一需要setAccessible
method4.setAccessible(true);
Object Value=method4.invoke(p,"CNN",10);//我的国际时 CNN 10
System.out.println(Value);//输出的返回值10
}
调用指定的构造器
//调用指定的构造器 创建运行时类的对象 反射使用的时newInstance来通过空构造器来创建类的对象
// 所以我们一般提前设置好空构造器 但是没有空构造器我们如何来创建对象 我们可以调用指定的构造器来创建
@Test
public void Test4() throws Exception {
String className="com.qijie.java.Person";
Class clazz=Class.forName(className);
Constructor constructor=clazz.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
Person person2=(Person) constructor.newInstance("scl" ,21);
System.out.println(person2);
其他类调用类的方法和属性还有变量,需要创建对象
package com.qijie.java;
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
public void show(){
System.out.println("我是一个人");
}
public void display(String nation){
System.out.println("我的国籍是:"+nation);
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试:
import org.junit.Test;
public class TestReflection {
@Test
public void Test1(){
Person person=new Person();
person.setAge(12);
person.setName("石晨霖");
System.out.println(person);
person.show();
person.display("HK");
}
}
有了反射后可以通过反射创建一个类的对象并调用其中的结果 有反射前 我们如何创建一个类的对象并对用其中的方法或属性
在一个类中利用反射来调用另一个类的属性和方法
clazz是反射的源头
/**
* @author qijie
* @date 2022/7/29 21:52
*/
public class TestReflection {
@Test
public void test1() throws Exception {
Class clazz=Person.class;
Person person= (Person) clazz.getConstructor().newInstance();
System.out.println(person);
System.out.println("-----------------------分割线-------------------------------------------");
// getDeclaredField 获取类的属性
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
field.set(person,"scl");
// 因为clazz对象完全掌握Person所有信息 再通过clazz创建的对象p得到属性 f1时通过反射的方法来创建的对象 从而使得f1来改变属性的信息
System.out.println(person);
System.out.println("-----------------------分割线-------------------------------------------");
Field age = clazz.getDeclaredField("age");
age.setAccessible(true);
age.set(person,10);
System.out.println(person);
System.out.println("-----------------------分割线-------------------------------------------");
// /通过反射调用属性的方法 因为方法会重载 所以需要标注其中的形参 没有形参则不需要
Method show = clazz.getMethod("show");
show.invoke(person);
System.out.println("-----------------------分割线-------------------------------------------");
Method display = clazz.getMethod("display", String.class);
display.invoke(person,"China");
System.out.println("-----------------------分割线-------------------------------------------");
}
结果
最初的Person p=newPerson()
我们创建person对象
我们是先看Person类是否在内存中缓存下来,若没有他就去加载 然后有Class 如果没有反射就是直接调用空参构造器
通过反射的话也还是一样 ,创建Class再通过newInstance来调用空参构造器
属于是getclass方法是找到谁创建的这个对象的原本的类
属于是儿子找到了爸爸
@Test
public void Test2(){
Person person=new Person();
Class clazz=person.getClass();
System.out.println(clazz);
}
结果class person
静态与动态代理
代理模式介绍
是一种设计模式,提供了对目标对象额外的访问方式,即通过代理对象访问目标对象,这样可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能。
简言之,代理模式就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。
举个例子,我们生活中经常到火车站去买车票,但是人一多的话,就会非常拥挤,于是就有了代售点,我们能从代售点买车票了。这其中就是代理模式的体现,代售点代理了火车站对象,提供购买车票的方法。
静态代理
这种代理方式需要代理对象和目标对象实现一样的接口。
优点:可以在不修改目标对象的前提下扩展目标对象的功能。
缺点:
-
冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
-
不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
举例:保存用户功能的静态代理实现
接口A
public interface UserA {
public void save();
}
目标对象
//目标对象
public class UserB implements UserA{
@Override
public void save() {
System.out.println("这是B方法");
}
}
代理类
public class UserC implements UserA{
private UserA userA;
public UserC(UserA userA){
this.userA=userA;
}
@Override
public void save() {
System.out.println("这是C方法");
userA.save();
System.out.println("这是D方法");
}
}
测试类
public class ProxyTest {
@Test
public void TestProxy(){
// 目标对象
UserA userB=new UserB();
UserC userC=new UserC(userB);
userC.save();
}
}
输出结果
这是C方法 这是B方法 这是D方法
在spring中也类似只不过代理类使用了构造器的方式来实现依赖注入
动态代理
动态代理利用了,动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。
静态代理与动态代理的区别主要在:
-
静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
-
动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
特点: 动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。
JDK提供了 java.lang.reflect. InvocationHandler java.lang.reflect.proxy类两个互相配合入口时Proxy
-
,主要方法为
static Object newProxyInstance(ClassLoader loader, //指定当前目标对象使用类加载器
Class<?>[] interfaces, //目标对象实现的接口的类型
InvocationHandler h //事件处理器
)
//只要出入类加载器和一组接口 事件处理器 他就给你返回代理Class对象
所以我们一旦明确接口 就可以通过接口的Class对象创建一个代理Class 通过代理Class即可创建代理对象
-
,主要方法为
Object invoke(Object proxy, Method method, Object[] args)
// 在代理实例上处理方法调用并返回结果。
接口A
public interface UserA {
public void save();
}
目标对象
//目标对象
public class UserB implements UserA{
@Override
public void save() {
System.out.println("这是B方法");
}
}
动态代理类:
凡是与动态代理相关的都需要一个InvocationHandler接口
public class ProxyFactory implements InvocationHandler {
private Object target;//实现了接口的被代理类的对象的声明 相当于RealSubjet的对象
public ProxyFactory(Object target){
this.target=target;
}
// 为了目标对象生成代理对象
public Object getProxyInstance(){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理方法");
Object result = method.invoke(target,args);
return null;
}
}
测试类
*/public class ProxyTest {
@Test
public void TestProxy(){
// 目标对象
UserA userB=new UserB();
UserA userC=(UserA) new ProxyFactory(userB).getProxyInstance();
userC.save();
}
}
代理方法 这是B方法
我们知道了接口UserA ,UserB是实现UserA接口的一个目标类,我们的目的是写一个增强方法来增强UserB
由于我们知道了接口UserA,实际就是一个被代理类,根据反射可以得到被代理类(接口)的类加载器,接口,方法等等
通过反射实例化Proxy代理类的对象,以及重写的处理方法,实现对目标对象的增强
cglib代理
(Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
cglib特点
-
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。 如果想代理没有实现接口的类,就可以使用CGLIB实现。
-
CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。 它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
-
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。 不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
cglib与动态代理最大的区别就是
-
使用动态代理的对象必须实现一个或多个接口
-
使用cglib代理的对象则无需实现接口,达到代理类无侵入。
使用cglib需要引入,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version> 3.2.12</version>
</dependency>
目标对象
public class UserD {
public void save(){
System.out.println("D方法");
}
}
代理类
public class ProxyFactory implements MethodInterceptor {
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
public Object getProxyInstance(){
//工具类
Enhancer en = new Enhancer();
// 设置父类
en.setSuperclass(target.getClass());
// 设置回调函数
en.setCallback(this);
// 创建子类代理对象
Object childobject = en.create();
return childobject;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 执行目标对象的方法
Object invoke = method.invoke(target,objects);
System.out.println("代理类方法");
return null;
}
}
测试
@Test
public void ProxyTest(){
UserD target=new UserD();
UserD proxyInstance = (UserD) new ProxyFactory(target).getProxyInstance();
proxyInstance.save();
}
D方法 代理类方法
总结
-
静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。
-
JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。
-
静态代理在编译时产生class字节码文件,可以直接使用,效率高。
-
动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
-
cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。
jdk动态代理对象效率高,但是执行效率低,CGLib创建效率低但是执行效率高(直接修改字节码得到)。但是jdk动态代理效率越来越好,所以spring中通常使用jdk动态代理,不能使用再使用CGLib动态代理。
反射的知识点总结
什么是反射?
反射是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
哪里用到反射机制?
-
JDBC中,利用反射动态加载了数据库驱动程序。
-
Web服务器中利用反射调用了Sevlet的服务方法。
-
Eclispe等开发工具利用反射动态刨析对象的类型与结构,动态提示对象的属性和方法。
-
很多框架都用到反射机制,注入属性,调用方法,如Spring。
. 反射机制的优缺点?
-
优点:可以动态执行,在运行期间根据业务功能动态执行方法、访问属性,最大限度发挥了java的灵活性。
-
缺点:对性能有影响,这类操作总是慢于直接执行java代码。
动态代理是什么?有哪些应用?
-
动态代理是运行时动态生成代理类。
-
动态代理的应用有 Spring AOP数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。
怎么实现动态代理?
-
JDK 原生动态代理和 cglib 动态代理。
-
JDK 原生动态代理是基于接口实现的,而 cglib 是基于继承当前类的子类实现的。
Java反射机制的作用
在运行时判断任意一个对象所属的类 在运行时构造任意一个类的对象 在运行时判断任意一个类所具有的成员变量和方法 在运行时调用任意一个对象的方法
反射的应用场景
反射常见的应用场景
-
Spring 实例化对象:当程序启动时,Spring 会读取配置文件
applicationContext.xml
并解析出里面所有的 标签实例化到IOC
容器中。 -
反射 + 工厂模式:通过
反射
消除工厂中的多个分支,如果需要生产新的类,无需关注工厂类,工厂类可以应对各种新增的类,反射
可以使得程序更加健壮。 -
JDBC连接数据库:使用JDBC连接数据库时,指定连接数据库的
驱动类
时用到反射加载驱动类
Spring 的 IOC 容器
在 Spring 中,经常会编写一个上下文配置文件applicationContext.xml
,里面就是关于bean
的配置,程序启动时会读取该 xml 文件,解析出所有的 <bean>
标签,并实例化对象放入IOC
容器中。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="smallpineapple" class="com.bean.SmallPineapple">
<constructor-arg type="java.lang.String" value="小菠萝"/>
<constructor-arg type="int" value="21"/>
</bean>
</beans>COPY
在定义好上面的文件后,通过ClassPathXmlApplicationContext
加载该配置文件,程序启动时,Spring 会将该配置文件中的所有bean
都实例化,放入 IOC 容器中,IOC 容器本质上就是一个工厂,通过该工厂传入 \ 标签的id
属性获取到对应的实例。
public class Main {
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
SmallPineapple smallPineapple = (SmallPineapple) ac.getBean("smallpineapple");
smallPineapple.getInfo(); // [小菠萝的年龄是:21]
}
}COPY
Spring 在实例化对象的过程经过简化之后,可以理解为反射实例化对象的步骤:
-
获取Class对象的构造器
-
通过构造器调用 newInstance() 实例化对象
当然 Spring 在实例化对象时,做了非常多额外的操作,才能够让现在的开发足够的便捷且稳定。
反射 + 抽象工厂模式
传统的工厂模式,如果需要生产新的子类,需要修改工厂类,在工厂类中增加新的分支;
public class MapFactory {
public Map<Object, object> produceMap(String name) {
if ("HashMap".equals(name)) {
return new HashMap<>();
} else if ("TreeMap".equals(name)) {
return new TreeMap<>();
} // ···
}
}COPY
利用反射和工厂模式相结合,在产生新的子类时,工厂类不用修改任何东西,可以专注于子类的实现,当子类确定下来时,工厂也就可以生产该子类了。
反射 + 抽象工厂的核心思想是:
-
在运行时通过参数传入不同子类的全限定名获取到不同的 Class 对象,调用 newInstance() 方法返回不同的子类。细心的读者会发现提到了子类这个概念,所以反射 + 抽象工厂模式,一般会用于有继承或者接口实现关系。
例如,在运行时才确定使用哪一种 Map
结构,我们可以利用反射传入某个具体 Map 的全限定名,实例化一个特定的子类。
public class MapFactory {
/**
* @param className 类的全限定名
*/
public Map<Object, Object> produceMap(String className) {
Class clazz = Class.forName(className);
Map<Object, Object> map = clazz.newInstance();
return map;
}
}COPY
className
可以指定为 java.util.HashMap,或者 java.util.TreeMap 等等,根据业务场景来定。
JDBC 加载数据库驱动类
在导入第三方库时,JVM不会主动去加载外部导入的类,而是等到真正使用时,才去加载需要的类,正是如此,我们可以在获取数据库连接时传入驱动类的全限定名,交给 JVM 加载该类。
public class DBConnectionUtil {
/** 指定数据库的驱动类 */
private static final String DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
public static Connection getConnection() {
Connection conn = null;
// 加载驱动类
Class.forName(DRIVER_CLASS_NAME);
// 获取数据库连接对象
conn = DriverManager.getConnection("jdbc:mysql://···", "root", "root");
return conn;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?