介绍
Spring的核心在于控制反转,组件之间不再通过new
来进行关联。而是以面向接口的方式编程,利用容器或者框架来建立组件(Class
)之间的关系,最终实现松耦合。
本文从一个简单的main
方法程序,逐渐过渡到工厂
的思想,通过该思想模拟出Spring的核心:“控制反转”。
一个简单程序
一个名为HelloWorld
的Class,里面有一个主函数,使用PrintStream
打印一句Hello World
。
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
该程序写完后,执行的功能是单一的,它只能调用System.out
去打印Hello World
。
缺点:
System.out
是固定的,如果我们想改写成System.err
,需要重新编码。Hello World
是固定的,它至少应该是一个消息类,以后可以通过某种消息中间件去接收消息(超纲)。
我们改写一下
接口编程
接口的思想就是,我先设计一个模板,先不做具体实现。接着上面的内容,我现在需要:
- 一个专门产生消息的类,调用方法获得消息。
public interface MessageSource {
public String getMessage();
}
- 一个打印消息的类,做一个打印打印的动作。
public interface MessageDestination {
public void write(String msg);
}
程序现在的UML结构应该是这样的,它解决了固定的问题,因为消息和打印体我可以变动,但实际还是硬编码,程序依然耦合。
public class HelloWorld {
public static void main(String[] args) {
PrintMessage printMessage = new PrintMessageImpl();
MessageSource messageSource = new MessageSourceImpl("Hello World");
printMessage.print(System.out, messageSource.getMessage());
}
}
好像程序不但耦合了,而且变得复杂了,但是至少它的可维护性在增加(从工程的角度上,我把一个整块的程序在做拆分,变成一个个模块)。这里面就是我们开头提到的一个问题:组件之间是通过new
来关联的,是硬编码,紧耦合的关系。接下去我们准备消除这个new
,但是为了引出工厂的优点,我们再次完善一下UML的结构。
这次改写接口PrintMessage
,不再传入打印流了,而是由两个实现类分别去做不同的事。
工厂 + 反射
消除这个new
,也就是需要我们在运行的过程中才去创建实例对象。
- 使用反射这个技术,可以实现在运行过程中去创建实例对象。
- 结合工厂模式可以解决,根据某些规则去选择实例化对象的方法。
所以将这两种技术结合可以将代码进行解耦。
我先来画个图,解释一下:工厂+反射。
大致就是说,BeanFactory
是用来专门创建类对象的,根据一定的规则(该规则我们可以通过读取配置文件的方式,将传入的key
找到类的全类路径)去创建对象(创建的方式可以使用反射的方式创建)。
完整的UML图如下:
具体代码如下:
工厂接口
public interface BeanFactory {
// 通过某种名称就拿到一个Bean
public Object getBean(String name);
}
工厂实现类
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* @ClassName
* @Description
* @Author:chengyunlai
* @Date
* @Version 1.0
**/
public class BeanFactoryImpl implements BeanFactory {
private static Map<String,String> beanDefinitions;
// 使用Properties作为配置文件,希望再构造该工厂的时候传入配置,加载配置文件
public BeanFactoryImpl(String config) {
readBeanDefinitions(config);
}
private static void readBeanDefinitions(String config) {
Properties properties = new Properties();
System.out.println(BeanFactoryImpl.class.getClassLoader());
InputStream resourceAsStream = BeanFactoryImpl.class.getClassLoader().getResourceAsStream(config);
if (resourceAsStream == null) {
throw new RuntimeException("文件不存在");
}
try {
properties.load(resourceAsStream);
resourceAsStream.close();
beanDefinitions = new HashMap<>();
for (Map.Entry<Object, Object> objectObjectEntry : properties.entrySet()) {
beanDefinitions.put(objectObjectEntry.getKey().toString(),objectObjectEntry.getValue().toString());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public Object getBean(String name){
System.out.println(name);
String beanClassPath = this.beanDefinitions.get(name);
System.out.println(beanClassPath);
if (beanClassPath == null){
return null;
}
try {
// 反射
return Class.forName(beanClassPath).newInstance();
} catch (Exception e) {
throw new RuntimeException("无法创建该bean:" + beanClassPath);
}
}
}
主函数测试
public class FactoryTest {
public static void main(String[] args) {
BeanFactory beanFactory = new BeanFactoryImpl("factory.properties");
MessageSource messageSource = (MessageSource) beanFactory.getBean("messageSource");
ErrPrintMessage errMessageImpl = (ErrPrintMessage) beanFactory.getBean("errMessageImpl");
errMessageImpl.print(messageSource.getMessage());
}
}
配置文件
#为了方便回头取这些类的全限定名,我给每一个类名都起一个“小名”(别名),这样我就可以根据小名来找到对应的全限定类名了。
messageSource=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.MessageSourceImpl
errMessageImpl=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.ErrPrintMessage
outMessageImpl=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.OutPrintMessage
看看Spring是怎么做的
导入依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.24</version>
</dependency>
</dependencies>
Properties方式
Spring也提供了工厂和读取Properties
文件的类,看看它是怎么做的:
DefaultListableBeanFactory
:即工厂。BeanDefinitionReader
:即读取配置文件。
public class DISpringHelloWorld {
public static void main(String[] args) {
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
BeanDefinitionReader reader = new PropertiesBeanDefinitionReader(bf);
reader.loadBeanDefinitions(new ClassPathResource("factory.properties"));
ErrPrintMessage errMessageImpl = (ErrPrintMessage) bf.getBean("errMessageImpl");
errMessageImpl.print("你好");
}
}
配置文件内容
其实就是有点小特殊,在key
那额外加了.(class)
messageSource.(class)=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.MessageSourceImpl
errMessageImpl.(class)=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.ErrPrintMessage
outMessageImpl.(class)=top.chengyunlai.spingPro.sping_02_Introduction.code2_4.impl.OutPrintMessage
额外的不展开了,平时用的不多。
XML方式
这个方式,我们接触的最多,即使用ClassPathXmlApplicationContext("xml路径")
,直接拿到容器上下文。
配置类方式
这个方式也接触的很多,即使用AnnotationConfigApplicationContext(XXX.class)
,拿到容器的上下文。