目录
a 首先定义一个Java简单对象(Plain Ordinary Java Object,POJO),然后再定义一个Java配置文件AppConfig.java
b 下面使用AnnotationConfigApplicationContext来构建自己的IoC容器
a 修改user类 加入注解@Component 和 @Value
9、使用属性文件application.properties
b application.properties配置属性清单
c 两种引用application.properties的方法
c1 通过@Value注解,使用${......}这样的占位符读取配置在属性文件的内容
c2 使用注解@ConfigurationProperties,通过它使得配置上有所减少
1、一个例子看懂spring IOC
Spring IoC容器是一个管理Bean的容器,在Spring的定义中,它要求所有的IoC容器都需要实现接口BeanFactory,它是一个顶级容器接口。
a 首先定义一个Java简单对象(Plain Ordinary Java Object,POJO),然后再定义一个Java配置文件AppConfig.java
User.java
package com.springboot.chapter3.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
public class User {
private Long id;
private String userName;
private String note;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
AppConfig.java
package com.springboot.chapter3.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
//@Configuration代表这是一个Java配置文件,Spring的容器会根据它来生成IoC容器去装配Bean
public class AppConfig {
@Bean(name="user")
//@Bean
//代表将initUser方法返回的POJO装配到IoC容器中,而其属性name定义这个Bean的名称,如果没有配置
//它,则将方法名称“initUser”作为Bean的名称保存到Spring IoC容器中
public User initUser() {
User user = new User();
user.setId(1L);
user.setUsername("course1");
user.setNote("note1");
return user;
}
}
@Configuration
代表这是一个Java配置文件,Spring的容器会根据它来生成IoC容器去装配Bean
@Bean
代表将initUser方法返回的POJO装配到IoC容器中,而其属性name定义这个Bean的名称,如果没有配置它,则将方法名称“initUser”作为Bean的名称保存到Spring IoC容器中
b 下面使用AnnotationConfigApplicationContext来构建自己的IoC容器
package com.springboot.chapter3.config;
import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import com.springboot.course1.config.AppConfig;
import com.springboot.course1.config.User;
public class IocTest {
private static Logger log = Logger.getLogger(IocTest.class);
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user = ctx.getBean(User.class);
log.info(user.getUsername());
}
}
配置在配置文件中的名称为user的Bean已经被装配到IoC容器中,并且可以通过getBean方法获取对应的Bean,并将Bean的属性信息输出出来
当然这只是很简单的方法,而注解@Bean也不是唯一创建Bean的方法,还有其他的方法可以让IoC容器装配Bean,而且Bean之间还有依赖的关系需要进一步处理
2、装配bean
Spring中允许我们通过XML或者Java配置文件装配Bean,但是Spring Boot是基于注解方式的
如果一个个的Bean使用注解@Bean注入Spring IoC容器中,那将是一件很麻烦的事情
Spring还允许我们进行扫描装配Bean到IoC容器中,对于扫描装配而言使用的注解是@Component和@ComponentScan
@Component是标明哪个类被扫描进入Spring IoC容器,而@ComponentScan则是标明采用何种策略去扫描装配Bean
a 修改user类 加入注解@Component 和 @Value
package com.springboot.chapter3.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("user")
//@Component表明这个类将被Spring IoC容器扫描装配
//其中配置的“user”则是作为Bean的名称,当然你也可以不配置这个字符串,那么IoC容器就会把类名第一个
//字母作为小写,其他不变作为Bean名称放入到IoC容器中;注解@Value则是指定具体的值,使得Spring
//IoC给予对应的属性注入对应的值
public class User {
@Value("1")
private Long id;
@Value("user_name_1")
private String userName;
@Value("note_1")
private String note;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
b 改造类AppConfig 加入注解@ComponentScan
package com.springboot.chapter3.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
//@Configuration代表这是一个Java配置文件,Spring的容器会根据它来生成IoC容器去装配Bean
@ComponentScan()
//这里加入了@ComponentScan,意味着它会进行扫描,但是它只会扫描类AppConfig所在的当前包和其子包
public class AppConfig {
//@Bean(name="user")
//@Bean
//代表将initUser方法返回的POJO装配到IoC容器中,而其属性name定义这个Bean的名称,如果没有配置
//它,则将方法名称“initUser”作为Bean的名称保存到Spring IoC容器中
public User initUser() {
//User user = new User();
//user.setId(1L);
//user.setUsername("course1");
//user.setNote("note1");
//return user;
}
}
再运行原来的IocTest类进行测试,可以获取到bean
3、@ComponentScan的使用方法总结
a 扫描某个包下面全部包和类
@ComponentScan({“ com.springboot.chapter3.*”)
b 指定多个包
@ComponentScan( basePackage = {“com.springboot.chapter3.pojo”,“com.springboot.chapter3.config”})
c 指定某个类
@ComponentScan( basePackageClasses = { User.class })
d excludeFilters过滤
UserService类,为了标注它为服务类,将类标注@Service(该标准注入了@Component,所以在默认的情况下它会被Spring扫描装配到IoC容器中),加入了excludeFilters的配置,使标注了@Service的类将不被IoC容器扫描注入,这样就可以把UserService类排除到Spring IoC容器中了
@ComponentScan(basePackages = {"com.springboot.chapter3.*"},excludeFilters = {@Filter(classes=Service.class)})
4、自定义第三方bean
现实的Java的应用往往需要引入许多来自第三方的包,并且很有可能希望把第三方包的类对象也放入到Spring IoC容器中,这时@Bean注解就可以发挥作用了
例子:引入一个DBCP数据源
pom.xml上加入项目所需要DBCP包和数据库MySQL驱动程序的依赖:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
通过@Bean定义了其配置项name为“dataSource”,那么Spring就会把它返回的对象用名称“dataSource”保存在IoC容器中
AppConfig.java代码:
package com.springboot.chapter3.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Profile;
@Configuration
@ComponentScan(basePackages = "com.springboot.chapter3.*")
public class AppConfig {
@Bean(name = "dataSource", destroyMethod = "close")
public DataSource getTestDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/test_spring_boot");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
5、依赖注入
Bean之间的依赖,在Spring IoC的概念中,我们称为依赖注入(Dependency Injection,DI)
例子:
首先来定义两个接口,一个是人类(Person),另外一个是动物(Animal)。人类是通过动物去提供一些特殊服务的
package com.springboot.chapter3.pojo.definition;
public interface Person {
public void service();
public void setAnimal(Animal animal);
}
package com.springboot.chapter3.pojo.definition;
public interface Animal {
public void use();
}
接下来我们需要两个实现类
package com.springboot.chapter3.pojo;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import com.springboot.chapter3.pojo.definition.Animal;
import com.springboot.chapter3.pojo.definition.Person;
@Component
public class BussinessPerson implements Person{
@Autowired
private Animal animal = null;
@Override
public void service() {
this.animal.use();
}
@Override
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
package com.springboot.chapter3.pojo;
import org.springframework.stereotype.Component;
import com.springboot.chapter3.pojo.definition.Animal;
@Component
public class Dog implements Animal {
@Override
public void use() {
System.out.println("狗【" + Dog.class.getSimpleName()+"】是看门用的。");
}
}
注意看 animal属性的注解@Autowired,这也是我们在Spring中最常用的注解之一,十分重要,它会根据属性的类型(by type)找到对应的Bean进行注入
Dog类是动物的一种,所以Spring IoC容器会把Dog的实例注入BussinessPerson中
6、探讨@Autowired
@Autowired是我们使用得最多的注解之一
我们创建一个猫的类,继承接口animal
package com.springboot.chapter3.pojo;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.springboot.chapter3.pojo.definition.Animal;
@Component
@Primary
public class Cat implements Animal {
@Override
public void use() {
System.out.println("猫【" + Cat.class.getSimpleName()+"】是抓老鼠。");
}
}
提出问题:在的BussinessPerson类中,因为这个类只是定义了一个动物属性(Animal),而我们却有两个动物,一个狗,一个猫,Spring IoC如何注入呢?
如果你还进行测试,很快你就可以看到IoC容器抛出异常
原因:Spring IoC容器并不能知道你需要注入什么动物(是狗?是猫?)给BussinessPerson类对象,从而引起错误的发生
解决:
① 这里,我们只是将属性的名称从animal 修改为了dog,那么我们再测试的时候,你可以看到是采用狗来提供服务的
@Autowired
private Animal animal = null;
改为
@Autowired
private Animal dog = null;
注意事项:
注意的是@Autowired是一个默认必须找到对应Bean的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null,那么你可以配置@Autowired属性required为false
@Autowired(required = false)
除了可以标注属性外,还可以标注方法,如setAnimal方法
7、消除歧义的注解:@Primary和@Quelifier
注解@Primary,它是一个修改优先权的注解,当我们有猫有狗的时候,假设这次需要使用猫,那么只需要在猫类的定义上加入@Primary就可以了
package com.springboot.chapter3.pojo;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import com.springboot.chapter3.pojo.definition.Animal;
@Component
@Primary
public class Cat implements Animal {
@Override
public void use() {
System.out.println("猫【" + Cat.class.getSimpleName()+"】是抓老鼠。");
}
}
@Quelifier:它的配置项value需要一个字符串去定义,它将与@Autowired组合在一起,通过类型和名称一起找到Bean。我们知道Bean名称在Spring IoC容器中是唯一的标识,通过这个就可以消除歧义性了
@Autowired
@Qualifier("dog")
public void setAnimal(Animal animal) {
this.animal = animal;
}
Spring IoC将会以类型和名称去寻找对应的Bean进行注入
8、@Autowired注解对构造方法的参数进行注入
package com.springboot.chapter3.pojo;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import com.springboot.chapter3.pojo.definition.Animal;
import com.springboot.chapter3.pojo.definition.Person;
import org.springframework.beans.factory.DisposableBean;
@Component
public class BussinessPerson implements Person {
private Animal animal = null;
public BussinessPerson(@Autowired @Qualifier("dog") Animal animal){
this.animal = animal;
}
@Override
public void service() {
this.animal.use();
}
@Override
public void setAnimal(Animal animal) {
this.animal = animal;
}
}
代码中取消了@Autowired对属性和方法的标注。在参数上加入了@Autowired和@Qualifier注解,使得它能够注入进来
9、使用属性文件application.properties
a 属性文件的maven配置依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
有了依赖,就可以直接使用application.properties文件工作了
b application.properties配置属性清单
database.driverName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/chapter3
database.username=root
database.password=123456
c 两种引用application.properties的方法
c1 通过@Value注解,使用${......}这样的占位符读取配置在属性文件的内容
@Value注解,既可以加载属性,也可以加在方法上
package com.springboot.chapter3.pojo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/******** imports ********/
@Component
public class DataBaseProperties {
@Value("${database.driverName}")
private String driverName = null;
@Value("${database.url}")
private String url = null;
private String username = null;
private String password = null;
public void setDriverName(String driverName) {
System.out.println(driverName);
this.driverName = driverName;
}
public void setUrl(String url) {
System.out.println(url);
this.url = url;
}
@Value("${database.username}")
public void setUsername(String username) {
System.out.println(username);
this.username = username;
}
@Value("${database.password}")
public void setPassword(String password) {
System.out.println(password);
this.password = password;
}
/**** getters ****/
public String getDriverName() {
return driverName;
}
public String getUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
c2 使用注解@ConfigurationProperties,通过它使得配置上有所减少
注解@ConfigurationProperties中配置的字符串database,将与POJO的属性名称组成属性的全限定名去配置文件里查找,这样就能将对应的属性读入到POJO当中
package com.springboot.chapter3.pojo;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties( "database")
public class DataBaseProperties {
private String driverName = null;
private String url = null;
private String username = null;
private String password = null;
public void setDriverName(String driverName) {
System.out.println(driverName);
this.driverName = driverName;
}
public void setUrl(String url) {
System.out.println(url);
this.url = url;
}
public void setUsername(String username) {
System.out.println(username);
this.username = username;
}
public void setPassword(String password) {
System.out.println(password);
this.password = password;
}
public String getDriverName() {
return driverName;
}
public String getUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
d 数据库的属性可以配置在jdbc.properties中
配置从application.properties中迁移到jdbc.properties中
然后使用@PropertySource去定义对应的属性文件,把它加载到Spring的上下文中
jdbc.properties
database.driverName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/chapter3
database.username=root
database.password=123456
使用@PropertySource去定义对应的属性文件:
package com.springboot.chapter3.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.PropertySource;
@SpringBootApplication
@ComponentScan(basePackages = {"com.springboot.chapter3"})
@PropertySource(value={"classpath:jdbc.properties"}, ignoreResourceNotFound=true)
@ImportResource(value = {"classpath:spring-other.xml"})
public class Chapter3Application {
public static void main(String[] args) {
SpringApplication.run(Chapter3Application.class, args);
}
}
value可以配置多个配置文件。使用classpath前缀,意味着去类文件路径下找到属性文件;ignoreResourceNotFound则是是否忽略配置文件找不到的问题。ignoreResourceNotFound的默认值为false,也就是没有找到属性文件,就会报错;这里配置为true,也就是找不到就忽略掉,不会报错
10、使用注解@Conditional进行条件装配Bean
有时候某些客观的因素会使一些Bean无法进行初始化
比如 :在数据库连接池的配置中漏掉一些配置会造成数据源不能连接上。在这样的情况下,IoC容器如果还进行数据源的装配,则系统将会抛出异常,导致应用无法继续
Spring提供了@Conditional注解处理这种场景
@Conditional注解需要配合另外一个接口
Condition(org.springframework.context.annotation.Condition)
来完成对应的功能
使用属性初始化数据库连接池
package com.springboot.chapter3.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Profile;
@Configuration
@ComponentScan(basePackages = "com.springboot.chapter3.*")
@ImportResource(value = {"classpath:spring-other.xml"})
public class AppConfig {
@Bean(name = "dataSource", destroyMethod = "close")
@Conditional(DatabaseConditional.class)
public DataSource getDataSource(
@Value("${database.driverName}") String driver,
@Value("${database.url}") String url,
@Value("${database.username}") String username,
@Value("${database.password}") String password
) {
Properties props = new Properties();
props.setProperty("driver", driver);
props.setProperty("url", url);
props.setProperty("username", username);
props.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
加入了@Conditional注解
并且配置了类DatabaseConditional,那么这个类就必须实现Condition接口。对于Condition接口则要求实现matches方法
DatabaseConditional:
package com.springboot.chapter3.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class DatabaseConditional implements Condition {
@Override
/*
*
* @param context 条件上下文
* @param
*/
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment env = context.getEnvironment();
return
env.containsProperty("database.driverName")
&&env.containsProperty("database.url")
&&env.containsProperty("database.username")
&&env.containsProperty("database.password");
}
}
matches方法首先读取其上下文环境,然后判定是否已经配置了对应的数据库信息。这样,当这些都已经配置好后则返回true。这个时候Spring会装配数据库连接池的Bean,否则是不装配的
11、bean的作用域
在一般的容器中,Bean都会存在单例(Singleton)和原型(Prototype)两种作用域
在Web容器中,存在页面(page)、请求(request)、会话(session)和应用(application)4种作用域
12、使用@Profile注解实现各个环境之间的切换
项目往往要面临开发环境、测试环境、准生产环境(用于模拟真实生产环境部署所用)和生产环境的切换,这样在一个互联网企业中往往需要有4套环境,而每一套环境的上下文是不一样的
Spring还提供了Profile机制,使我们可以很方便地实现各个环境之间的切换。
假设存在dev_spring_boot和test_spring_boot两个数据库,这样可以使用注解@Profile定义两个Bean
package com.springboot.chapter3.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
import org.springframework.context.annotation.Profile;
@Configuration
@ComponentScan(basePackages = "com.springboot.chapter3.*")
@ImportResource(value = {"classpath:spring-other.xml"})
public class AppConfig {
// @Bean(name = "dataSource", destroyMethod = "close")
// @Conditional(DatabaseConditional.class)
// public DataSource getDataSource(
// @Value("${database.driverName}") String driver,
// @Value("${database.url}") String url,
// @Value("${database.username}") String username,
// @Value("${database.password}") String password
// ) {
// Properties props = new Properties();
// props.setProperty("driver", driver);
// props.setProperty("url", url);
// props.setProperty("username", username);
// props.setProperty("password", password);
// DataSource dataSource = null;
// try {
// dataSource = BasicDataSourceFactory.createDataSource(props);
// } catch (Exception e) {
// e.printStackTrace();
// }
// return dataSource;
// }
@Bean(name = "dataSource", destroyMethod = "close")
@Profile("dev")
public DataSource getDevDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/dev_spring_boot");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
@Bean(name = "dataSource", destroyMethod = "close")
@Profile("test")
public DataSource getTestDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/test_spring_boot");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
待续........ 2022-07-26