一、什么是反射

谈java反射前我们先来说说物理界的“反射”,第一次见反射这个词语,估计还是从初中物理中看到的,我们先来看看物理界中反射的定义。

我们可以细读这一句:在分界面上改变传播方向又返回原来物质的现象。

从这句话我们可以得知,通过反射我么可以获取物质的一些属性与特质,同然,在java中反射也具有这样的功能。

java中的反射是指在运行过程中,对任意一个类,都能知道类的所有属性与方法;对任意一个对象,能够调用它任意一个方法与属性;这种动态获取信息和动态调用对象的方法就叫做java语言的反射机制。

接下来代码演示:

//正常调用
ZhangSan zhangSan = new ZhangSan();
zhangSan.setAge(30);
zhangSan.setNickName("法外狂徒");
System.out.println("ZhangSan:"+zhangSan);

//使用反射调用
Class c = Class.forName("pojo.ZhangSan");
Method setAge = c.getMethod("setAge", Integer.class);
Constructor constructor = c.getConstructor();
Object o = constructor.newInstance();
setAge.invoke(o,18);
Method getAge = c.getMethod("getAge");
System.out.println("ZhangSan:"+getAge.invoke(o));

查看输出演示:

上面两段代码产生的效果是一样的,不同是正常调用的这段代码是直接new对象,而反射调用是用类反射调用。

所以什么是反射?

反射是在运行时知道要操作的类是什么,并且在运行时获取类的完整构造,并调用相应方法。

可能这时有小伙伴就要问了,我直接正常调用他不香吗,为啥要废大力气去用反射。

八哥其实也有这样的疑惑,当我百思不得其解时,望向了窗外的一缕反射过来的阳光,原来反射他就是一面镜子,不用管本体在哪,只要由镜子反射过来就可以了,我不用刻意去寻找本体,大道至简,突然想起了马斯克说的一切皆归于物理。


二、为什么要用反射

现在我们说说为啥要用到反射,反射的好处什么。我们先来讲讲跟反射相关的技术点,最开始我们学数据库连接jdbc的时候就用到过。

先看看没有用反射的数据库连接方式

方式一:

//1.注册驱动
DriverManager.registerDriver(new com.mysql.jdbc.Driver());

//2.建立连接
//方法一 参数一:协议+访问数据库,参数二:用户名,参数三:密码
connection = DriverManager.getConnection("jdbc:mysql://localhost/student", "root", "password");

//3.创建statement,跟数据库打交道一定需要这个对象
st = connection.createStatement()

再来看看用反射方法实现数据库连接的

方式二:

//获取properties中的url
String url = prop.getProperty("jdbc.url");
//获取用户名
String username = prop.getProperty("jdbc.username");
//获取密码
String password = prop.getProperty("jdbc.password");

从上面的对比我们就可以看出两种方式的区别。

方式一需要在业务代码层面输入用户名和密码,一旦有配置的变动与修改需要自己去修改代码,然后重新发版上线,这种方法不方便而且成本高。试想一下如果员工A离职了,员工B接受A的代码shi山,如果配置都是在业务代码层面的,B去查找修改时会很不方便。

方式二就显得比较优雅一点,将配置与代码解耦合,所有的配置写在properties文件里,这样如果有配置变动,我们只需要在properties里修改配置就行了。


三、java中用到的反射

在java中很多地方用到了反射。比如动态代理与spring的事务底层都用到了反射。

spring事务基本原理:

spring事务本质是数据库对事务的的支持,没数据库事务支持,Spring无法提供事务功能。

以前我们纯jdbc操作的时候如果用到事务,可按照如下步骤进行:

获取连接 Connection con=DriverManager.getConnection();

开启事务 con.setAutoCommit(true/false);

执行CRUD

提交事务回滚 con.commit();

关闭连接 conn.close();

后来交给spring进行事务管理后,不用写步骤2与4的代码,而是由Spring自动完成。Spring是怎么在CRUD前后开启与关闭事务的呢?下面简单介绍下:

配置文件开启注解驱动,相关类与方法通过注解 @Transactional 标识。

spring启动会解析相关bean,此时查看相关注解类与方法,并且为这些类与方法生成代理,根据@Transactional 相关参数进行配置

真正数据库层的事务提交与回滚通过binlog或者redo log实现。


声明式事务:

Spring支持声明式事务,使用注解选择需要使用事务的方法,使用@Transactional注解在方法上表明方法需要事务支持,基于AOP的实现操作。

AOP代理的两种实现:

jdk是代理接口,私有方法不会存在接口里,因此不会拦截到。

cglib是子类,private方法不会出现在子类,不会被拦截。


Java动态代理

具体有如下四步骤:

实现invocationHandler接口创建自己的调用处理器

通过Proxy类指定ClassLoader对象与interface创建动态代理类

反射机制获得动态代理类构造函数,唯一参数类型调用处理器接口类型。

构造函数创建动态代理类实例,构造时调用处理器对象作为参数传入。

CGLIB代理

cglib封装了asm,运行期间动态生成class

cglib用于AOP,jdk的proxy基于接口,cglib没限制。


原理区别

java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

如果目标对象实现了接口,可以强制使用CGLIB实现AOP

如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

如果是类内部方法直接不是走代理,这个时候可以通过维护一个自身实例的代理。