Spring的两个核心理念:
- 一个是控制反转(Inversion of Control, IoC)
- 另一个是面向切面编程(Aspect Oriented Programming, AOP)
IoC是一和通过描述生成或者获取对象的技术。Springboot不建议使用XML,而是通过注解的描述生成对象。
IoC容器需要具备两个基本功能:
- 通过描述管理Bean,包括发布和获取Bean
- 通过描述完成Bean之间的依赖关系。
IoC容器简介
Sping IoC是管理Bean的容器。Spring要求所有的IoC容器都需要实现BeanFactory。
源码BeanFactory接口中,可以知道,Spring IoC容器中,允许我们按类型或者名称获取Bean。
其中,isSingleton方法判断Bean是否在Spring IoC中为单例的。默认司下,Bean 都是单例存在的,也主是使得getBean方法返回都是同一个对象。
与isSingleton方法相反的是isProtorype方法,如果它返回的是ture,那么getBean方法获取Bean是,容器将创建一个新的Bean返回给调用者。
public class AppConfig {
@Bean(name = "user")
public User initUser() {
User user = new User();
user.setId(1L);
user.setUserName("user_name_1");
user.setNote("note_1");
return user;
}
}
@Configuration 代表这是一个Java配置文件,Spring的容器会根据它来生成IoC容器去装配 Bean;@Bean 代表将initUser方法返回的POJO装配到IoC容器中,而其属性name定义这个Bean名称,如果没有配置它,则将方法名称"initUser" 作为Bean的名称保存到Spring IoC容器中。
装配你的Bean
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.context.annotation
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
// 在一个类中可重复定义
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
// 定义扫描的包
@AliasFor("basePackages")
String[] value() default {};
// 定义扫描的包
@AliasFor("value")
String[] basePackages() default {};
// 定义扫描的类
Class<?>[] basePackageClasses() default {};
// Bean name 生成器
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
// 作用域解析器
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
// 作用代理模式
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
// 资源匹配模式
String resourcePattern() default "**/*.class";
// 是否启动默认过滤器
boolean useDefaultFilters() default true;
// 当满足过滤器条件时扫描
ComponentScan.Filter[] includeFilters() default {};
// 当不满足过滤器条件时扫描
ComponentScan.Filter[] excludeFilters() default {};
// 是否延迟初始化
boolean lazyInit() default false;
// 定义过滤器
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
- 可以通过配置项basePackages定义扫描包名,在没有定义的情况下,它只会扫描当前包和其子包下的路径
- 可以通过basePackageClasses定义扫描的类
- 还有includeFilters 和 excludeFilters
- includeFilters是定义满足过滤器(Filter)条件的Bean才去扫描
- excludeFilters是排除过滤器条件的Bean。
- 它们都需要通过@Filter去定义,它有一个type类型,这里可以定义为注解或者正则式等类型。
@Component("user")
public class User {
@Value("1")
private Long id;
@Value("user_name_1")
private String userName;
@Value("note_1")
private String note;
}
@ComponentScan("com.springboot.chapter3.*")
@ComponentScan(basePackages = {"com.springboot.chapter3.pojo"})
@ComponentScan(basePackageClasses = {User.class})
public class AppConfig {
}
上述三种方式都能够使用IoC容器去扫描User类,而包名可以采用正则式去匹配。
@ComponentScan(basePackages = “com.springboot.chapter3.*”, excludeFilters = {@ComponentScan.Filter(classes = {Service.class})})
@excludeFilters的配置,使标注了@Service的类将不在被IoC容器扫描注入。
SpringBootApplication 也注入了 @ComponentScan。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.boot.autoconfigure;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.TypeExcludeFilter;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
// 自定义排除扫描类
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication
// 自定义排除扫描类
@AliasFor(
annotation = EnableAutoConfiguration.class
)
Class<?>[] exclude() default {};
// 通过名称排除自动配置为
@AliasFor(
annotation = EnableAutoConfiguration.class
)
String[] excludeName() default {};
//定义扫描包
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackages"
)
String[] scanBasePackages() default {};
// 定义扫描的类
@AliasFor(
annotation = ComponentScan.class,
attribute = "basePackageClasses"
)
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
通过它可以定义扫描哪些包。它所提供的exclude 和 excludeName两个方法是对其内部自动配置类才会生效的。
扫描User而不扫描UserService
@SpringBootApplication
@ComponentScan(basePackages = "com.springboot.chapter3.*", excludeFilters = {@ComponentScan.Filter(classes = {Service.class})})
自定义第三方Bean
Spring会将"dataSource"保存在IoC容器中。
@Configuration
public class AppConfig {
@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", "root");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
}
依赖注入
人类(Person) 有时候利用一些动物 (Animal) 去完成一些事情,比方说狗(Dog) 看门, 猫 (Cat) 爬老鼠, 鹦鹉(Parrot)迎客…
// 人和动物的接口
package com.springboot.chapter3.pojo.definition;
public interface Animal {
public void use();
}
public interface Person {
public void service();
public void setAnimal(Animal animal);
}
//人类实现类
package com.springboot.chapter3.pojo;
@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;
}
}
// 动物实现类
@Component
public class Dog implements Animal {
@Override
public void use() {
System.out.println("狗【" + Dog.class.getSimpleName() + "】是看门用的");
}
}
package com.springboot.chapter3.config;
public class IoCTest {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
Person person = ctx.getBean(BussinessPerson.class);
person.service();
}
}
输出:狗【Dog】是看门用的
@Autowrited 它会根据属性的类型(by type) 找到对应的Bean进行注入。这里的Dog是动物的一种,所以SpingIoC会把Dog的实例注入BussinessPerson中。
@Autowired它注入的机制最基本的一条是根据类型(by type),IoC容器顶级接口BeanFacory,可以知道IoC容器通过getBean方法获取对应Bean的,而 getBean又支持根据类型或根据名称。
创建一个猫类:
@Component
public class Cat implements Animal {
@Override
public void use() {
System.out.println("猫【" + Cat.class.getSimpleName() + "】是抓老鼠");
}
}
异常:
expected single matching bean but found 2: cat,dog
从这里可以看出,Spring IoC并不能判断是要注入什么动物,给BussinessPerson类对象,从而引起错误的发生。
这里假设我们目前是需要狗提供服务,那么可以属性名转为dog
@Autowired
private Animal animal = null;
修改为
@Autowired
private Animal dog = null;
@Autowired提供这样的规则,首先它会根据类型找到对应的Bean,如果对应的类型Bean不是唯一的,那么会根据其属性名称进行匹配。匹配不上则抛出异常。
@Autowired 是一个默认必须找到对应Bean的注解,如果不能确定其标注属性一定会存在并且允许这个被标注的属性为null。那么可以配置属性required 为 false;
@Autowired(required = false)
消除歧义性-- @Primary 和 @Quelifier
上述出院歧义我和将 animal修改为了dog。产生注入失败根本问题是按类型查找(by type)查找。
@Primary,它是一个修改优先权的注解(优先及by name by type之上)。
@Component
@Primary
public class Cat implements Animal {}
@Primary的含义是告诉Spring IoC容器,当发现多个Bean时优先使用我注入。如果存在多个 @Primary 还是会引发歧义。
@Qulifier和@Autowired组合在一起,通过类型和名称一起找到Bean。
<T> T getBean(String name, Class<T> requiredType) throws BeansException。
假设猫已经标注@Primary我们需要狗提供服务。
@Autowired
@Qualifier("dog")
private Animal animal = null;
带参数的构造方法类装配
有些类只有带构造参数的构造方法,于是上述的方式都失效了。修改BussinePerson满足这个功能
@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;
}
}
生命周期
Bean的生命周期过程,它大致分为Bean定义、Bean的初始化、Bean的生存期和Bean的销毁。
- Spring通过我们的配置,如@ComponentScan定义的扫描路径去找到带有@Component的类,这个过程就是一个资源定位的过程。
- 一旦找到资源,它就开始解析,并且将定义的信息保存起来。注意,此时还没有初始化Bean,也没有Bean的实例,它有的仅仅是Bean的定义
- 然后会把Bean定义发布到Spring IoC容器中。此时IoC容器中,也只有Bean的定义。
默认情况下Spring会继续完成Bean的实例化和依赖注入,这样从IoC容器中就可以得到一个依赖注入完成的Bean。这是我们希望,Bean只是将定义发布到IoC容器而不做实例化和依赖注入,当我们取出来的时候才做初始化和依赖流入等操作。
Spring初始化流程
![](https://i-blog.csdnimg.cn/blog_migrate/e105d89fe40794dbf0445a926e54db36.png)
ComponentScan 中还有一个配置项 lazyInit,只可以配置Boolean值,且默认值为false,因此在默认情况下,spring会对Bean进行实例化和依赖注入对应的属性值。
@ComponentScan(basePackages = "com.springboot.chapter3.*",
excludeFilters = {@ComponentScan.Filter(classes = {Service.class})},
lazyInit = true)
Spring在完成依赖注入后,还提供了一系列的接口和配置来完成Bean初始化过程。图中描述的是整个IoC容器初始化Bean的流程
对于那么没有实现ApplicationContext的接口容器,在生命周期对应ApplicationContextAware定义的方法是不会被调用的。
实现BeanPostProcessor这个Bean后置处理器将对所有的Bean有效,而其它的接口都是对于单个Bean起作用。
自定义初始化和销毁方法
@Bean(initMethod = "init" , destroyMethod = "destroy")
使用属性文件
可以采用application.properties,也可以定义配置文件。
SpringBoot中,可以在Maven中添加依赖,SpringBoot将创建读取属性文件上下文。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
配置属性:
database.driverName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/chapter3
database.username=root
database.password=123456
SpringBoot会通过其机制读取到上下文,可以引用它。
第一种:Spring表达式。
@Component
@ConfigurationProperties("database")
public class DataBaseProperties {
@Value("{${database.driverName}")
private String driverName = null;
.....
@Value("${database.username}")
public void setUsername(String username) {
this.username = username;
}
}
@ConfigurationProperties
@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;
}
}
... GET方法
@ConfigurationProperties 中配置的字符串 database ,将与POJO 的属性名组成属性的全限定名去配置文件里查找。
@SpringBootApplication
@ComponentScan(basePackages = {"com.springboot.chapter3"})
@PropertySource(value = {"classpath:jdbc.properties"}, ignoreResourceNotFound = true)
public class Chapter3Application {
public static void main(String[] args) {
SpringApplication.run(Chapter3Application.class, args);
}
}
@PropertySource 定义对应的属性文件将它加载到Spring上下文中,
ignoreResourceNotFound 则是忽略配置文件找不到问题。 其默认值为false,找不到就抛出异常。
条件装配
某些客观因素会例一些Bean无法进行初始化,例如:错误的配置导入数据源不能连接上。这种情况下,如果IoC还进行数据源装配则系统抛出异常,导致应用无法继续。
@Bean(name = "dataSource", destroyMethod = "close")
@Conditional(DatabaseCondition.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;
}
/**
数据库装配条件
context 上下文
metadata 注释 类型元数据
return ture 表示装配Bean
*/
public class DatabaseCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment env = conditionContext.getEnvironment();
return env.containsProperty("database.driverName")
&& env.containsProperty("database.url")
&& env.containsProperty("database.username")
&& env.containsProperty("database.password");
}
}
@Conditional注解,可以实现按条件装配。 matched方法首先读取上下文环境,然后判定是否已经配置了对应的数据库信息。
Bean作用域
单例(Singleton) 和 原型(Prototype):
isSingleton方法如果返回ture,则Bean在IoC容器中以单例存在,这是Spring IoC容器默认值;如果方法返回true,则当我们每次获取Bean的时候,IoC容器都会创建一个新的Bean。
Web容器中,存在page,request,session,application四种作用域。page是针对jsp当前页面的作用域,所以Spring是无法支持的。
configurableBeanFactory 提供 单例SCOPE_SINGLETON和原型 SCOPE_PROTOTYPE两个种
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean {
}
可以使用 WebApplicationContext去定义其他作用域
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class ScopeBean {
}
使用@Profile
此注解应用于 开发环境、测试环境、生产环境多个环境中的数据库资源切换
@Bean(name = "dataSource", destroyMethod = "close")
@Profile("dev")
public DataSource getDevDataSource(){
System.out.println("我是开发环境");
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/springboot_dev");
props.setProperty("username", "root");
props.setProperty("password", "123");
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(){
System.out.println("我是测试环境");
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/springboot_test");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {e.printStackTrace();}
return dataSource;
}
启动时,我们只需要如下配置profile就可以完成数据库资源切换:
JAVA_OPTS="-Dspring.profiles.active=dev"
Spring 常用EL
@Value 中的 #{…}代表占位符,它会读取上下文中的属性值装配到属性中
@Value("${database.driverName")
String driver
#{…}代表启动Spring表达式,它将具体运算功能, T(…)代表的是引入类。 System是java.lang.*包的类, 是java默认加载的包,因此可以不必定全限定名。
@Value("#{T(System) .currentTimeMillis() }")
private Long initTime = null;
@Value("#{9.3E3}")
private double d;
//获取其他的Spring Bean 的属性来给当前的Bean属性
@Value("#{beanName.str}"")
private String otherBeanProp = null;
//判断当前属性是否为空,如果不为空则执行toUpperCase()方法。
@Value("#{beanName.str?.toUpperCase()}"")
private String otherBeanProp = null;
@Value("#{dog.username?.toUpperCase()}")
private String otherBeanProp = null;