==============================
Spring 的依賴注入
==============================
對於 Spring 程序, Spring 框架為我們提供一個 IoC 容器, 該容器負責創建對象和維護對象之間的依賴關系.
對於普通程序, 我們是通過對象本身來創建和解決自己的依賴問題.
ApplicationContext 即是 Spring 程序的 IoC 容器, 該容器負責創建 Bean, 並將功能類 Bean 注入到你需要的 Bean 中. 那么, Spring 是如何知道我們有哪些 Bean 類, 以及這些類的依賴關系是什么? 有三種配置方式告知 Spring 程序, 分別是 Xml 配置方式/注解配置方式/Java 配置方式.
ApplicationContext 是 Spring 程序的核心, 不管是 Spring 程序, 還是 Spring MVC 程序, 還是SpringBoot 程序, 其 main() 函數最主要的代碼就是初始化了 ApplicationContext 容器, Spring 框架為我們提供了多種容器實現, 可以針對不同的應用場景選擇.
1. AnnotationConfigApplicationContext: 該容器讀取從一個或多個基於 java 的配置類, 適用於 Java 配置方式;
2. AnnotationConfigWebApplicationContext: 專門為 web 應用准備的, 適用於注解方式;
3. XmlWebApplicationContext: 該容器讀取一個或多個 Xml 配置文件,使用於 Xml 配置方式;
4. ClassPathXmlApplicationContext, 該容器從 classpath 路徑下讀取 Xml 配置文件,使用於 Xml 配置方式.
//ApplicationContext context = new ClassPathXmlApplicationContext("resouces/applicationContext.xml");
//ApplicationContext context = new AnnotationConfigApplicationContext(ManConfig.class);
==============================
三種 Bean 配置方式
==============================
1. Xml 配置方式: 老的程序中經常見到, 比如將 Spring bean 聲明放到 applicationContext.xml 中.
2. 注解配置方式: 在類定義時通過@Service, @Controller, @Repository, @Component 聲明為 Spring Bean.
@Service, 用於業務服務層
@Controller, 用於展現層
@Repository, 用於 DAO 層
@Component, 通用組件, 它是上面 3 個注解的父注解, 沒有明確的角色, 對於普通的組件最好使用@Component 注解
3. Java 配置方式: 該方式是通過@Configuration+@Bean 實現的
具體為, 該方式引入了一個 Config 類, 在類中通過方法函數聲明 bean 對象, 而 Pojo 類定義不加@Component 之類的注解, Config 類需要加上@Configuration 注解, Config 類中的 bean 方法需要加上@Bean 注解.
@Configuration 等同於 xml 配置中的 <beans> </beans> 標簽, 需說明的是@Component 其實也是@Component 的一個子注解,
@Bean 等同於 xml 配置中的 <bean> </bean>標簽. @Bean 用來注解一個函數, 該函數應該一個 Bean 對象, Bean 對象的名稱是方法名.
最佳實踐: 注解配置方式和 Java 配置方式沒有孰優孰劣, 理論上是可以相互替換的, 但可以總結一個最佳實踐, 全局性的配置使用 Java 配置 (比如數據庫相關配置, MVC 相關配置), 業務 Bean 配置使用注解配置, 盡量少用 Xml 配置方式.
==============================
示例程序的 pom.xml
==============================
開發基於 Spring 的命令行程序, 只需要引入 spring-context 即可.
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> </dependencies>
===============================
基於注解配置示例
===============================
基於注解的配置, 主要在各個 Pojo Class 定義時使用@Component 來聲明該類是一個 Spring 的 Bean, 比如 Car/Boss 類都使用的@Component 注解, 依賴注入可以使用@Autowired 或@Resource 注解, 比如在 Boss 類中, 使用了@Autowired 自動注入了一個 Car 對象.
//Car.java //將 Car 加上注解 @Component, 表明它是一個 Spring bean @Component public class Car { @Override public String toString() { return "Brand: Benz, price:1000"; } }
//Boss.java //將 Boss 加上注解 @Component, 表明它是一個 Spring bean //Boss 類的 field Car 是通過 @Autowired 注入的. @Component public class Boss { @Autowired private Car car; @Override public String toString() { return "Boss has one car:("+car+")"; } }
//SomeConfig.java //這里創建 SomeConfig 類目的只有一個, 不用准備 Xml 配置文件. //在 main() 函數中將使用這個 config 類初始化容器, 並加上@ComponentScan, 告知 IoC 容器需要掃描指定 package 來獲取 bean 的定義 (包括各種注解了@Component/@Service/@Controller/@Repository 的類). @Configuration @ComponentScan("javaTestMaven.demo2") public class SomeConfig { }
//App.java //入口函數,先初始化容器,然后通過容器獲得 boss 對象 public class App { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SomeConfig.class); context.refresh(); Boss boss = context.getBean(Boss.class); System.out.println(boss); context.close(); } }
===============================
基於 Java 配置示例
===============================
基於注解的配置, 主要在各個 Pojo Class 定義時使用@Component 來聲明該類是一個 Spring 的 Bean; 但如果使用基於 Java 的配置, Pojo Class 上不需要加上任何 IoC 相關的注解, 而是需要在 Config 類中使用 @Bean 來注解那些需要 Spring 容器管理的對象, @Bean 注解往往加在一個函數體上, 該函數需要返回一個對象.
//Car.java //Car 類就是一個普通的 Class, 沒有加@Component 注解 public class Car { @Override public String toString() { return "Brand: Benz, price:1000"; } }
//Boss.java //Boss 類就是一個普通的 Class, 沒有加@Component 注解 //Boss 類的 field Car 是通過 @Autowired 注入的. @Component public class Boss { @Autowired private Car car; @Override public String toString() { return "Boss has one car:("+car+")"; } }
//SomeConfig.java //在上個示例中 SomeConfig 類目的只有一個, 不用准備 Xml 配置文件. //這個示例中 SomeConfig 類是一個重點, 不僅加了@Configuration, 而且使用了@Bean 注解定義了多個 Bean 對象 //因為所有的 Bean 都在 SomeConfig 類定義了, 所以 ComponentScan 注解可有可無. @Configuration @ComponentScan("javaTestMaven.demo3") //可有可無 public class SomeConfig { @Bean public Car car() { return new Car(); } @Bean public Boss boss() { return new Boss(); } }
//App.java //入口函數,先初始化容器,然后通過容器獲得 boss 對象 public class App { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SomeConfig.class); context.refresh(); Boss boss = context.getBean(Boss.class); System.out.println(boss); context.close(); } }
===============================
多個 Java 配置類的處理方式
===============================
有時候我們會在多個 Config 類中定義不同的 Bean, 如何告訴 Spring IoC 加在這些 Config 類呢? 有兩個方法分別是: 1. 將 Config 類分為主從 Config 兩個類別, 在主要 Config 導入從屬 Config, 在初始化 Spring 容器的時候只要注冊主要 Config 類即可. 2. 不區分中從 Config 類, 在初始化 Spring 容器的時候將這些 Config 類都注冊進去.
推薦使用主從 Config 處理方式, 下面是兩種處理方式的示例代碼, 這個代碼是修改自"基於 Java 配置示例", 主要改動是將原來 SomeConfig 分為兩個類.
-------------------------------
區分主從 Config 的處理方式
-------------------------------
//SomeConfig1.java //SomeConfig1 是主 Config, 使用@Import 導入從屬的 Config @Configuration @Import(SomeConfig2.class) public class SomeConfig1 { @Bean Boss boss() { return new Boss(); } }
//SomeConfig2.java @Configuration public class SomeConfig2 { @Bean public Car car() { return new Car(); } }
//App.java //僅僅導入了主 Config 類 public class App { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SomeConfig1.class); context.refresh(); Boss boss = context.getBean(Boss.class); System.out.println(boss); context.close(); } }
-------------------------------
不區分主從 Config 的處理方式
-------------------------------
@Configuration public class SomeConfig1 { @Bean Boss boss() { return new Boss(); } }
@Configuration public class SomeConfig2 { @Bean public Car car() { return new Car(); } }
//App.java //導入了兩個 Config 類 public class App { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SomeConfig1.class); context.register(SomeConfig2.class); context.refresh(); Boss boss = context.getBean(Boss.class); System.out.println(boss); context.close(); } }
===============================
Java 配置和注解配置類組合使用
===============================
最佳實踐: 注解配置方式和 Java 配置方式沒有孰優孰劣, 理論上是可以相互替換的, 但可以總結一個最佳實踐, 全局性的配置使用 Java 配置 (比如數據庫相關配置, MVC 相關配置), 業務 Bean 配置使用注解配置, 盡量少用 Xml 配置方式.
這本示例中, Boss bean 采用了 Java 配置方式, 而 Car bean 采用注解配置方式.
//Boss.java //該 Java 沒有加任何 IoC 注解 public class Boss { @Autowired private Car car; @Override public String toString() { return "Boss has one car:("+car+")"; } }
//Car.java //該 Java 加上了基於注解的配置說明 @Component public class Car { @Override public String toString() { return "Brand: Benz, price:1000"; } }
//SomeConfig.java //該配置類中僅定義了 boss Bean, 沒有直接定義 car Bean, 但實際上 Boss 對象中仍能被成功地注入 Car. //原因是我們組合使用了基於注解的配置方法, Config 類加上了@ComponentScan 后, IoC 容器能掃描到 Car 類 (因為其被注解為 Component). @Configuration @ComponentScan("javaTestMaven.demo3") public class SomeConfig { @Bean Boss boss() { return new Boss(); } }
//App.java //將 Config 類注冊一下 public class App { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(SomeConfig.class); context.refresh(); Boss boss = context.getBean(Boss.class); System.out.println(boss); context.close(); } }
===============================
SpringBoot 程序的 @EnableAutoConfiguration
===============================
前面的示例都是基於經典的 Spring 框架, 在程序啟動時需要我們將 Config 類主動注冊容器上. 而對於 SpringBoot 程序, 因為已經加了 @EnableAutoConfiguration, 所以可以省去注冊 Config 類的過程.
下面示例和上一個示例代碼只有兩處不同, 即 pom.xml 和 App.java 程序.
pom.xml, 引入 spring-boot-starter-parent
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> </dependencies>
//App.java //基於 SpringBoot 的命令行程序 //無需注冊 Config 類 @SpringBootApplication //same as @Configuration @EnableAutoConfiguration @ComponentScan public class App implements CommandLineRunner { @Autowired private ApplicationContext context; public static void main(String[] args) throws Exception { SpringApplication app = new SpringApplication(App.class); app.setBannerMode(Banner.Mode.OFF); app.run(args); } // Put your logic here. @Override public void run(String... args) throws Exception { Boss boss = context.getBean(Boss.class); System.out.println(boss); } }
//Boss.java //該 Java 沒有加任何 IoC 注解 public class Boss { @Autowired private Car car; @Override public String toString() { return "Boss has one car:("+car+")"; } }
//Car.java //該 Java 加上了基於注解的配置說明 @Component public class Car { @Override public String toString() { return "Brand: Benz, price:1000"; } }
//SomeConfig.java //該配置類中僅定義了 boss Bean, 並加上了@ComponentScan 后, IoC 容器能掃描到 Car 類 (因為其被注解為 @Component) @Configuration @ComponentScan("javaTestMaven.demo3") public class SomeConfig { @Bean Boss boss() { return new Boss(); } }
===============================
挖掘機技術到底哪家強?
===============================
摘自<<用小說的形式講解Spring(3) —— xml、注解和Java Config到底選哪個>>
http://bridgeforyou.cn/2017/10/03/Spring-Novel-3-Annotaion-Based-Configuration-and-Java-Based-Configuration/
總結的非常好.
===============================
參考
===============================
http://bridgeforyou.cn/2017/10/03/Spring-Novel-3-Annotaion-Based-Configuration-and-Java-Based-Configuration/
https://www.ibm.com/developerworks/cn/java/j-lo-spring25-ioc/
https://www.ibm.com/developerworks/cn/java/j-lo-spring25-mvc/
https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-iocannt/index.html