Spring 是什么
- Spring 是 Java 世界應(yīng)用的事實(shí)標(biāo)準(zhǔn)篙骡。(事實(shí)標(biāo)準(zhǔn)是指非由標(biāo)準(zhǔn)化組織制定的囚似,而是由處于技術(shù)領(lǐng)先地位的企業(yè)、企業(yè)集團(tuán)制定滨攻,由市場實(shí)際接納的技術(shù)標(biāo)準(zhǔn))
- 沒有 Spring 的年代我們怎么開發(fā) Java 應(yīng)用够话?
1.一個(gè) main 程序打天下蓝翰,這么做規(guī)模上來以后難以維護(hù),就是異常災(zāi)難女嘲。
2.根據(jù)功能拆分為多個(gè)模塊畜份,拆分并且手動管理所有的依賴,依賴關(guān)系紛繁復(fù)雜欣尼。 - Spring 容器就是一個(gè) IoC 容器爆雹。
- Spring MVC 基于 Spring 和 Servlet 的 Web 應(yīng)?框架。Servlet 就是一個(gè)小服務(wù)器愕鼓,假如我們開發(fā)一個(gè) Web 應(yīng)用钙态,一個(gè)進(jìn)程必須去監(jiān)聽某個(gè)端口,世界上所有的人都可以通過這個(gè)端口來訪問你的程序菇晃。我們將這種最底層的册倒,處理交互的東西叫做 Servlet(Java 中大名鼎鼎的 Servlet 容器有 Tomcat 和 Jetty)。Servlet 的功能就是把外界的HTTP請求封裝成一個(gè)對象交給上層的 WebApp(Java 中 WebApp 的事實(shí)標(biāo)準(zhǔn)就是 Spring)磺送,WebApp 處理完后交給 Servlet驻子,然后 Servlet 幫你發(fā)送出去。所謂的 Spring MVC 就是支持 Servlet 和網(wǎng)絡(luò)請求的這么一個(gè) Spring 程序册着,換一句話說拴孤,Spring MVC 就是在 Spring 容器之上的一個(gè)功能更強(qiáng)大的應(yīng)用
- Spring Boot 集成度和?動化程度更?。(Spring Boot 內(nèi)嵌了一個(gè) Servlet 容器甲捏,操作更加傻瓜了)Spring Boot 是近年來才火起來的,高度集成化鞭执。給我們開發(fā)人員的直觀感受就是司顿,減少了很多配置工作,但是我們喪失了對于底層的所有控制權(quán)兄纺。
- 總的來說大溜,Spring 的出現(xiàn)解放了我們的雙手。
Spring 容器核心概念
下面我們演示一個(gè)最精簡的 Spring 程序估脆,模擬一個(gè)簡單的訂單服務(wù)钦奋。
OrderDao 主要完成一些數(shù)據(jù)庫操作。
public class OrderDao {
public void select() {
System.out.println("select from database");
}
}
OrderSerive 提供對外的服務(wù)疙赠。
public class OrderService {
private OrderDao orderDao;
public void doSomething() {
System.out.println("do some thing");
}
}
首先需要引入一個(gè)第三方類庫 Spring Context(這里對于 context 比較好的解釋應(yīng)該是“環(huán)境”付材,“上下文”總覺得怪怪的)。現(xiàn)在圃阳,用的比較多的是 Spring Boot厌衔,在上古年代,人們使用 spring xml 來配置 Spring 容器捍岳。在 src/main/resource 下新建 config.xml 文件富寿。我們從官網(wǎng)抄來了配置文件睬隶,根據(jù)自己的需求,添加了兩個(gè) bean∫承欤現(xiàn)在解釋一下 bean 是什么苏潜。程序員都是非常浪漫的,Java 是咖啡变勇,而 Bean 是咖啡豆恤左。一個(gè) Bean 可以去依賴其他 Bean,本質(zhì)上贰锁,Bean 就是一個(gè) Java 對象(其實(shí) Spring 整個(gè)程序赃梧,也是一個(gè) Java 對象,只不過容器給我們的概念是一個(gè)很大的盒子)豌熄。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- bean definitions here -->
<bean id="orderDao" class="com.github.lazyben.OrderDao"/>
<bean id="orderService" class="com.github.lazyben.OrderService"/>
</beans>
容器是什么呢授嘀?其實(shí)就是 BeanFactory。現(xiàn)在我們可以去完成一些操作了锣险。發(fā)現(xiàn)了沒有整個(gè)程序我們沒有手工去處理依賴關(guān)系蹄皱。
public class SpringMain {
public static void main(String[] args) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:config.xml");
final OrderService orderService = (OrderService) beanFactory.getBean("orderService");
final OrderDao orderDao = (OrderDao) beanFactory.getBean("orderDao");
System.out.println(orderService);
orderService.doSomething();
}
}
此時(shí)報(bào)了一個(gè)空指針異常。因?yàn)榇藭r(shí) orderService 中的 orderDao 是 null芯肤。怎么辦呢巷折?
com.github.lazyben.OrderService@57f23557
Exception in thread "main" java.lang.NullPointerException
at com.github.lazyben.OrderService.doSomething(OrderService.java:7)
at com.github.lazyben.SpringMain.main(SpringMain.java:11)
最簡單的就是基于注解的,我們可以添加注解 @Inject
(注入)或 @Resource
(資源)或 @Autowired
(自動裝配)崖咨。為什么我們添加一個(gè)注解就能完成注入依賴的工作呢锻拘?這個(gè)注解其實(shí)本身沒什么卵用,只是在運(yùn)行時(shí)被掃描到有這個(gè)注解击蹲,并完成了工作署拟。這里就不多贅述了,這不是本篇所要討論的內(nèi)容歌豺。另外推穷,我們還需要在 config.xml 中加入一行配置 <context:annotation-config/>
。多說一句类咧,這里比較推薦使用 @Inject
注解馒铃,不僅Spring能識別它,Google 的 Guice 也能識別它痕惋。
Spring 也支持構(gòu)造器注入区宇。我們可以把這些注解寫在構(gòu)造器上(稱為構(gòu)造器注入),在屬性上寫注解有一個(gè)很要命的問題血巍,一旦脫離 Spring 就無法工作了萧锉,而寫在構(gòu)造器上我們可以在沒有 Spring 的情況下通過構(gòu)造器的方式傳入屬性(就和寫原生 Java 代碼一樣),寫單元測試也更加的方便述寡。構(gòu)造器注入是一種更好的實(shí)踐柿隙。
public class OrderService {
// @Inject
// @Resource
@Autowired
private OrderDao orderDao;
public void doSomething() {
orderDao.select();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<context:annotation-config/>
<!-- bean definitions here -->
<bean id="orderDao" class="com.github.lazyben.OrderDao"/>
<bean id="orderService" class="com.github.lazyben.OrderService"/>
</beans>
打上斷點(diǎn)叶洞,神奇的事情發(fā)生了,orderService 中的 orderDao禀崖,和 orderDao 居然還是同一個(gè)對象衩辟。為什么上文說 Spring 解放了我們的雙手?道理就在這里波附。
這里我們回顧一下上文中提到的一些 Spring 的核心概念:
- Bean
容器中的最??作單元艺晴,通常為?個(gè) Java 對象霞掺。 - BeanFactory/ApplicationContext
它們都是容器本身對應(yīng)的Java對象滔金。
容器本身對應(yīng)的 Java 對象。 - 依賴注? Dependency Injection(DI)
容器負(fù)責(zé)注?所有的依賴节预。 - 控制反轉(zhuǎn) Inverse of Control(IoC)
?戶將控制權(quán)交給了容器仅财。
手寫一個(gè)最精簡的 IoC 容器
無非是以下幾個(gè)步驟狈究。定義Bean、加載 Bean 的定義盏求、實(shí)例化 Bean抖锥、查找依賴實(shí)現(xiàn)自動注入。
- 為了簡單碎罚,我們使用 .properties 格式文件作為配置文件磅废,在 src/main/resources 下創(chuàng)建配置文件 ioc.properties。propweties 使用等號劃分一個(gè)鍵值對荆烈。
orderDao=com.github.lazyben.OrderDao
orderService=com.github.lazyben.OrderServicew
- 使用一個(gè)古老的 Java 類 Properties 可以解析 properties 文件拯勉,于是我們拿到了配置文件。
public class MyIoCContainer {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(MyIoCContainer.class.getResourceAsStream("/ioc.properties"));
System.out.println("properties = " + properties);
}
}
- 根據(jù)拿到的配置憔购,使用反射生成實(shí)例谜喊。
Map<String, Object> beans = new HashMap<>();
properties.forEach((beanName, beanFullyQualifiedClassName) -> {
try {
Class<?> klass = Class.forName((String) beanFullyQualifiedClassName);
Object newInstance = klass.getConstructor().newInstance();
beans.put((String) beanName, newInstance);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
- 依賴注入。對于每一個(gè)實(shí)例倦始,獲取它的所有帶
@Autowired
注解的屬性,在 beans 中找到相應(yīng)的實(shí)例山卦,并注入依賴鞋邑。值得注意的是 private 屬性,我們不能直接用反射直接設(shè)置账蓉,需要設(shè)置權(quán)限field.setAccessible(true);
枚碗。
beans.forEach((beanName, beanInstance) -> dependencyInject(beanInstance, beans));
private static void dependencyInject(Object beanInstance, Map<String, Object> beans) {
final List<Field> fieldsToBeAutoWired = Stream.of(beanInstance.getClass().getDeclaredFields())
.filter(bean -> bean.getAnnotation(Autowired.class) != null)
.collect(Collectors.toList());
fieldsToBeAutoWired.forEach(field -> {
try {
final Object dependencyBeanInstance = beans.get(field.getName());
field.setAccessible(true);
field.set(beanInstance, dependencyBeanInstance);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
}
- 源碼
public class MyIoCContainer {
public static void main(String[] args) throws IOException {
Properties properties = new Properties();
properties.load(MyIoCContainer.class.getResourceAsStream("/ioc.properties"));
Map<String, Object> beans = new HashMap<>();
properties.forEach((beanName, beanFullyQualifiedClassName) -> {
try {
Class<?> klass = Class.forName((String) beanFullyQualifiedClassName);
Object newInstance = klass.getConstructor().newInstance();
beans.put((String) beanName, newInstance);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
beans.forEach((beanName, beanInstance) -> dependencyInject(beanInstance, beans));
final OrderService orderService = (OrderService) beans.get("orderService");
orderService.doSomething();
}
private static void dependencyInject(Object beanInstance, Map<String, Object> beans) {
final List<Field> fieldsToBeAutoWired = Stream.of(beanInstance.getClass().getDeclaredFields())
.filter(bean -> bean.getAnnotation(Autowired.class) != null)
.collect(Collectors.toList());
fieldsToBeAutoWired.forEach(field -> {
try {
final Object dependencyBeanInstance = beans.get(field.getName());
field.setAccessible(true);
field.set(beanInstance, dependencyBeanInstance);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
}
}