根据个人学习情况,筛选出特别关注的信息,完整内容请看原版,仅仅是个人学习笔记。
目录
一、IoC类型
IoC(inverse of Control) 包括两方面内容:控制和反转
主要可以划分为3种类型:构造函数注入、属性注入和接口注入。
故事情节1:有一个剧本《墨攻》MoAttack类,通过调用类的构造函数,不需要关心具体由谁来饰演隔离这个角色,在①处传入饰演者并按照剧本要求完成表演即可。
(1)构造函数注入
/*剧本类*/
public class MoAttack {
private GeLi geli;
//①注入隔离的具体饰演者
public Moattack(GeLi geli) {
this.geli = geli;
}
public void cityGateAsk() {
geli.responseAsk("墨者隔离");
}
}
故事情节2:角色的具体饰演者由导演来安排
/*导演类*/
public class Director{
public void direct() {
//指定角色的饰演者
GeLi geli = new LiuDeHua();
//注入具体饰演者到剧本中
MoAttack moAttack = new MoAttack(geli);
moAttack.cityGateAsk();
}
}
故事情节3:虽然饰演隔离者是影片第一主角,但并非每个场景需要隔离者的出现,在这个情况下通过构造函数的注入方式并不妥当,这时考虑使用属性注入,有选择的通过Setter方法完成调用类所需依赖的注入。
(2)属性注入
/*剧本类*/
public class MoAttack {
private GeLi geli;
public void setGeLi(GeLi geli) {
this.geli = geli;
}
public void cityGateAsk() {
geli.responseAsk("墨者隔离");
}
}
/*导演类*/
public class Director{
public void direct() {
MoAtack moAttack = new MoAttack();
//调用属性Setter方法注入
GeLi geli = new LiuDeHua();
moAttack.setGeLi(geli);
moAttack.cityGateAsk();
}
}
(3)接口注入(不推荐)
为了采取接口注入,需要先声明一个ActorArrangable接口,如下所示:
public interface ActorArrangable{
void injectGeLi(GeLi geli);
}
/*剧本类*/
public class MoAttack implements ActorArrangable{
private GeLi geli;
public void injectGeLi(GeLi geli) {
this.geli = geli;
}
public void cityGateAsk() {
geli.responseAsk("墨者隔离");
}
}
/*导演类*/
public class Director{
public void direct() {
MoAtack moAttack = new MoAttack();
GeLi geli = new LiuDeHua();
moAttack.injectGeLi(geli);
moAttack.cityGateAsk();
}
}
由于通过接口注入需要额外声明一个接口,增加了类的数目,而且实现效果和属性注入无本质区别,所以不提倡采用这种方式。
二、通过容器完成依赖关系的注入
Spring通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入工作。以下为Spring实例的配置文件片段:
<?xml version="1.0" encoding="UTF-8" ?>
<bean xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w4.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<!-- 实现类实例化 -->
<bean id="geli" class="LiuDeHua" />
<!-- 通过geli-ref建立依赖关系 -->
<bean id="moAttack" class="com.smart.ioc.Moattack"
p:geli-ref="geli" />
</beans>
通过new XmlBeanFactory("beans.xml")等方式即可启动容器,自动实例化Bean并完成依赖关系的装配,从容器中即可返回准备就绪的Bean实例,后续可直接使用。以上实现是归功于Java语言本身的类反射功能。下面请了解Java语言的反射知识。
三、Java反射机制
代码示例如下:
/*汽车类,测试反射机制调用其方法*/
public class Car {
private String brand;
private String color;
private int maxSpeed;
public Car(){}
public Car(String brand, String color, int maxSpeed) {
this.brand = brand;
this.color = color;
this.maxSpeed = maxSpeed;
}
//省略get和set方法
...........................
public void instroduce() {
System.out.println("brand:" + brand + "|color:" + color + "|maxSpeed:" + maxSpeed);
}
}
/* 测试反射机制的类 */
public class ReflectTest {
public static Car initByDefaultConst() throws Throwable {
//① 通过类装载器获取Car类对象
ClassLoader loader = Thread.currentThread().getContextClassLoader();
//查看类加载器
System.out.println("current loader:" + loader);
System.out.println("parent loader:" + loader.getParent());
System.out.println("grandparent loader:" + loader.getParent().getParent());
Class clazz = loader.loadClass("com.imooc.demo.reflect.Car");
//② 获取类的默认构造器对象并通过它实例化Car
Constructor cons = clazz.getDeclaredConstructor((Class[])null);
Car car = (Car) cons.newInstance();
//③ 通过反射方法设置属性
Method setBrand = clazz.getMethod("setBrand",String.class);
setBrand.invoke(car,"红旗CA72");
Method setColor = clazz.getMethod("setColor",String.class);
setColor.invoke(car,"黑色");
Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class);
setMaxSpeed.invoke(car, 280);
return car;
}
public static void main(String[] args) throws Throwable {
Car car = initByDefaultConst();
car.instroduce();
}
}
输出结果:
current loader:sun.misc.Launcher$AppClassLoader@18b4aac2
parent loader:sun.misc.Launcher$ExtClassLoader@7cc355be
grandparent loader:null
brand:红旗CA72|color:黑色|maxSpeed:280
四、类装载器ClassLoader
类装载器就是寻找类的节码文件并构造出类在JVM内部标识对象的组件。在Java中,类加载器把一个类装入JVM中,经过以下步骤:
(1)装载:查找和导入Class文件。
(2)链接:执行校验、准备和解析步骤,其中解析步骤是可以选择的。
2-1 校验:检查载入Class文件数据的正确性。
2-2 准备:给类的静态变量分配存储空间。
2-3 解析:将符号引用转换成直接引用。
(3)初始化:对类的静态变量、静态代码块执行初始化工作。
- 类加载工作由ClassLoader及其子类负责。ClassLoader负责在运行时查找和装入Class字节码文件。JVM在运行时会产生3个ClasLoader:根装载器、ExtClassLoader(扩展类装载器)和AppClassLoader(应用类装载器)。其中根加载器不是ClassLoader的子类,使用C++语言编写,在Java中看不到它,根装载器负责装载JRE的核心类库,如JRE目标下的rt.jar、charsets.jar等。ExtClassLoader和AppClassLoader都是ClassLoader的子类,其中ExtClassLoader负责装载JRE扩展母乳ext中的JAR类包;AppClassLoader负责装载Classpath路径下的类包。
- 这3个类装载器之间存在父子层级关系,即根装载器是在ExtClassLoader的父装载器,ExtClassLoader是AppClassLoader的父装载器。默认情况下,使用AppClassLoader装载应用程序的类。
代码示例如下:
public classClassLoaderTest{
public static void main(String[] args) {
//① 通过类装载器获取Car类对象
ClassLoader loader = Thread.currentThread().getContextClassLoader();
//查看类加载器
System.out.println("current loader:" + loader);
System.out.println("parent loader:" + loader.getParent());
System.out.println("grandparent loader:" + loader.getParent().getParent());
}
}
输出结果:
current loader:sun.misc.Launcher$AppClassLoader@18b4aac2
parent loader:sun.misc.Launcher$ExtClassLoader@7cc355be
grandparent loader:null
JVM装载类时使用"全盘负责委托机制","全盘负责"是指当一个ClassLoader装载一个类时,除非显示使用另一个ClassLoader,该类所依赖及引用的类也应由这个ClassLoader载入;"委托机制"是指先委托父装载器寻找目标类,只有在找不到的情况下才从自己的类路径中查找并装载目标类。这一点是从安全角度考虑的。
比如说:我们单独写一个java.lang.String:
package java.lang;
public class String {
public String(String msg) {
System.out.println("String");
System.out.println(msg);
}
}
那么我们在其他类调用java.lang.String会出现什么结果呢,比如
System.out.println(new String("myString")); //代码写完会标识报错