反射技术
为什么需要反射技术。
在看完咕泡学院介绍的“手把手带你手写tomcat”之后(https://v.qq.com/x/page/i0508wo8qeg.html),
了解了Tomcat如何对待GET请求中诸如 /dy/AServlet?name="abc" 的数据,Tomcat的解析过程是:
1、在启动时,加载所有的Servlet到内存中的一个List<Map>里,List的数据来源于WEB-INF中的web.xml,其中每个Map代表<url-pattern>和一个经过加载的Servlet类的映射(这里就用到反射技术)。
2、Tomcat主运行类中,试图抓取并匹配GET 请求中的uri地址字符串(注: uri指的是: www.baidu.com/AServlet 中 /AServlet 即称为uri),在List中遍历,看每个map中是否有这样的key存在,找不到就报404响应,找到就创建对应的HttpServlet对象,调用service方法,也就进而调用doGet方法去处理 请求和 写回响应。
这一系列过程,特别是Map中的"经过加载的Servlet类的映射",由于uri是服务器由浏览器的HTTP请求而得到的,这在服务器的Web项目运行之前是未知的。
使我想起了在JavaSe的IO那一章中,讲内存中的输入数据来源,有3个位置:本地硬盘文件、控制台输入、网络上。
这里等于uri来源于网络上,所以服务器不知道会传入何种URI;
下面("plus_a" 、“Test”、x ),全部是参数,参数就是一个函数方法的输入,如果采用如下反射的方式执行这些函数,这些对于服务器来说的输入,都可以来自于HTTP请求中。
而反射技术中,最常见的,调用Test类中的成员方法plus_a的方式:
Method m = Test.class.getDeclaredMethod("plus_a");
m.setAccessible(true); //如果不加上,不能访问private的方法
Test x = Class.forName(“Test”).newInstance(); //这步,相当于是Test x = new Test();
m.invoke(x); //这步,相当于是 x.plus_a
//还有一种语法,仅能获取到类中的(或继承到的)public修饰的成员方法
Method m = Test.class.getMethod("plus_a");
在构造对象时,如果要调用类的含参构造方法,可以用下面的方式:String.class这里指的就是单参数的构造方法,参数类型就是String,即,多了个 .getConstructor(String.class)
Test x = Class.forName(“Test”).getConstructor(String.class).newInstance("张三"); //这步,相当于是Test x = new Test("张三");
所以,我们经常说反射用于解耦,从IO的角度来说,也可以解释。
如果我们不用反射,简单来说,当面对外界未知的输入时,就需要用很多的if语句去一个个的预测匹配,进而执行对应的设计好的代码。
当如果用反射,我们可以直接提取外界的输入,作为参数去执行方法,当然,如果参数是方法不支持的,是不能让它往里面传的,这就要求我们在传参之前,要判断是否我们有对应设计的参数,就像Tomcat处理uri来执行对应的Servlet代码一样。
别人说的反射的动态性 其实就是程序跑起来之后 参数的输入才过来 这就叫所谓的动态,而不是把参数写死在代码中。
那根据本文的观点,什么时候不需要用反射呢?
当方法的参数,是固定的,数据源根本不会来自各种IO输入流时,那么就无需使用反射技术。
IoC (DI) 原理
Spring中的控制反转 IoC 的实现原理就是工厂模式加反射机制。
1.我们首先看一下不用反射机制时的工厂模式,工厂模式是一种设计风格(设计模式):
/**
* 工厂模式
*/
interface fruit{
public abstract void eat();
}
class Apple implements fruit{
public void eat(){
System.out.println("Apple");
}
}
class Orange implements fruit{
public void eat(){
System.out.println("Orange");
}
}
// 构造工厂类
// 也就是说以后如果我们在添加其他类的实例时,只需要修改工厂类就行了
class Factory{
public static fruit getInstance(String fruitName){
fruit f=null;
if("Apple".equals(fruitName)){
f=new Apple();
}
if("Orange".equals(fruitName)){
f=new Orange();
}
return f;
}
}
public class Test{
public static void main(String[] args){
fruit f=Factory.getInstance("Orange");
f.eat();
}
}
当我们在添加一个子类的时候,就需要修改工厂类了。但如果我们添加太多的子类的时候,改的就会很多。
2. 利用反射机制的工厂模式:
package Reflect; //该代码文件在Reflect包中
interface fruit{
public abstract void eat();
}
class Apple implements fruit{
public void eat(){
System.out.println("Apple");
}
}
class Orange implements fruit{
public void eat(){
System.out.println("Orange");
}
}
class Factory{
public static fruit getInstance(String str){
fruit f=null;
try{
f=(fruit)Class.forName(str).newInstance(); //注意这里,相当于是
// Class<?> X = Class.forName(str); 此步称为加载,会执行类中的静态代码块;Class<?> 是未知的泛型类型
// Test x = ( fruit ) X.newInstance(); 此步会创建一个对象;
}catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
public class Test{
public static void main(String[] a){
fruit f=Factory.getInstance("Reflect.Apple"); //注意这里
if(f!=null){
f.eat();
}
}
}
关于Class类下的方法 forName(String str) 以及进而使用的 newInstance()的例子如下:
这是经常从文件、从HTTP请求中获得将要实例化的类名,与之前学习的new 类名是类似的。
区别在于,使用Class.forName(String str).newInstance() ,会分两步进行:
1、Class.forName(String str) 此步进行的是称为“类的加载”的过程,效果就是查找该类是不是已经导入(是否能访问到该类),并且会执行该类的static代码块;
2、 类.newInstance() 此步进行的是,创建一个该类的对象。
我们之前用的new 类名;其实是把上面两个步骤混在了一起,直接一步到位了。而上面的Class.forName(String str).newInstance()是只有在JavaWeb阶段才会遇到的内容。
import java.io.*;
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
Class<?> X = Class.forName("Test");
Test x = (Test) X.newInstance();
}
static {
System.out.print("你好");
}
public void hello() {
System.out.print("hello");
}
}
运行结果: 你好
使用反射机制的工厂模式可以通过反射取得接口的实例,但是需要传入完整的包和类名。
而且用户也无法知道一个接口(这里是fruit接口)有多少个可以使用的子类(这里是具体水果类),所以我们通过属性文件的形式配置所需要的子类。
3.使用反射机制并结合读取硬盘文件的工厂模式(即IoC)
首先创建一个名为 fruit.properties 的硬盘文件:文件中只有两行配置
apple=Reflect.Apple
orange=Reflect.Orange
package Reflect;
import java.io.*;
import java.util.*;
interface fruit{
public abstract void eat();
}
class Apple implements fruit{
public void eat(){
System.out.println("Apple");
}
}
class Orange implements fruit{
public void eat(){
System.out.println("Orange");
}
}
//操作属性文件类,主要是Properties类的使用
class init{
public static Properties getPro() throws FileNotFoundException, IOException{
Properties pro=new Properties();
File f=new File("fruit.properties");
if(f.exists()){
pro.load(new FileInputStream(f));
}else{
pro.setProperty("apple", "Reflect.Apple");
pro.setProperty("orange", "Reflect.Orange");
pro.store(new FileOutputStream(f), "FRUIT CLASS");
}
return pro;
}
}
class Factory{
public static fruit getInstance(String str){
fruit f=null;
try{
f=(fruit)Class.forName(str).newInstance();
}catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
public class Test{
public static void main(String[] a) throws FileNotFoundException, IOException{
Properties pro=init.getPro();
fruit f=Factory.getInstance(pro.getProperty("apple"));//这里就是通过 pro.getProperty方法获得apple对应的value,即类名
if(f!=null){
f.eat();
}
}
}
//【运行结果】:Apple
在平时的java应用开发中,我们要实现某一个功能或者说是完成某个业务逻辑时至少需要两个或以上的对象来协作完成.
在没有使用Spring的时候,每个对象在需要使用他的合作对象时,自己均要使用像new object() 这样的语法来将合作对象创建出来,这个合作对象是由自己主动创建出来的,创建合作对象的主动权在自己手上,自己需要哪个合作对象,就主动去创建,创建合作对象的主动权和创建时机是由自己把控的,而这样就会使得对象间的耦合度高了,A对象需要使用合作对象B来共同完成一件事,A要使用B,那么A就对B产生了依赖,也就是A和B之间存在一种耦合关系,并且是紧密耦合在一起,而使用了Spring之后就不一样了,创建合作对象B的工作是由Spring来做的,Spring创建好B对象,然后存储到一个容器里面,当A对象需要使用B对象时,Spring就从存放对象的那个容器里面取出A要使用的那个B对象,然后交给A对象使用,至于Spring是如何创建那个对象,以及什么时候创建好对象的,A对象不需要关心这些细节问题(你是什么时候生的,怎么生出来的我可不关心,能帮我干活就行),A得到Spring给我们的对象之后,两个人一起协作完成要完成的工作即可。
所以控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。
面向切面编程AOP
指的就是在执行方法(函数)时,在其上方、下方各插入一些固定的模板,把原先的方法夹在了中间,形成了一个切面。
AOP的思想,在Spring的数据库事务处理中体现很多。
Spring的事务机制包括声明式事务和编程式事务:
1) 编程式事务管理:Spring推荐使用TransactionTemplate。 但在实际开发中使用声明式事务较多。
2) 声明式事务管理:将我们从复杂的事务处理中解脱出来,获取连接,关闭连接、事务提交、回滚、异常处理等这些操作都不用我们处理了,Spring都会帮我们处理。
声明式事务管理使用了AOP面向切面编程实现的,本质就是在目标方法执行前后进行添加其他内容。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。
AOP的思想,还会经常用在日志 等场景之下。