Spring Framework主要包括几个模块:
支持IoC和AOP的容器;
支持JDBC和ORM的数据访问模块;
支持声明式事务的模块;
支持基于Servlet的MVC开发;
支持基于Reactive的Web开发;
以及集成JMS、JavaMail、JMX、缓存等其他模块。
IoC容器
容器是一种为某种特定组件的运行提供必要支持的软件环境。
使用容器运行组建,除了提供一个组件运行环境外,容器还提供了许多底层服务。
Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的Bean组件,提供的底层服务包括组件的生命周期的管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。
IoC原理
控制反转,即由IoC容器控制组件的创建和配置。
public class BookService{
private DataSource dataSource;
public void setDataSource(DataSource dataSource){ //通过setDataSource()方法来注入DataSource
this.dataSource = dataSource;
}
//public BookService(DataSource dataSource){ //也可通过构造方法注入
this.dataSource = dataSource;
}
IoC又称为依赖注入,将组件的创建和配置与组件的使用相分离。
装配Bean
public class UserService {
private MailService mailService;
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
application.xml配置文件告诉Spring的IoC容器如何创建并组装Bean:
<bean id="userService" class="com.itranswarp.learnjava.service.UserService">
<property name="mailService" ref="mailService" />
</bean>
通过注入了一个Bean。
如果注入的不是Bean,而是boolean,int,String这样的数据类型,则通过value注入,如:
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test" />
<property name="username" value="root" />
<property name="password" value="password" />
<property name="maximumPoolSize" value="10" />
<property name="autoCommit" value="true" />
</bean>
在主方法中加载配置文件,创建一个IoC容器实例,这样就能从Spring容器中取出装配好的Bean然后使用它。
Spring的IoC容器接口是ApplicationContext;
通过XML配置文件实现IoC容器时,使用ClassPathXmlApplicationContext;
持有IoC容器后,通过getBean()方法获取Bean的引用。
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService userService = context.getBean(UserService.class); //根据Bean的类型获取Bean的引用
User user = userService.login("bob@example.com", "password");
System.out.println(user.getName());
}
}
另一种IoC容器叫BeanFactory,使用和ApplicationContext类似:
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("application.xml"));
MailService mailService = factory.getBean(MailService.class);
使用Annotation配置(更简单)
使用@Component注解相当于定义了一个Bean
使用@Aurowired相当于将指定类型的Bean注入到指定的字段中
@Component
public class UserService {
MailService mailService;
public UserService(@Autowired MailService mailService) {
this.mailService = mailService;
}
...
}
使用@Configuration表示它是一个配置类
使用@ComponentScan告诉容器,自动搜索当前类所在的包以及子包
最后编写一个AppConfig类启动容器:
@Configuration
@ComponentScan
public class AppConfig{
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = context.getBean(UserService.class);
User user = userService.login("bob@example.com", "password");
System.out.println(user.getName());
}
}
定制Bean
1.Spring默认使用Singleton创建Bean,也可指定Scope为Prototype.
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // @Scope("prototype")
public class MailSession {
...
}
2.可将相同类型的Bean注入List。
@Component
public class NameValidator implements Validator{
public void validate(String email, String password, String name){
...}
}
@Component
public class EmailValidator implements Validator {
public void validate(String email, String password, String name) {
...}
}
@Component
public class Validators{
@Autowired
List<Validator> validators;
public void validate(String email, String password, String name){
for(var validator: this.validators){
validator.validate(email, password, name);
}
}
}
3.可用@Autowired(required=false)允许可选注入;
4.如果一个Bean不在自己的package管理之内,可用带@Bean标注的方法创建Bean;
@Configuration
@ComponentScan
public class AppConfig {
// 创建一个Bean:
@Bean
ZoneId createZoneId() {
return ZoneId.of("Z");
}
}
5.可使用@PostConstruct和@PreDestroy对Bean进行初始化和清理;
@Component
public class MailService {
@Autowired(required = false)
ZoneId zoneId = ZoneId.systemDefault();
@PostConstruct
public void init() {
System.out.println("Init mail service with zoneId = " + this.zoneId);
}
@PreDestroy
public void shutdown() {
System.out.println("Shutdown mail service");
}
}
6.相同类型的Bean只能有一个指定为@Primary,其他必须用@Quanlifier(“beanName”)指定别名;
@Configuration
@ComponentScan
public class AppConfig {
@Bean("z")
ZoneId createZoneOfZ() {
return ZoneId.of("Z");
}
@Bean
@Qualifier("utc8")
ZoneId createZoneOfUTC8() {
return ZoneId.of("UTC+08:00");
}
}
注入时,可通过别名@Quanlifier(“beanName”)指定某个Bean;
@Component
public class MailService {
@Autowired(required = false)
@Qualifier("z") // 指定注入名称为"z"的ZoneId
ZoneId zoneId = ZoneId.systemDefault();
...
}
使用Resource(读取文件)
Spring提供了Resource类便于注入资源文件,它可以像String、int一样使用@Value注入,然后使用resouce.getInputStream()方法获取到输入流。
@Component
public class AppService{
@Value("classpath:/logo.txt")
private Resource resource;
private String logo;
@PostConstruct
public void init() throws IOException {
try (var reader = new BufferedReader(
new InputStreamReader(resource.getInputStream(), StandardCharsets.UTF_8))) {
this.logo = reader.lines().collect(Collectors.joining("\n"));
}
}
}
注入配置(读取配置文件)
@PropertySource来自动读取配置文件
@Configuration
@ComponentScan
@PropertySource("app.properties")
public class AppConfig{
@Value("${app.zone:Z}")//读取key为app.zone的value
String zoneId;
@Bean
ZoneId createZoneId() {
return ZoneId.of(zoneId);
}
}
还可以把注入的注解写到方法参数中:
@Bean
ZoneId createZoneId(@Value("{app.zone:Z}") String zoneId){
return ZoneId.of(zoneId);
}
另一种注入配置的方法是通过一个简单的JavaBean持有所有的配置,例如一个SmtpConfig:
@Component
public class SmtpConfig {
@Value("${smtp.host}")
private String host;
@Value("${smtp.port:25}")
private int port;
public String getHost() {
return host;
}
public int getPort() {
return port;
}
}
然后,在需要读取的地方,使用#{smtpConfig.host}注入:
@Component
public class MailService {
@Value("#{smtpConfig.host}")
private String smtpHost;
@Value("#{smtpConfig.port}")
private int smtpPort;
}
使用条件装配
创建某个Bean时,可以根据**@Profile来决定是否创建,表示不同的环境**
例如分别定义三种环境:开发、测试和生产
@Configuration
@ComponentScan
public class AppConfig {
@Bean
@Profile("!test")
ZoneId createZoneId() {
return ZoneId.systemDefault();
}
@Bean
@Profile("test")
ZoneId createZoneIdForTest() {
return ZoneId.of("America/New_York");
}
}
Spring还可以根据**@Conditional决定是否创建某个Bean。**