目录
一 前言
接上篇小白新手web开发简单总结(四)-web应用开发中的MVC总结下IoC容器的相关内容。
IoC容器是Spring框架的一个核心功能。通过该容器来管理所有的JavaBean组件,提供组件的生命周期管理、配置和组装、AOP支持、以及建立在AOP基础上的声明事务服务等。
二 什么是IoC容器
IoC(Inversion of Control 控制反转)容器
通常Java组件通过new一个实例的方式实现组件之间的引用,并且组件中的两个实例都含有共享组件的时候,也不能共享该组件。如果系统中有大量的组件的时候,不仅要维护组件的生命周期,还要维护组件之间的依赖关系,大大增加了系统的复杂度,而且组件之间会相互耦合,这些控制流程完全有开发者自行控制。
而IoC容器就是将控制权进行反转,将控制权交给了IoC容器,由IoC容器负责组件的创建和配置,负责组件的生命周期管理,而开发者只需要定义装配规则,这样就将组件的创建配置和组件的使用进行分离。并且如果在一个组件的多个实例中含有共享组件的时候,是可以相互共享的,不需要多次注入。
在Spring的IoC容器中,所有的组件都被成为JavaBean组件,每个组件即一个Bean。
通常有两种方式来定义组件的配置规则:
- (1)XML
- (2)注解
三 实例
通过一个简单的实例来看下这个JavaBean组件是怎么定义配置规则。
1.通过XML配置规则
- 1.在项目pom.xml中引入spring-context依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
- 2.定义项目中需要使用的JavaBean
(1)定义一个RegisterService:通过输入的手机号码和密码,来注册该用户,并将注册成功之后给用户发送注册成功的短信。
public class RegisterService {
private MessageService messageService;
public void setMessageService(MessageService service) {
this.messageService = service;
}
public User register(String phone, String password) {
User user = new User();
user.phone = phone;
user.password = password;
//随机生成一个用户名
user.name = "小刘";
//需要将该信息写入数据库
SqlDataBase sql = new SqlDataBase();
sql.insert(user);
//注册成功之后,给用户发送短息
if (messageService == null) {
System.err.println("还没有初始化MessageService");
return user;
}
messageService.sendRegisterSuccessMessage(phone);
return user;
}
}
(2)定义MessageService: 向用户的手机发送短信(逻辑省略)
public class MessageService {
/**
* 发送短信
*
* @param phone
*/
public void sendRegisterSuccessMessage(String phone) {
String message = "您已经成功注册该网站账户";
System.out.println(String.format("已经成功的向%s发送注册短信:%s", phone, message));
}
}
- 3.定义配置JavaBean的规则
(1)在resouce目录下添加application-context.xml的文件
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="registerService" class="com.wj.spring.RegisterService">
<property name="messageService" ref="messageService"/>
</bean>
<bean id="messageService" class="com.wj.spring.MessageService">
</bean>
</beans>
其中id为该bean的唯一识别 ,通过 <property name="messageService" ref="messageService"/>的方式在RegisterService中增加了MessageService的引用,其实上面的XML配置的内容就等价于下面的java代码:
MessageService messageService = new MessageService();
RegisterService registerService = new RegisterService();
registerService.setMessageService(messageService);
- 4.通过读取XML文件,获取到ApplicationContext,也就是IoC容器
public class RegisterApplication {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("config/application-context.xml");
RegisterService service = context.getBean(RegisterService.class);
//RegisterService service = (RegisterService)context.getBean("registerService");
User user = service.register("12345678901", "123456");
System.out.println("注册之后的用户名为:" + user.name);
}
}
当 ClassPathXmlApplicationContext要传入的是这个application-context.xml的相对路径。这样就可以context.getBean()的方式获取到定义的JavaBean。运行项目如下:
相关的代码已经上传到github:https://github.com/wenjing-bonnie/build-spring.git:对应的com/wj/spring下的相关代码。
2.通过注解
使用XML文件的方式,可以把所有的JavaBean的都一一列举出来,并且通过配置注入能直观的了解到每个JavaBean的依赖关系,但是缺点也就显现出来,这种写法非常繁琐,每增加一个组件,就必须在XML将该组件添加进去。所以就有了注解这种方式,可以让IoC容器自动扫描所有的Bean并组装他们。
- 1.同上面的1
- 2.同样定义同上面的JavaBean,只不过在JavaBean增加@Component注解,来标示是一个JavaBean组件
(1)定义一个RegisterService
@Component
public class RegisterAnnotationService {
// @Autowired将指定类型的Bean直接注入到指定的字段
@Autowired
private MessageAnnotationService messageAnnotationService;
}
通过 @Autowired将指定类型的Bean直接注入到指定的字段。当然@Autowired也可以注解List<JavaBean>集合中,这样每增加一个同类型的JavaBean,都会被IoC容器自动装配到该集合中。如果要设置JavaBean被装配的顺序,则可通过在组件上增加@Order(1)、@Order(2)……
默认情况下,当给定字段标记了 @Autowired,如果IoC容器没有找到对应的JavaBean,则会抛出NoSuchBeanDefinitionException异常,所以可以增加@Autowired(required=false)来避免该异常。
(2)定义MessageService
@Component
public class MessageAnnotationService {
}
通过增加上面的两个注解的方式,就完成了JavaBean之间的配置规则。
默认的将JavaBean标记为@Component,那么IoC容器会自动创建一个单例,即容器初始化的时候创建JavaBean,容器销毁的时候销毁。在整个容器运行期间,getBean()获取的是一个实例。
其实可以通过@Scope来标记该实例的作用域:即@Scope(value = "singleton")或@Scope( "singleton"),该value对应的有四种作用域
singleton :唯一的bean实例,默认的为单例。即getBean()获取的是一个实例。
prototype:每次容器返回的都是一个新的实例。即getBean()获取的都是一个新的实例。
request:每次Http请求的时候都会产生一个新的实例,仅在当前request请求中有效。
session:每次Http请求的时候都会产生一个新的实例,仅在当前session内有效。
- 3.通过读取注解来获得到IoC容器
@Configuration //用来标明是一个配置类,在创建AnnotationConfigApplicationContext,需要传入该类
@ComponentScan //用来告诉容器,自动扫描当前类所在的包以及子包,把所有标注为@Component的Bean都自动创建出来,并根据@Autowired进行装配
public class RegisterAnnotationApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(RegisterAnnotationApplication.class);
RegisterAnnotationService service = context.getBean(RegisterAnnotationService.class);
//RegisterService service = (RegisterService)context.getBean("registerService");
User user = service.register("12345678901", "123456");
System.out.println("注册之后的用户名为:" + user.name);
}
}
不同于通过XML文件的配置方式,在读取ApplicationContext的时候,需要将该类增加一个 @Configuration 用来标明是一个配置类(其实@Configuration可以用@Component来代替,也就是如果分不清这个组件具体什么作用,那就直接用@Component),在创建AnnotationConfigApplicationContext的时候,需要传入该类;增加@ComponentScan 用来告诉容器,自动扫描当前类所在的包以及子包,把所有标注为@Component的Bean都自动创建出来,并根据@Autowired进行装配。
运行项目结果和上面的一致。相关代码已经上传github:https://github.com/wenjing-bonnie/build-spring.git:对应的com/wj/springannotation下的相关代码。
3.Spring中的其他几种注解
1.JavaBean组件相关的注解
像之前大体有印象的一些@Controller/@RestController、@Service、@Respository等这些都是用来注解为JavaBean,只不过这是在后面的Spring MVC具有一些特殊功能的JavaBean,如果分不清该组件属于哪一种,就直接使用@Component来注解。
2.读取配置文件、资源文件等相关注解
- (1)读取Resources下面的一些普通的文件
使用IoC容器,可以很方便的把这些文件引入进来,方便程序读取,省掉了很多繁琐的代码来获取InputStream。在Maven项目中,经常会将这些文件放置到Resources目录下,所以Spring框架就提供了org.springframework.core.io.Resource来可以直接读取到文件的InputStream
@Value("readme.txt")
private Resource readmeTxt;
那么在项目中就可以通过readmeTxt.getInputStream()获取到该txt的inputstream。
- (2)读取Resouces下的一些key/value结构的文件
经常有些配置文件是key/value形式的存在文件中,在使用上面的方式,显得不太方便,那么IoC容器还提供了更简便的方式来读取。通过添加@PropertySource注解的方式,就可以直接根据key来获取到对应的value。例如有一个配置文件config/application.properties的内容如下:
application.ipaddress = 192.168.110.118
我们只需要在需要读取该配置文件的组件上添加 @PropertySource("config/application.properties"),那么就可以通过key来得到对应的value值。
@Configuration
@PropertySource("config/application.properties")
public class ApplicationConfig {
@Value("${application.ipaddress}")
public String ipAddress;
}
@Value("${application.ipaddress}"):为没有默认值的value,如果想给定默认值的话,则 @Value("${application.ipaddress:192.168.0.0}")。
- (3)在其他组件中引用配置文件的相关内容
还是上面的例子,如果在其他地方也要引用config/application.properties里面的配置项,其实可以直接通过@Value("#{bean.property}")的方式来得到对应的配置项,bean就是ApplicationConfig的实例applicationConfig(该实例不需要引入,只是组件名的首字母小写)的ipAddress属性值引入到OtherAnnotation。
@Component
public class OtherAnnotation {
@Value("#{applicationConfig.ipAddress}")
public String ipAddress;
}
通过该注解方式,在OtherAnnotation中可以不用引入config/application.properties就可以读取到定义在ApplicationConfig里面的配置项。与(2)不同的是,在(2)中使用的是@Value("${key:defaultValue}")的形式读取配置文件中的值,而在OtherAnnotation中通过@Value("#{bean.property}")读取在ApplicationConfig中读到的配置项。
四 JavaBean生命周期的监听
当我们将一个JavaBean添加到IoC容器时,有的时候可能需要在容器初始化该JavaBean的时候监听消息等,在容器关闭的时候清理资源。那么就可以通过在该JavaBean只能够定义两个方法,在项目中引入javax.annotation依赖,通过javax.annotation的@PostContruct和@PreDestroy添加到上述的两个方法中。其过程如下:
(1)在pom.xml添加javax.annotation依赖
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
(2)在MessageAnnotationService中需要承载初始化和销毁的方法上添加@PostContruct和@PreDestroy
public class MessageAnnotationService {
@PostConstruct
public void init() {
System.out.println("MessageAnnotationService init");
}
@PreDestroy
public void destroy() {
System.out.println("MessageAnnotationService destroy");
}
}
这样在IoC容器初始化和销毁的时候,就会看到对应的方法的调用 。
五 总结
1.Spring的核心功能就是IoC容器,提供组件的创建和装配,只需要定义配置规则,IoC容器会自动完成所有的过程;
2.可以通过XML文件和注解两种方式来定义组件之间的配置规则;
3.终于搞明白了项目中为什么有那么多bean的配置文件;
4.使用@Component来注解一个类为组件,那么IoC容器在启动的时候,就会自动初始化该组件;
5.可以用@Scope来指定组件实例的作用域,是单例还是每次都是一个新的实例;
6.一个ApplicationContext就是一个IoC容器,可以从ApplicationContext中通过getBean()来获取到对应的组件实例;
7.使用javax.annotation可以在组件初始化或者销毁的时候做一些事情;
8.可以使用Resource轻松得到resouces下的文件的inputStream;
9.可以通过 @PropertySource的方式轻松得到配置文件的key/value;
10.之前有点印象的@Controller/@RestController、@Service、@Respository等其实都是一些组件,只不过是一些拥有特殊功能的组件。
每天都在进步一点,加油!!!