SpringBoot系列: 理解 Spring 的依赖注入(一)
==============================
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