Spring
如果你对Spring有一些认识之后,你一定就会觉得Spring就是一个容器。
首先了解一下Spring是为了解决对象与对象之间耦合性的框架
理解耦合与非耦合的区别
public class TestService {
public void foo() {
System.out.println("hahahahha");
}
}
public class Main {
public static void main(String[] args) {
TestServie service = new TestService();//简单的创建对象的过程
service.foo();
}
}
上面的Main和TestService之间是强耦合关系。因为如果将TestService删除掉,Main中创建对象的过程会报错,这就是强耦合。要是删除掉不报错叫做弱耦合关系。
public class FooService {
public void foo() {
System.out.println("我是Foo");
}
}
-------------------------------------------------------------------------------------
public class TestService {
private FooServie service = new FooService();
public void foo() {
service.foo();
}
}
这时如果删除掉FooService方法,Main不会报错,而是TestService报错,这时Main和FooService之间就不存在了强耦合关系。但是TestService和FooService、TestService和Main之间还存在强耦合关系。只要强耦合链上有一个出了问题程序都不能正常执行。
可以通过工厂模式来解耦合
解耦合之后
public interface IService {
public void foo();
}
-------------------------------------------------------------------------------------
public class FooService implements IService {
@Override
public void foo() {
System.out.println("我是Foo");
}
}
-------------------------------------------------------------------------------------
public class TestServiceFactory {
/*
public static IService create() {
return new FooService();//TestServiceFactory依然和FooService存在耦合关系
}
*/
public static Map<String, Object> map = new HashMap<>();//Spring底层用的是ConcurrentHashMap
//消除TestServiceFactory和FooService存在的耦合关系
public static Object create(String name) {
if (map.containsKey(name)) {
return map.get(name);
}
Object o = null;
try {
Class aClass = Class.forName(name);
o = aClass.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
map.put(name, o);
return o;
}
}
-------------------------------------------------------------------------------------
public class Main {
public static void main(String[] args) {
// IService service = new FooService();//Main和FooService强耦合
IService service = (IService) TestServiceFactory.create("com.home.test.service.FooService");
//这时Main和TestServiceFactory之间存在强耦合,而TestServiceFactory和FooService之间是完全消除了的耦合
service.foo();
}
}
这时删除了FooService类之后,编译不会报错,可以正常运行,只是运行后会抛出找不到类的异常。
上面的TestServiceFactory就可以简单看成我们Spring。也就是说如果我们的框架用Spring搭建,那么我们自己的项目与Spring是强耦合的,但是和除Spring以外的所有东西都是弱耦合的,因为其他所有的对象都由Spring来创建了。因此可以理解Spring就是容器
上面的这个过程就叫做控制反转。
控制反转 IoC:将原来由动作发起者控制创建对象的行为改成由中间的工厂来创建的行为的过程叫做IoC。一个类与工厂之间如果IoC以后,这个时候,动作发起者(Main)已经不能明确的知道自己获得到的对象是不是自己想要的对象了,因为这个对象的创建的权利与交给我这个对象的权力全部转移到了工厂上了。(原来在Main中通过new创建对象,控制权在Main手中,现在控制器交给了工厂。比如拿上面的代码来说,在create方法中,如果传入的参数name是com.home.test.service.FooService,表明Main想要一个FooService的对象。但是我工厂偏不听,增加一个判断语句,判断如果你想要FooService,我就非给你创建一个TestService。)
当实现控制反转后,以后再拿到对象是不是你想要的、或者要不要创建对象、这个对象怎么使用、要拿哪个对象,这些所有的控制过程全部都转移到了中间的工厂中了。
TestServiceFactory就像是一个502一样把Main和IService粘合在了一起。这个粘合的过程有些麻烦。并且要创建谁的对象已经写死了(在创建的过程给create传参写死了),程序运行起来后就无法再更改了,无法达到动态效果。这时就引入了配置文件。
myfactory.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans><!--这些标签名是随便定义的-->
<bean id="testservice" class="com.home.test.service.TestService"></bean>
<bean id="fooservice" class="com.home.test.service.FooService"></bean>
</beans>
public class TestServiceFactory {
Map<String, Object> objectFactory = new HashMap<>();//该容器用于放创建的对象
Map<String, String> objectIDMap = new HashMap<>();//该容器用于放读取的配置文件的属性信息
//该方法主要用于读取配置文件,并将其信息保存在objectIDMap容器中
public TestServiceFactory(String path) {
//在创建TestServiceFactory时用dom4j解析xml文件
SAXReader reader = new SAXReader();
File file = new File(path);
try {
Document document = reader.read(file);
Element root = document.getRootElement();//root就是xml文件的根节点
List<Element> childElements = root.elements();
for (Element e : childElements) {
int count = e.attributeCount();//拿到每个标签有多少个属性
String pName = null;
String pValue = null;
for (int i = 0; i < count; i++) {
Attribute attribute = e.attribute(i);//拿到每个属性
if(attribute.getName().equals("id")) {//如果拿到的属性是id
pName = attribute.getValue();
} else if (attribute.getName().equals("class")){
pValue = attribute.getValue();
}
}
objectIDMap.put(pName,pValue);//即 testservice对映com.home.test.service.TestService
objectFactory.put(pValue,null);
}
System.out.println(objectIDMap.get("testservice"));
} catch (DocumentException e) {
System.err.println("系统没有找到对应的配置文件,请检查配置文件路径是否正确");
e.printStackTrace();
}
}
//该方法主要用于通过传来的参数创建对应的对象
public Object getObjectById(String name) {
String className = objectIDMap.get(name);
Class c = null;
Object o = null;
try {
c = Class.forName(className);
o = c.newInstance();
} catch (ClassNotFoundException e) {
System.err.println("没有找到对应的类,请检查配置文件中的映射关系,id=\"" + name + "\" class=\"" + className+"\"");
e.printStackTrace();
} catch (IllegalAccessException e) {
System.err.println("该类型没有共有的默认的构造方法");
e.printStackTrace();
} catch (InstantiationException e) {
System.err.println("该类型的参数集合不对");
e.printStackTrace();
}
return o;
}
}
测试:
public class Main {
public static void main(String[] args) {
TestServiceFactory testServiceFactory = new TestServiceFactory("./src/myfactory.xml");
IService service = (IService) testServiceFactory.getObjectById("testservice");
service.foo();
}
}
上面的过程在每次使用testServiceFactory时他都在重新创建对象。现在如果需要所有对象只被创建一次。
只需要对TestServiceFactory的getObjectById稍加修改即可:(且需要在xml文件的标签中定义属性:scope=prototype)
//还有一次修改此处省略,在TestServiceFactory中定义一个scopeMap,在构造函数中,把从xml文件的标签中读取到的scope属性保存进来
public Object getObjectById(String name) {
String className = objectIDMap.get(name);
Class c = null;
Object o = objectFactory.get(name);//如果容器中有则直接拿到,没有就为null
if((scopeMap.get(name) == null || !scopeMap.get(name).equals("prototype")) && o != null) {//scope为null,或者scope不为prototype。并且o为不为null则为单例,不需要再创建对象,返回原有的
return o;
}
try {
c = Class.forName(className);
o = c.newInstance();
objectFactory.put(name,o);//将创建的对象保存在容器中
} catch (ClassNotFoundException e) {
System.err.println("没有找到对应的类,请检查配置文件中的映射关系,id=\"" + name + "\" class=\"" + className+"\"");
e.printStackTrace();
} catch (IllegalAccessException e) {
System.err.println("该类型没有共有的默认的构造方法");
e.printStackTrace();
} catch (InstantiationException e) {
System.err.println("该类型的参数集合不对");
e.printStackTrace();
}
return o;
}
那么如果需要指定某些二对象是单例的,有些创建的是新的,这时候该怎么办呢?
在xml文件中加定义新的属性lazy-init(是否懒加载)
public TestServiceFactory(String path) {
//在创建TestServiceFactory时用dom4j解析xml文件
SAXReader reader = new SAXReader();
File file = new File(path);
try {
Document document = reader.read(file);
Element root = document.getRootElement();//root就是xml文件的根节点
List<Element> childElements = root.elements();
for (Element e : childElements) {
int count = e.attributeCount();//拿到每个标签有多少个属性
String pName = null;
String pValue = null;
String scope = null;
Boolean flag = true;
for (int i = 0; i < count; i++) {
Attribute attribute = e.attribute(i);//拿到每个属性
if(attribute.getName().equals("id")) {//如果拿到的属性是id
pName = attribute.getValue();
} else if (attribute.getName().equals("class")){
pValue = attribute.getValue();
} else if (attribute.getName().equals("scope")) {
scope = attribute.getValue();
} else if (attribute.getName().equals("lazy-init")) {
flag = Boolean.valueOf(attribute.getValue());
}
}
objectIDMap.put(pName,pValue);//即 testservice对映com.home.test.service.TestService
Object o = null;
if ((scope == null || !scope.equals("prototype")) && !flag) {//如果lazy-init设置为false,到这里取非,加载文件时创建对象
Class c = Class.forName(pValue);
o = c.newInstance();
}//否则不创建对象,o为null。直到在谁调用getObjectById指定要用它时才被创建
objectFactory.put(pName,o);
scopeMap.put(pName,scope);
}
System.out.println(objectIDMap.get("testservice"));
} catch (DocumentException e) {
System.err.println("系统没有找到对应的配置文件,请检查配置文件路径是否正确");
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
这里Spring的入门就完成了,通过上面的过程我们了解到直接在主类中创建对象的弊端(高耦合),所以通过工厂来创建,但是每次自己写一个工厂又很麻烦,这时就引入了Spring。(其实上面的过程是我们手写了一个非常简单的spring)
Spring的对象保存在beanFactory中
Spring的IoC:将创建并使用对象的过程由原来对象的执行者反向依托于Spring的过程了。Spring替我们创建对象,替我们保存对象(可以把它当成一个容器),我们再需要用对象的时候去Spring中拿就行了。