若有错,请指出
第二章 搭建Springboot环境,配置视图解析器jsp页面
第三章 全注解下的Spring Ioc
第四章 约定编程-Spring AOP
第五章 Spring Boot的数据库编程
第九章 初识Spring MVC
文章目录
3.1 IOC容器简介
Spring IOC容器是一个管理Bean的容器,在Spring的定义中,所有的IOC容器都需要实现顶级容器接口BeanFactory
部分源码:
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
//多个getBean方法
//根据名称获取Bean
Object getBean(String var1) throws BeansException;
//根据名称+类型获取Bean
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
//是否包含Bean
boolean containsBean(String var1);
//Bean是否单例,即每次getBean返回同一个对象(默认值)
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
//Bean是否单例,即每次getBean返回不同对象
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
}
继承关系图
举例子
目录结构
在pom.xml引入依赖配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--引入dbcp包和数据库MySQL的依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 添加属性文件依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
创建简单的springboot项目后,定义简单对象
public class User {
private Long id;
private String userName;
private String note;
//右键快捷生成get和set方法
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;
}
}
定义Java配置文件
@Configuration注解表明这是配置文件,Spring的容器会根据它来生成IOC容器去装配Bean
@Bean表示将initUser方法返回的POJO对象装配到IOC容器中,属性name定义这个bean的名称,如果没添加name,则将方法名initUser作为bean名称装配到容器中
import com.springboot.chapter3.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConfig {
@Bean(name = "user")//定义Bean名称
public User initUser(){
User user=new User();
user.setId(1L);
user.setUserName("hh");
user.setNote("note");
return user;
}
}
构建IOC容器
import com.springboot.chapter3.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IocTest {
public static void main(String[] args){
//读取配置文件AppConfig,装配Bean到IOC容器中
ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
//根据Bean的类名获取Bean对象
User user=context.getBean(User.class);
System.out.println(user.getId()+" "+user.getUserName()+" "+user.getNote());
}
}
运行截图
3.2 装配Bean
3.2.1 通过扫描装配Bean
用@Bean一个个注入容器中很麻烦,可以通过进行扫描的方式装配Bean
@Component:表明哪个类注入容器中
@ComponentScan:表明采用何种策略扫描Bean
像@Controller、@Service、@Repository常用注解里面都标注了@Component
在pojo中添加注解@Component
@Component("user")//user为bean名称,若不写默认以类名第一个字母小写其余不变作为bean名
public class User {
@Value("111")//属性赋值
private Long id;
@Value("userName111")
private String userName;
@Value("note111")
private String note;
//右键快捷生成get和set方法
}
改造AppConfig
启动IoCTest类
@ComponentScan做的事情就是告诉Spring从哪里找到Bean
@ComponentScan用法
basePackageClasses:定义扫描的类
下面两个需要通过@Filter去定义
includeFilters:定义满足过滤器条件的Bean才去扫描
excludeFilters:排除过滤条件的Bean
把User类放回到pojo包中再运行会报错,找不到bean对象,可能扫描不到包下的类或者缺失注解@Component
所以要修改AppConfig的注解@ComponentScan的扫描策略,能够扫描User类,有三种方式
//@ComponentScan("com.springboot.chapter3.*")//扫描该包及其子包被@Component或其他能被扫描的注解标注的类
//@ComponentScan(basePackages = {"com.springboot.chapter3.pojo"})//扫描pojo包下的类
@ComponentScan(basePackageClasses = {User.class})//扫描User类
再次运行就能成功了
在上面例子中是通过@Configuration标注的配置类AppConfig的注解@ComponentScan来采用扫描策略
我们也可以在启动类中添加如下注解,@ComponentScan详解&@SpringBootApplication的scanBasePackages属性
(scanBasePackages ={"com.springboot.chapter3"} )
3.2.2 自定义第三方Bean
许多Java的应用要引入第三方包,也有可能希望把第三方包对象注入到IOC容器中,可用@Bean注解注入
定义DBCP数据源
<!--引入dbcp包和数据库MySQL的依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
AppConfig类添加下面代码生成数据源
// 自定义第三方bean
//若不写name,Spring会把方法名称作为Bean名称保存到IOC容器中
@Bean(name = "dataSource")
public DataSource getDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/chapter3");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
编写IocTest类
public class IoCTest {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext ctx=null;
try {
ctx=new AnnotationConfigApplicationContext(AppConfig.class);
//获取第三方Bean
DataSource dataSource=ctx.getBean(DataSource.class);//通过类型获取
System.out.println(dataSource.getConnection().getMetaData().getDatabaseProductName());
}finally {
if(ctx!=null)
ctx.close();//关闭IOC容器
}
运行截图
3.3 依赖注入
上一节中讲了如何将bean装配到IOC容器中,这里讲讲Bean之间的依赖,也就是依赖注入(Dependency Injection,DI)
例如,人类(Person)利用一些动物(Animal)去完成一些事,狗(Dog)用来看门,猫(Cat)用来抓老鼠;也就是说人类可以依赖动物提供一些特定的服务
3.3.1 样例
目录结构
创建Person接口和实现类
public interface Person {
public void service();
}
@Component
public class BussinessPerson implements Person {
//按类型(Animal)查找IOC容器的Bean,如果找的到就进行赋值
@Autowired
Animal animal = null;
//使用动物服务
@Override
public void service() {
this.animal.use();
}
}
定义动物接口和实现类
public interface Animal {
public void use();
}
@Component
public class Dog implements Animal {
@Override
public void use() {
System.out.println("狗【" + Dog.class.getSimpleName() + "】看门用的");
}
}
测试类
public class IocTest {
public static void main(String[] args){
ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
Person person=context.getBean(BussinessPerson.class);
person.service();
}
}
运行截图
通过这例子说明Perso需要使用特定动物服务只需要加入一些描述如@Autowired
3.3.2 注解@Autowire
它是使用最多的注解之一,注入机制最基本一条就是根据类型(type)。前面提到的IOC容器顶级接口BeanFactory里有一些getBean方法,就是通过类型(type)或者名称(name)获取bean。
使用规则:
1.先根据类型找到对应Bean
2.如果对应类型不唯一,则根据属性名和Bean名称进行更精确匹配
3.如果还无法匹配就会抛出异常
该注解可以写到属性,set方法和构造方法参数上,效果一样。另外,@Autowire是默认必须找到对应Bean的注解,如果标注了但又找不到会报错,所以可以配置@Atuowire(required=false)允许被标注的属性值为空(不推荐这样写)
3.3.3 消除歧义性-@Primary和@Qualifier
出现歧义原因可划分3点
1.多个Bean类实现同一个接口
2.Java中存在继承,通过父类型去查找存在多个子类的Bean
3.Java中一个类可以存在多个实例,多个Bean实例都可以存放在IOC容器中
这里模拟第一种情况的歧义
创建猫类
@Component
public class Cat implements Animal {
@Override
public void use() {
System.out.println("猫【" + Cat.class.getSimpleName() + "】抓老鼠的");
}
}
虽然可以通过修改属性名让@Autowire根据属性名和类型匹配,但是非常不方便需要改动多处代码
@Autowired
Animal cat = null;
我们可以用@Primary标注(这注解只能出现一个)
也可以用@Qualifier,IOC容器就会以类型和名称去寻找对应的Bean进行注入
还有比较少用的JavaEE自带注解@Resource,@Autowire和@Resource区别
3.4 Bean的生命周期(重要,面试题)
Bean的生命周期可分为bean的定义、bean的初始化、bean的生存期、bean的销毁4个部分。
在这张图中除了BeanPostProcessor外,其他的接口都是针对当前Bean有效,BeanPostProcessor则是对全部Bean有效。ApplicationContextAware这个接口,只对实现类ApplicationContext的IOC容器生效,只有实现了ApplicationContext接口的容器才会在生命周期调用ApplicationContextAware定义的方法setApplicationContext。
Bean的生命周期图
Bean定义过程如下:
1.Spring通过我们的配置
ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
根据AppConfig配置类的注解@ComponentScan定义的扫描路径去找带有@Component的类(@Controller、@Service里面包含@Component也可以被扫描),这里是资源定位的过程
2.找到资源后会开始解析,将定义的信息保存起来。(没有初始化Bean,没有Bean的实例,只是Bean的定义)
3.把Bean的定义发布到IOC(ApplicationContext context)容器中,只有Bean定义,还没bean的实例
有时候我们只需要在取处Bean的时候才做初始化和依赖注入,就需要做到延迟初始化@ComponentScan的配置项lazyInit
在BussinessPerson类添加无参构造方法
//测试延迟初始化
public BussinessPerson(){
System.out.println("初始化");
}
未延迟初始化时
配置延迟初始化后需要在取bean的时候就是执行getBean方法时才初始化
这里来模拟Bean完整的生命周期,实现生命周期图中的接口
改造类BussinessPerson
@Component
public class BussinessPerson implements Person, BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
//按类型(Animal)查找IOC容器的Bean,如果找的到就进行赋值
@Qualifier("cat")
@Autowired//1.注入方式:属性上
Animal animal = null;
//测试延迟初始化
public BussinessPerson(){
System.out.println("初始化");
}
//2.注入方式:构造方法参数上
// public BussinessPerson(Animal animal){
// this.animal=animal;
// }
//3.注入方式:set方法上
//@Autowired
public void setAnimal(Animal animal) {
this.animal = animal;
}
//使用动物服务
@Override
public void service() {
this.animal.use();
}
@Override
public void setBeanName(String beanName) {
System.out.println("【" + this.getClass().getSimpleName()
+ "】调用BeanNameAware的setBeanName");
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName()
+ "】调用BeanFactoryAware的setBeanFactory");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName()
+ "】调用ApplicationContextAware的setApplicationContext");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("【" + this.getClass().getSimpleName()
+ "】调用InitializingBean的afterPropertiesSet方法");
}
@PostConstruct
public void init() {
System.out.println("【" + this.getClass().getSimpleName()
+ "】注解@PostConstruct定义的自定义初始化方法");
}
@PreDestroy
public void destroy1() {
System.out.println("【" + this.getClass().getSimpleName()
+ "】注解@PreDestroy定义的自定义销毁方法");
}
@Override
public void destroy() throws Exception {
System.out.println("【" + this.getClass().getSimpleName()
+ "】 DisposableBean方法");
}
}
实现BeanPostProcessor接口的类,对所有bean有效
@Component
public class BeanPostProcessorBean implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("所有Bean有效的postProcessBeforeInitialization方法");
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("所有Bean有效的postProcessAfterInitialization方法");
return bean;
}
}
IocTest类
public class IocTest {
public static void main(String[] args){
//测试Bean的生命周期
AnnotationConfigApplicationContext context=null;
try {
context=new AnnotationConfigApplicationContext(AppConfig.class);
Person person=context.getBean(Person.class);
person.service();
}finally {
if(context!=null)context.close();//关闭IOC容器
}
}
运行截图
@PostConstruct和@PreDestroy注解自定义方法和接口ApplicationContextAware的setApplicationContext方法较为常用
3.5 使用属性文件
在Spring Boot中经常使用配置文件,我们可以用默认的配置文件application.properties也可以自己创建配置文件
1.使用默认配置文件
目录结构
application.properties
database.driverName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/chapter3
database.username=root
database.password=123456
创建新类使用属性配置
@Component
public class DataBaseProperties {
//引用application.properties属性名的值
@Value("${database.driverName}")
private String driverName;
@Value("${database.url}")
private String url;
private String username;
private String password;
public String getDriverName() {
return driverName;
}
public void setDriverName(String driverName) {
this.driverName = driverName;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
//@Value也可以写在set方法上注入
@Value("${database.username}")
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
@Value("${database.password}")
public void setPassword(String password) {
this.password = password;
}
}
上面使用@Value赋值我们也可以用@ConfigurationProperties注解赋值
控制层
@Controller
public class PropsController {
@Autowired
private DataBaseProperties dataBaseProperties = null;
@RequestMapping("/dbp")
@ResponseBody
public DataBaseProperties getDBP() {
return dataBaseProperties;
}
}
启动项目然后输入路径
2.使用新的属性文件
将application.properties的配置放在同目录下的database.properties
为了能让该配置文件能被扫描还需要在启动类添加如下注解
@PropertySource("classpath:database.properties")
启动后如果出现说database.properties is not exist建议可以试试重建项目(rebuild)
3.6 条件装配bean
AppConfig类
@Profile(value = "test")
@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无法初始化。例如在数据库连接池中漏掉一些配置造成无法连接,在这种情况下如果IOC容器还想继续装配就会抛出异常。这时不希望装配bean,而是都提供配置后才装配bean。
为了处理这样搞场景我们可以使用过@Conditional注解,需要实现接口Condition的方法来完成对应功能
在getDataSource方法添加注解@Conditional(DatabaseConditional.class)
创建DatabaseConditional类,通过这样在装配bean的时候就match方法会读取上下文环境如果已经配置了对应的数据库信息,返回true执行getDataSource方法,否则不装配bean
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;
//配置条件装配bean
public class DatabaseConditional implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//取出环境配置
Environment environment = context.getEnvironment();
//判断属性文件是否存在对应的数据库配置
return environment.containsProperty("database.driverName") &&
environment.containsProperty("database.url") &&
environment.containsProperty("database.username") &&
environment.containsProperty("database.password");
}
}
3.7 Bean的作用域
之前提到过的IOC容器最顶级接口BeanFactory里面有isSingleton(默认值,IOC容器只存在单例)和isPrototype(每从容器中取bean就创建一个新的bean)
未设置作用域的时候是单例模式,要想定义作用域类可以添加注解@Scope
测试作用域