1. 组件注册
① @Configuration
配置类
作用: 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用
ApplicationContext applicationContext = new AnnotationApplicationContext(有@Configuration 注解的类.class)
属性: value:用于指定配置类的字节码
@Configuration
public class SpringConfiguration {
//给容器注册一个Bean,类型为返回值类型,id默认是方法名
@Bean
public Person person(){
return new Person();
}
}
② @ComponentScan
自动扫描 + 组件标注注解(@Controller
…)
作用: 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的: <context:component-scan base-package="com.minifull"/>
是一样的
属性:
value:指定要扫描的package;
includeFilters=Filter[]:指定只包含的组件
excludeFilters=Filter[]:指定需要排除的组件;
useDefaultFilters=true/false:指定是否需要使用Spring默认的扫描规则:被@Component, @Repository, @Service, @Controller或者已经声明过@Component自定义注解标记的组件;
使用
@Configuration
@ComponentScan("com.minifull")//也可以是ComponentScans配置多个
public class SpringConfiguration {
}
过滤规则说明
1. 扫描指定类文件
@ComponentScan(basePackageClasses = Person.class)
2. 扫描指定包,使用默认扫描规则,即被@Component, @Repository, @Service, @Controller或者已经声明过@Component自定义注解标记的组件;
@ComponentScan(value = "com.yibai")
3. 扫描指定包,加载被@Component注解标记的组件和默认规则的扫描(因为useDefaultFilters默认为true)
@ComponentScan(value = "com.yibai", includeFilters = { @Filter(type = FilterType.ANNOTATION, value = Component.class) })
4. 扫描指定包,只加载Person类型的组件
@ComponentScan(value = "com.yibai", includeFilters = { @Filter(type = FilterType.ASSIGNABLE_TYPE, value = Person.class) }, useDefaultFilters = false)
5. 扫描指定包,过滤掉被@Component标记的组件
@ComponentScan(value = "com.yibai", excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Component.class) })
6. 扫描指定包,自定义过滤规则
@ComponentScan(value = "com.yibai", includeFilters = { @Filter(type = FilterType.CUSTOM, value = ColorBeanLoadFilter.class) }, useDefaultFilters = true)
自定义规则
③ @Bean
注册组件
针对上面@ComponentScan自动扫描 + 组件标注注解(@Controller..)
注册组件的方式只适合我们自己写的组件bean,但是对于别人提供的jar包的类,我们不能去打开包修改源码加上注解,那么可以使用@Bean
在配置类中注册
作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
属性: name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。
@Configuration
public class JdbcConfig {
/**
* 创建一个数据源,并存入 spring 容器中 * @return
*/
@Bean(name="dataSource")
public DataSource createDataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setUser("root");
ds.setPassword("1234");
ds.setDriverClass("com.mysql.jdbc.Driver");
ds.setJdbcUrl("jdbc:mysql:///spring_day02");
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 创建一个 DBAssit,并且也存入 spring 容器中
* @param dataSource
* @return */
@Bean(name="dbAssit")
public DBAssit createDBAssit(DataSource dataSource) {
return new DBAssit(dataSource);
}
}
//我们已经把数据源和 DBAssit 从配置文件中移除了,此时可以删除 bean.xml 了。
//但是由于没有了配置文件,创建数据源的配置又都写死在类中了
④ @Scope
设置组件作用域 / @Lazy懒加载
作用: 指定 bean 的作用范围。
属性: value:指定范围的值(singleton prototype request session globalsession
)
@Scope("singleton")//单例 默认是饿汉式;也可以设置为懒加载
@Lazy//懒加载
@Scope("singleton")
⑤ @Conditional
按照条件注册bean
@Conditional
@Conditional
是Spring4新提供的注解,它的作用是按照一定的条件进行判断,满足条件给容器注册bean
@Conditional
的定义:
//此注解可以标注在类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
从代码中可以看到,需要传入一个Class
数组,并且需要继承Condition
接口,Condition
接口,需要实现matches
方法,返回true
则注入bean,false
则不注入
public interface Condition {
boolean matches(ConditionContext var1, AnnotatedTypeMetadata var2);
}
实例:
以下有Person类,我想根据当前操作系统来注入Person实例,windows下注入bill,linux下注入linus,怎么实现呢?
public class Person {
//....
}
这就需要我们用到@Conditional
注解了,前言中提到,需要实现Condition
接口,并重写方法来自定义match
规则
首先,创建一个WindowsCondition
类
public class WindowsCondition implements Condition {
/**
* @param conditionContext:判断条件能使用的上下文环境
* @param annotatedTypeMetadata:注解所在位置的注释信息
* */
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
//获取ioc使用的beanFactory
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
//获取类加载器
ClassLoader classLoader = conditionContext.getClassLoader();
//获取当前环境信息
Environment environment = conditionContext.getEnvironment();
//获取bean定义的注册类
BeanDefinitionRegistry registry = conditionContext.getRegistry();
//获得当前系统名
String property = environment.getProperty("os.name");
//包含Windows则说明是windows系统,返回true
if (property.contains("Windows")){
return true;
}
return false;
}
}
matches
方法的两个参数的意思在注释中讲述了,值得一提的是,conditionContext
提供了多种方法,方便获取各种信息,也是SpringBoot
中 @ConditonalOnXX
注解多样扩展的基础
接着,创建LinuxCondition
类:
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
Environment environment = conditionContext.getEnvironment();
String property = environment.getProperty("os.name");
if (property.contains("Linux")){
return true;
}
return false;
}
}
接着就是使用这两个类了,此注解可以标注在方法上和类上
//或标记在类上
//@Conditional({WindowsCondition.class})
@Configuration
public class BeanConfig {
//只有一个类时,大括号可以省略
//如果WindowsCondition的实现方法返回true,则注入这个bean
@Conditional({WindowsCondition.class})
@Bean(name = "bill")
public Person person1(){
return new Person("Bill Gates",62);
}
//如果LinuxCondition的实现方法返回true,则注入这个bean
@Conditional({LinuxCondition.class})
@Bean("linus")
public Person person2(){
return new Person("Linus",48);
}
}
上面说到@Conditional
注解传入的是一个Class数组,存在多种条件类的情况,这个时候都为true才为true
⑥ @Import
导入其他配置类
这个注解有多个作用
① 快速声明一个bean
② 导入@Configuration
注解的配置类
③ 导入ImportSelector
的实现类
④ 导入ImportBeanDefinitionRegistrar
的实现类
快速声明一个bean
public class TestBean1 {
}
@Import({TestBean1.class})
@Configuration
public class AppConfig {
}
导入@Configuration
注解的配置类
在引入其他配置类时,可以不用再写@Configuration
注解。当然,写上也没问题。
@Configuration
@ComponentScan(basePackages = "com.itheima.spring")
@Import({ JdbcConfig.class})
public class SpringConfiguration {
}
@Configuration
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig{
}
导入ImportSelector
的实现类
ImportSelector
强调的是复用性,使用它需要创建一个类实现ImportSelector
接口,实现方法的返回值是字符串数组,也就是需要注入容器中的组件的全类名。id同样也是全类名
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
// 返回值就是导入到容器中的组件全类名
// AnnotationMetadata:当前标注@Import注解的类的所有注解信息
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] { "test.spring.ZhangSan", "test.spring.LiSi", "test.spring.WangWu" };
}
}
@Configuration
@Import({ MyImportSelector.class})
public class SpringConfiguration {
}
导入ImportBeanDefinitionRegistrar
的实现类
public class MyBeanDefinitionRegistrat implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//RootBeanDefinition指定bean的信息
BeanDefinition beanDefinition = new RootBeanDefinition(Person.class);
//把所有要注入的bean通过BeanDefinitionRegistry的registerBeanDefinition方法注入
registry.registerBeanDefinition("PERSON", beanDefinition);
}
}
@Configuration
@Import(MyBeanDefinitionRegistrat.class)
public class ImportConfig {
}
⑦ FactoryBean
工厂bean
除了上面使用@Bean
注解得到的bean以外,还可以是使用实现了org.springframework.beans.factory.FactoryBean
接口的Bean,那么在从BeanFactory
中根据定义的id获取bean的时候,获取的实际上是FactoryBean
接口中的getObject()
方法返回的对象
public class StudentFactoryBean implements FactoryBean<Student> {
// 返回一个Student对象 这个对象会添加到容器中去
public Student getObject() throws Exception {
System.out.println("StudentFactoryBean ... getObject ... ");
return new Student();
}
// 返回泛型的类型
public Class<?> getObjectType() {
return Student.class;
}
//是否是单例
// true bean是单例 容器中唯一
// false 多实例
public boolean isSingleton() {
return true;
}
}
@Configuration
public class FactoryBeanConfig {
@Bean
public StudentFactoryBean studentFactoryBean(){
return new StudentFactoryBean();
}
}
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(FactoryBeanConfig .class);
Student student = applicationContext.getBean("studentFactoryBean");//拿到的是Student
//如果想拿到studentFactoryBean 可以在参数前加上&
Student student = applicationContext.getBean("&studentFactoryBean");//拿到的是studentFactoryBean
2. 生命周期 bean创建-->初始化-->销毁
生命周期是通常是指一个bean在ioc 容器中的一系过程。
- bean被创建(某个类的构造方法被初始化)
- bean的初始化这个bean初始化方法(
init()\ afterPropertiesSet() \ @PostConstruct标注的方法
)被调用。 - bean的销毁这个bean销毁方法(
destroy() \ @PreDestroy标注的方法
)被调用
单实例bean
在容器启动的时候创建对象;对象创建完成,赋好值,调用初始化方法;在容器关闭的时候执行销毁方法
多实例bean
在每次获取的时候创建对象;对象创建完成,赋好值,调用初始化方法;容器不会管理这个bean,容器销毁的时候不会自动调用销毁方法
① 通过@bean指定initMethod
和 destroyMethod
public class Blue {
public Blue(){
System.out.println("Blue constructor ......");
}
public void init(){
System.out.println("init ing.....");
}
public void destroy(){
System.out.println("destroy");
}
}
public class MyConfing {
@Bean(initMethod = "init",destroyMethod = "destroy")
public Blue blue(){
return new Blue();
}
}
② bean通过实现 initlizingBean
,DisposableBean
接口重写相关方法
public class Green implements InitializingBean,DisposableBean{
public Green green(){
System.out.println("Green的构造方法执行。。。。。");
return new Green();
}
//销毁方法
@Override
public void destroy() throws Exception {
System.out.println("Green 的销毁方法执行。。。。");
}
//初始化方法
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Green的初始化方法执行。。。。。");
}
}
③ 使用jsr规范的注解指定生命周期的方法
public class Gray {
public Gray gray(){
System.out.println("Gray 的构造方法执行。。。");
return new Gray();
}
//初始化方法
@PostConstruct
public void init(){
System.out.println("Gray的初始化方法执行。。。");
}
//销毁方法
@PreDestroy
public void destroy(){
System.out.println("Gray的销毁方法执行");
}
}
3. 属性赋值
① @Value
- 基本数值,String
- 可以写SpEL; #{}
- 可以写${};取出配置文件【properties】中的值(在运行环境变量里面的值)
public class Book {
@Value("#{2.4+3}") //SpEL表达式
private double price;
@Value("ZSM") //基本值
private String writer;
}
② PropertySource
用于配置类,读取配置文件信息
配置文件:book.properties
name=mybook
配置类:
@Configuration
@PropertySource(value={"classpath:/book.properties"})
public class ValueConfig {
@Bean
public Book book(){
return new Book();
}
}
实体类
public class Book {
@Value("${name}") //读取配置文件中的值
private String name ;
}
4. 自动装配
① @Autowired
自动按照类型注入,当使用注解注入属性时,set方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错
所以当遇到两个相同类型的Bean但是没有一个和我要注入的id相同,就会报错
如果没有这个类型的Bean也会保存,可以使用@Autowired(required=false)
,没有则为null
也可以标注在方法上
② @Qualifier
作用: 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。
属性: value:指定注入bean 的 id
@Autowired
@Qualifier("beanid")
③ @Resource
作用: 直接按照 Bean 的 id 注入,它也只能注入其他 bean 类型。
属性: name:指定 bean 的 id
@Resource(name = "beanid")
5. 推荐使用构造器注入
5.1 构造器注入的好处
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state.
- 依赖不可变:final关键字
- 依赖不为空(省去了我们对其检查):当要实例化FooController的时候,由于自己实现了有参数的构造函数,所以不会调用默认构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1、有该类型的参数->传入,OK 。2:无该类型的参数->报错。所以保证不会为空
- 完全初始化的状态:这个可以跟上面的依赖不为空结合起来,向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法,这里不详细展开。)。所以返回来的都是初始化之后的状态
不推荐使用@Autowired进行Field注入的原因
不推荐使用@Autowired进行Field注入的原因
- 不能像构造器那样注入不可变的对象
- 依赖对外部不可见,外界可以看到构造器和setter,但无法看到私有字段,自然无法了解所需依赖
- 会导致组件与IoC容器紧耦合(这是最重要的原因,离开了IoC容器去使用组件,在注入依赖时就会十分困难)
- 导致单元测试也必须使用IoC容器,原因同上
- 依赖过多时不够明显,比如我需要10个依赖,用构造器注入就会显得庞大,这时候应该考虑一下此组件是不是违反了单一职责原则
5.2 使用构造器注入
@Controller
public class FooController {
private final FooService fooService;
@Autowired
public FooController(FooService fooService) {
this.fooService = fooService;
}
}
5.3 使用@RequiredArgsConstructor
配合构造器注入
在Spring 4.3 以后,如果我们的类中只有单个构造函数,那么Spring就会实现一个隐式的自动注入
@Service
@RequiredArgsConstructor
public class AServiceImpl implements AService {
private final BService bService;