3、基于注解的IoC装配与依赖注入
一、XML开启注解支持
1、添加context名称空间
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"></beans>
2、开启注解支持
1、在xml配置中,开启对Spring IoC核心注解的支持有两种方式:
使用
<context:annotation-config />
标签使用
<context:component-scan base-package="" />
标签
3、annotation-config
1、它的作用是隐式地向IoC容器中注册以下4个BeanPostProcessor,不同的BeanPostProcessor用于检测、处理各自对应的的注解服务,相当于注解处理器,不同的Spring版本有不同的支持,详细请参考官网。
AutowiredAnnotationBeanPostProcessor
:用来识别处理@Autowired
和@Value
注解。
CommonAnnotationBeanPostProcessor
:用来识别处理@Resource
、@PostConstruct
、@PreDestroy
等注解。
PersistenceAnnotationBeanPostProcessor
:用来识别和处理@PersistenceContext
注解。
RequiredAnnotationBeanPostProcessor
:用来识别和处理@Required
的注解。
4、component-scan
1、它具有annotation-config的全部功能,它必须指定一个
base-package
属性,表示会自动扫描指定的包路径下面的所有类,并将带有注册注解的Bean注册到IoC容器中。2、默认情况下自动将带有
@Component、@Repository、@Service、@Controller、@RestController、@ControllerAdvice 和@Configuration
等注解的对象注册到IoC容器中。3、
当使用context:component-scan后,可以将context:annotation-config配置移除
。4、
base-package
属性说明:
如果要配置扫描多个包路径,可以使用
' '、','、';'
等符号分割。也可以使用通配符
*
,例如:com.itan.*表示扫描com.itan包路径下的所有的类及所有子包中的类。
<context:annotation-config /><context:component-scan base-package="com.itan.annotation.*" />
二、@Configuration注解
1、概述
1、
@Configuration
是一个类级别的注解,用于定义一个类为配置类,可替换bean.xml配置文件注册Bean对象。2、被注解标注的类内部包含一个或多个
@Bean
注解方法,可以被AnnotationConfigApplicationContext
或者AnnotationConfigWebApplicationContext
进行扫描。用于构建Bean定义以及初始化Spring容器。3、
@Configuation等价于<beans></beans>标签
。4、
@Configuration标注的配置类,将会通过cglib生成代理对象,因此要求配置类不能是final的类
。5、注解内部属性说明:
由于有
@Component
注解的加持,那么被声明的配置类本身也是一个组件,可以通过context进行获取。
value
属性用于声明配置类的名称。
proxyBeanMethods
属性用于声明是否是代理对象方法
2、基础使用
1、和
@Configuration
注解一起搭配使用的三个常见注解:
@Bean
:等价于<bean />
标签,用于注册Bean对象,内部有一些初始化、销毁的属性。
@Scope
:用于声明该Bean的作用域,作用域有singleton、prototype、request、session、application、websocket
。
@Lazy
:是否开启懒加载。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Subject {private String name;private Teacher teacher;
}@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {private String name;private Integer age;private String sex;
}
/*** @Date: 2022/11/19* bean配置类*/
@Configuration(value = "beanConfig")
public class BeanConfig {@Bean("teacher")public Teacher getTecher() {System.out.println("Teacher对象进行创建");return new Teacher("张三", 34, "男");}@Bean("subject")public Subject getSubject() {System.out.println("Subject对象进行创建");return new Subject("化学", getTecher());}
}
@Test
public void test1() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);String[] beanNames = context.getBeanDefinitionNames();for (String beanName : beanNames) {System.out.println(beanName);}System.out.println("============================");Teacher teacher = context.getBean("teacher", Teacher.class);System.out.println(teacher);Subject subject = context.getBean("subject", Subject.class);System.out.println(subject);Teacher teacher1 = context.getBean("teacher", Teacher.class);System.out.println("teacher与teacher1是否相等:" + (teacher == teacher1));
}
/*** 运行结果:* Teacher对象进行创建* Subject对象进行创建* 容器中的Bean的名称:org.springframework.context.annotation.internalConfigurationAnnotationProcessor* 容器中的Bean的名称:org.springframework.context.annotation.internalAutowiredAnnotationProcessor* 容器中的Bean的名称:org.springframework.context.annotation.internalCommonAnnotationProcessor* 容器中的Bean的名称:org.springframework.context.event.internalEventListenerProcessor* 容器中的Bean的名称:org.springframework.context.event.internalEventListenerFactory* 容器中的Bean的名称:beanConfig* 容器中的Bean的名称:teacher* 容器中的Bean的名称:subject* ============================* Teacher(name=张三, age=34, sex=男)* Subject(name=化学, teacher=Teacher(name=张三, age=34, sex=男))* teacher与teacher1是否相等:true*/
总结:
@Configuation标注的类本身也是一个组件,被注册到IoC容器中。
默认情况下是以
饿汉单例
的形式进行创建Bean。
3、proxyBeanMethods属性说明
1、两个概念:
Full全模式,Lite轻量级模式
;默认使用全模式,即proxyBeanMethods = true
。
Full
:当proxyBeanMethods参数设置为true时即为全模式,该模式下注入容器中的同一个组件无论被取出多少次都是同一个Bean实例,即单例对象,在该模式下Spring每次启动都会检查容器中是否存在该组件;该模式下,配置类会被代理,使用CGLIB代理
。
Lite
:当proxyBeanMethods参数设置为false时即为轻量级模式,该模式下注入容器中的同一个组件无论被取出多少次都是不同的Bean实例,即多实例对象,在该模式下Spring每次启动都会跳过检查容器中是否存在该组件
。2、全模式与轻量级模式如何选择:
当在你的同一个Configuration配置类中,注入到容器中的Bean实例之间有依赖关系时,建议使用Full全模式。
当在你的同一个Configuration配置类中,注入到容器中的bean实例之间没有依赖关系时,建议使用Lite轻量级模式,以提高Spring启动速度和性能。
4、使用全模式
/*** 修改bean配置类,并设置使用全模式*/
@Configuration(value = "beanConfig", proxyBeanMethods = true)
@Test
public void test2() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);BeanConfig beanConfig = context.getBean("beanConfig", BeanConfig.class);Subject subject = context.getBean(Subject.class);Subject subject1 = beanConfig.getSubject();System.out.println("subject与subject1是否相等:" + (subject == subject1));
}
/*** 运行结果:* Teacher对象进行创建* Subject对象进行创建* subject与subject1是否相等:true*/
5、使用轻量级模式
/*** 修改bean配置类,并设置使用轻量级模式*/
@Configuration(value = "beanConfig", proxyBeanMethods = false)
/*** 运行结果:由于subject中依赖teacher,可以看到teacher对象也每次进行重新实例化* Teacher对象进行创建* Subject对象进行创建* Teacher对象进行创建* Subject对象进行创建* Teacher对象进行创建* subject与subject1是否相等:false*/
三、@ComponentScan注解开启注解支持
1、概述
1、@ComponentScan注解主要用于扫描包上的注解将组件加载到IoC容器中,替代
<context:component-scan />
标签,简化XML配置文件。2、@ComponentScan还有一个父类@ComponentScans注解,用于存放多个@ComponentScan注解。
3、
@ComponentScan注解只能使用在有@Configuration配置注解的地方,因为component-scan标签必须存在于xml配置文件中;而在脱离xml使用时,需要使用带有在@Configuration配置注解上。并不局限与一定是 @Configuration也可以是@SpringBootConfiguration等
。4、主要用于扫描被
@Controller、@Service、@Repository、@Component等注解标注的类
。
2、常用内部属性
1、
value
:指定扫描哪些包下的组件注解。2、
basePackages
:同value属性作用相同。3、
basePackageClasses
:扫描指定的类,且该类有组件注解才能被扫描到。4、
useDefaultFilters
:过滤机制,常常搭配includeFilters与excludeFilters一起使用。是否启用自动检测使用@Component、@Repository、@Service、@Controller标注的类,默认为true。5、
includeFilters
:指定哪些类型有资格进行组件扫描,需要将useDefaultFilters设置为false,才能让includeFilters参数生效
。6、
excludeFilters
:指定哪些类型不符合组件扫描条件。7、
lazyInit
:由于包扫描是一次性的,无法单独配置哪个组件是否懒加载,因此提供该属性用于声明是否开启懒加载扫描的Bean。
3、扫描包
package com.itan.componentscan.controller;import org.springframework.stereotype.Controller;/*** @Date: 2022/11/20*/
@Controller
public class UserController {}
package com.itan.componentscan.service;import org.springframework.stereotype.Service;/*** @Date: 2022/11/20*/
@Service
public class UserService {}
package com.itan.componentscan.dao;import org.springframework.stereotype.Repository;/*** @Date: 2022/11/20*/
@Repository
public class UserDao {}
/*** @Date: 2022/11/20* bean配置类,可以一次性扫描多个包,也可以是单个包*/
@Configuration
@ComponentScan(value = {"com.itan.componentscan.controller","com.itan.componentscan.service","com.itan.componentscan.dao"
})
public class ComponentScanConfig {}
@Test
public void test1() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentScanConfig.class);String[] beanNames = context.getBeanDefinitionNames();for (String beanName : beanNames) {System.out.println("容器中的Bean的名称:" + beanName);}
}
/*** 运行结果:* 容器中的Bean的名称:componentScanConfig* 容器中的Bean的名称:userController* 容器中的Bean的名称:userService* 容器中的Bean的名称:userDao*/
4、扫描类
前提:
被扫描的类上必须有组件注册的注解,否则无法扫描到
。
@Configuration
@ComponentScan(basePackageClasses = {UserController.class,UserService.class,UserDao.class
})
public class ComponentScanConfig {}/*** 运行结果:* 容器中的Bean的名称:componentScanConfig* 容器中的Bean的名称:userController* 容器中的Bean的名称:userService* 容器中的Bean的名称:userDao*/
5、@Filter注解过滤
1、@ComponentScan注解中的includeFilters、excludeFilters属性用于指定组件的扫描与排除;不过更多的是使用excludeFilters排除掉不需要的组件。
2、@Filter注解的属性说明:
type
:要使用过滤器类型,默认为FilterType.ANNOTATION
。
value
:classes别名,数组类型。
classes
:同value。
pattern
:用于过滤器的模式,作为指定Class value的替代方法。3、过滤器类型取值:
FilterType.ANNOTATION
:按照注解类型排除或包含。
FilterType.ASSIGNABLE_TYPE
:按照类型,如果是接口,那么所有的实现类都会被排除或包含。
FilterType.ASPECTJ
:按照切面表达式排除或包含。
FilterType.REGEX
:按照正则表达式排除或包含。
FilterType.CUSTOM
:实现TypeFilter接口自定义排除或包含。
按注解类型进行排除:
/*** @Date: 2022/11/20* bean配置类,扫描com.itan.componentscan包下所有组件,并按照注解类型排除Controller类型的组件*/
@Configuration
@ComponentScan(value = "com.itan.componentscan",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
public class ComponentScanConfig {}/*** 运行结果:* 容器中的Bean的名称:componentScanConfig* 容器中的Bean的名称:userDao* 容器中的Bean的名称:userService*/
按照注解类型进行添加:
/*** @Date: 2022/11/20* bean配置类,扫描com.itan.componentscan包下所有组件,并按照注解类型只添加Controller类型的组件* 注:使用includeFilters前提是必须要将useDefaultFilters设置为false,否则就是扫描所有类型的组件*/
@Configuration
@ComponentScan(value = "com.itan.componentscan", useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = Controller.class)})
public class ComponentScanConfig {}/*** 运行结果:* 容器中的Bean的名称:componentScanConfig* 容器中的Bean的名称:userController*/
6、自定义TypeFilter指定过滤规则
1、完成自定义表达式规则需要实现
TypeFilter
接口,通过该类的方法可以拿到被一次扫描到的类信息、注解、路径、甚至还可以获取到该类的其他信息。
/*** @Date: 2022/11/20* 自定义扫描规则*/
public class CustomTypeFilter implements TypeFilter {/*** @param metadataReader :读取到到当前正在扫描的类的信息* @param metadataReaderFactory :可以获取到其他任何类的信息* @return* @throws IOException*/@Overridepublic boolean match(MetadataReader metadataReader,MetadataReaderFactory metadataReaderFactory) throws IOException {// 获取当前类的注解信息AnnotationMetadata metadata = metadataReader.getAnnotationMetadata();// 获取当前类的信息ClassMetadata classMetadata = metadataReader.getClassMetadata();// 获取当前类的路径Resource resource = metadataReader.getResource();// metadata和classMetadata变量又可以获取该类祖孙三代的很多信息System.out.println("经过自定义过滤器的类:" + classMetadata.getClassName());if (classMetadata.getClassName().contains("dao")) {System.out.println("被自定义过滤器放行的类:" + classMetadata.getClassName());// 放行所有全路径类名中包含dao的return true;}// 拒绝放行return false;}
}
/*** @Date: 2022/11/20* bean配置类,扫描com.itan.componentscan包下所有组件,并且使用自定义的过滤规,添加组件* 注:使用includeFilters前提是必须要将useDefaultFilters设置为false,否则就是扫描所有类型的组件*/
@Configuration
@ComponentScan(value = "com.itan.componentscan", useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.CUSTOM, value = CustomTypeFilter.class)}
)
public class ComponentScanConfig {}/*** 运行结果:* 经过自定义过滤器的类:com.itan.componentscan.controller.UserController* 经过自定义过滤器的类:com.itan.componentscan.dao.UserDao* 被自定义过滤器放行的类:com.itan.componentscan.dao.UserDao* 经过自定义过滤器的类:com.itan.componentscan.service.UserService* 容器中的Bean的名称:componentScanConfig* 容器中的Bean的名称:userDao*/
四、组件注册相关注解
1、@Bean注解
1、
@Bean
注解是方法级注解,等价于<bean />
标签,用于注册Bean对象,内部有一些初始化、销毁的属性等。2、内部属性说明:
value
:name属性的别名,在不需要其他属性时使用,也就是说value就是默认值。
name
:bean的名称,可以为多个;如果未指定,则bean的名称是标注@Bean
注解方法的方法名;如果指定了,方法的名称就会忽略;如果没有其他属性声明的话,bean的名称和别名可以通过value属性配置。
autowire
:设置自动注入类型(按名称或类型)注入依赖项,返回一个Autowire类型的枚举;
NO(AutowireCapableBeanFactory.AUTOWIRE_NO)
:默认值,不启用自动注入。
BY_NAME(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME)
:通过属性名称自动注入,根据set方法的“set”后面的字符串去匹配bean name,没有匹配到就不注入。
BY_TYPE(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE)
:通过setter方法的参数类型自动注入,如果有多个同类型的bean,则抛出异常。
initMethod
:设置初始化时调用的方法,调用时机:对象创建完成(构造方法执行完),并且属性注入值后,调用初始化方法
。
destroyMethod
:设置销毁时调用的方法,默认使用javaConfig配置的bean,如果存在close或者shutdown方法,则在bean销毁时会自动执行该方法,如果不想执行该方法,则设置destroyMethod=""来防止触发销毁方法。3、生命周期相关在《二、基于XML依赖注入详细配置》中详细说明了,请参考!
public class Car {public Car() {System.out.println("car constructor...");}private String brand;private String type;public void setBrand(String brand) {System.out.println("car setBrand...");this.brand = brand;}public void setType(String type) {System.out.println("car setType...");this.type = type;}public void init() {System.out.println("car init...");}public void detory() {System.out.println("car detory...");}@Overridepublic String toString() {return "Car{" +"brand='" + brand + '\'' +", type='" + type + '\'' +'}';}
}
/*** @Date: 2022/12/8* @Bean注解以及生命周期方法*/
@Configuration
public class AnnotationBeanLifecycleConfig {@Bean(value = "car", initMethod = "init", destroyMethod = "detory")public Car createCar() {Car car = new Car();car.setBrand("路虎");car.setType("四驱");return car;}
}
@Test
public void test1() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationBeanLifecycleConfig.class);// 获取容器中对象Car car = context.getBean("car", Car.class);System.out.println(car);// 销毁容器context.close();
}/*** 运行结果如下:* car constructor...* car setBrand...* car setType...* car init...* Car{brand='路虎', type='四驱'}* car detory...*/
2、@Component注解
1、@Component可以标注在类上,IoC容器启动时会自动在
component-scan
或@ComponentScan
注解配置包范围之中扫描被它标注的类,将它们注册到IoC容器中,这些类被称为“组件”,相当于XML中的<bean />
标签。2、在JavaWeb开发中,提供3个@Component注解衍生注解分别是:
@Controller、@Service、@Repository,默认都是单例的
。3、@Component泛指各种组件,就是说当我们的类不属于各种归类的时候,就可以使用@Component来标注这个类。
@Component
public class ComponentTest {public ComponentTest() {System.out.println("ComponentTest初始化");}
}
/*** @Date: 2022/11/20* bean配置类,扫描com.itan.component包下所有组件*/
@Configuration
@ComponentScan(value = "com.itan.component")
public class ComponentConfig {}
@Test
public void test2() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentConfig.class);String[] beanNames = context.getBeanDefinitionNames();for (String beanName : beanNames) {System.out.println("容器中的Bean的名称:" + beanName);}
}/*** 运行结果:* ComponentTest初始化* 容器中的Bean的名称:componentConfig* 容器中的Bean的名称:componentTest*/
3、@Scope注解
1、基于注解的组件的默认作用域都是
singleton(单例的)
,如果要指定不同的作用域,可以使用此注解。2、@Scope注解仅仅在一系列的Component组件注解标注的类和@Bean标注的工厂方法上有效果,它的作用域范围值与XML配置的作用域参数值一致,相当于
<bean scope="" />
,请参考XML配置中的scope属性。
/*** @Date: 2022/11/20* bean配置类*/
@Configuration
public class ScopeConfig {@Bean("teacher")@Scope("prototype")public Teacher getTecher() {System.out.println("Teacher对象进行创建");return new Teacher("张三", 34, "男");}
}
@Test
public void test3() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScopeConfig.class);Teacher teacher1 = context.getBean("teacher", Teacher.class);Teacher teacher2 = context.getBean("teacher", Teacher.class);System.out.println("teacher1与teacher2是否相等:" + (teacher1 == teacher2));
}/*** 运行结果:可以看到获取的时候才会进行初始化* Teacher对象进行创建* Teacher对象进行创建* teacher1与teacher2是否相等:false*/
4、@Lazy注解
1、@Lazy注解表示是否使用懒加载方式,相当于
<bean lazy-init="">
,默认值为true,表示延迟初始化,通过getBean获取的时候才会进行初始化。2、
@Lazy注解只对singleton(单例)的bean有效
。3、与@Component(@Repository、@Service、@Controller)、@Configuration注解连用时,类中的所有@Bean方法的bean默认延迟初始化,如果某些@Bean方法上存在@Lazy注解并且value=false,那么表示将会立即初始化该bean,如果该方法是静态方法,那么外部lazy的类不受影响,如果是非静态的,那么外部lazy的类也会跟着初始化。
@Configuration
public class ScopeConfig {@Lazy@Bean("teacher")public Teacher getTecher() {System.out.println("Teacher对象进行创建");return new Teacher("张三", 34, "男");}
}
@Test
public void test3() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ScopeConfig.class);Teacher teacher1 = context.getBean("teacher", Teacher.class);Teacher teacher2 = context.getBean("teacher", Teacher.class);System.out.println("teacher1与teacher2是否相等:" + (teacher1 == teacher2));
}/*** 运行结果:通过getBean获取的时候才会进行初始化* Teacher对象进行创建* teacher1与teacher2是否相等:true*/
5、@Conditional注解
1、@Conditional注解表示满足指定的条件,才向IoC容器中注册组件;如果不满足,则不注入。
2、标注位置:
类上:表示满足条件,这个类中的所有Bean才能注册。
方法上:表示满足条件,被标注的Bean才能注册。
3、它还有以下一些扩展注解:
注解 作用 @ConditionalOnBean
当容器里有指定Bean的条件下就注入 @ConditionalOnMissingBean
当容器里没有指定Bean的条件下就注入 @ConditionalOnSingleCandidate
当指定Bean在容器中只有一个,或者虽然有多个但是指定首选 Bean,则条件也将匹配 @ConditionalOnClass
当类路径下有指定类的条件下才匹配 @ConditionalOnMissingClass
当类路径下没有指定类的条件下才匹配 @ConditionalOnProperty
指定的属性是否有指定的值,如果有就匹配 @ConditionalOnResource
仅当指定的资源在类路径上时才匹配 @ConditionalOnExpression
基于SpEL表达式作为判断条件 @ConditionalOnJava
基于Java版本作为判断条件 @ConditionalOnJndi
在JNDI存在的条件下差在指定的位置 @ConditionalOnNotWebApplication
当前项目不是Web项目的条件下 @ConditionalOnWebApplication
当前项目是 Web项目的条件下 4、自定义条件实现需要实现
org.springframework.context.annotation.Condition
接口,该接口提供了一个boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)
方法,用于判断条件是否匹配,参数说明如下:
ConditionContext
:判断条件能使用的上下文(条件上下文环境)。
AnnotatedTypeMetadata
:标注@Conditional注解的注释信息。5、
ConditionContext
中的一些方法说明:
方法名 说明 ConfigurableListableBeanFactory getBeanFactory
获取IoC使用的Bean工厂(beanFactory) ClassLoader getClassLoader
获取类加载器 Environment getEnvironment
获取当前环境信息 BeanDefinitionRegistry getRegistry
获取Bean定义的注册类信息 ResourceLoader getResourceLoader
获取当前使用
/*** @Date: 2022/11/30* 条件注解bean配置类*/
@Configuration
public class ConditionalConfig {@Bean("linux")@Conditional(MacOSCondition.class)public Teacher linux() {return new Teacher("Linux", 34, "男");}@Bean("windows")@Conditional(WindowsCondition.class)public Teacher windows() {return new Teacher("Windows", 26, "女");}
}
/*** @Date: 2022/11/30* MacOS条件判断*/
public class MacOSCondition implements Condition {/*** 判断条件是否匹配* @param context 条件上下文* @param metadata 注释信息* @return*/@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {// 获取IoC使用的Bean工厂(beanFactory)ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 获取类加载器ClassLoader classLoader = context.getClassLoader();// 获取当前环境信息Environment environment = context.getEnvironment();// 获取Bean定义的注册类信息BeanDefinitionRegistry registry = context.getRegistry();// 获取操作系统信息String osName = environment.getProperty("os.name");System.out.println("操作系统为:" + osName);// 如果操作系统为Mac OS X,就将Bean注册到IoC容器中if (osName.equals("Mac OS X")) {return true;}return false;}
}
/*** @Date: 2022/11/30* Windows条件判断*/
public class WindowsCondition implements Condition {/*** 判断条件是否匹配* @param context 条件上下文* @param metadata 标注@Conditional注释的类或方法信息* @return*/@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {/*** 获取指定类型注解的属性,若不存在返回null* annotationName:为注释类型的完全限定类名*/Map<String, Object> attributes = metadata.getAnnotationAttributes("org.springframework.context.annotation.Bean");// 获取注解标注的方法名称String methodName = ((StandardMethodMetadata) metadata).getMethodName();System.out.println("方法名为:" + methodName);System.out.println("@Bean注解信息为:" + attributes);// 如果方法名为windows,就将Bean注册到IoC容器中if (methodName.equals("windows")) {return true;}return false;}
}
@Test
public void test1() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConditionalConfig.class);String[] definitionNames = context.getBeanDefinitionNames();for (String name : definitionNames) {System.out.println("bean名称:" + name);}
}
/*** 运行结果如下:* 操作系统为:Mac OS X* 方法名为:windows* @Bean注解信息为:{autowire=NO, autowireCandidate=true, destroyMethod=(inferred), initMethod=, name=[windows], value=[windows]}* bean名称:conditionalConfig* bean名称:linux* bean名称:windows*/
6、@Import注解
1、@Import注解通过快速导入的方式实现将Bean实例注册到IoC容器中,与在XML的配置中使用
<import>
标签一样。2、@Import在使用时可以声明在JAVA类上,或者作为元注解使用(即声明在其他注解上)。
3、
使用@Import注解导入的Bean默认的名称为类的完全限定名
/*** @Date: 2022/12/4* ImportBean测试Bean*/
public class ImportBean {}
/*** @Date: 2022/11/30* Import注解bean配置类*/
@Configuration
@Import(ImportBean.class)
public class ImportConfig {}
@Test
public void test2() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ImportConfig.class);String[] definitionNames = context.getBeanDefinitionNames();for (String name : definitionNames) {System.out.println("bean名称:" + name);}
}
/*** 运行结果如下:* bean名称:importConfig* bean名称:com.itan.imports.ImportBean*/
7、@Import注解扩展用法
1、通过@Import注解导入一个类,且该实现了
ImportSelector
接口,重写了selectImports
方法,该方法返回String类型的数组,数组中的类都会被注册到IoC容器中。2、通过@Import注解导入一个类,且该实现了
ImportBeanDefinitionRegistrar
接口,在重写方法registerBeanDefinitions
中,能够获取到BeanDefinitionRegistry
注册器,能手动向beanDefinitionMap中注册bean
。
/*** @Date: 2022/11/30* 自定义返回需要导入的组件*/
public class MyImportSelector implements ImportSelector {/*** 获取要导入到容器中的组件的全类名* @param importingClassMetadata 当前@Import注解的类的所有注解信息* @return*/@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {return new String[]{"com.itan.imports.ImportBean", Bed.class.getName()};}
}@Configuration
// @Import({ImportBean.class})
@Import({ImportBean.class, MyImportSelector.class})
public class ImportConfig {}/*** test2方法运行结果如下:* bean名称:importConfig* bean名称:com.itan.imports.ImportBean* bean名称:com.itan.beans.Bed*/
/*** @Date: 2022/11/30* 自定义导入bean注册器*/
public class MyImportDefintionRegistrar implements ImportBeanDefinitionRegistrar {/**** @param importingClassMetadata 当前@Import注解的类的所有注解信息* @param registry BeanDefinition注册器,可以对未定义的bean进行注册*/@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {// 获取是否存在beanboolean exists = registry.containsBeanDefinition("people");System.out.println("注册之前是否存在people:" + exists);// 不存在手动注册if (!exists) {RootBeanDefinition beanDefinition = new RootBeanDefinition(People.class);// 可以指定bean的名称registry.registerBeanDefinition("people", beanDefinition);}exists = registry.containsBeanDefinition("people");System.out.println("注册之后是否存在people:" + exists);}
}@Configuration
// @Import({ImportBean.class})
@Import({ImportBean.class, MyImportSelector.class, MyImportDefintionRegistrar.class})
public class ImportConfig {}/*** test2方法运行结果如下:* 注册之前是否存在people:false* 注册之后是否存在people:true* bean名称:importConfig* bean名称:com.itan.imports.ImportBean* bean名称:com.itan.beans.Bed* bean名称:people*/
8、@DependsOn注解
1、@DependsOn注解可以指定当前Bean所依赖的其他Bean,也就是说被依赖的Bean会比当前Bean先注册到IoC容器中;等同于XML配置中的
depends-on
属性。2、@DependsOn既可以指定初始化的依赖顺序,也可以指定销毁顺序(倒序执行销毁,也就是当前Bean销毁之后,其依赖的Bean倒序执行销毁);
只针对作用范围为singleton的Bean才有效,对于作用范围为prototype的Bean也会按照顺序进行初始化,但不会执行销毁回调
。3、作用范围:
可以定义在类和方法上。
可以定义在带有@Component注解及其衍生注解的类上。
可以定义在带有@Bean注解的方法上。
@Scope("singleton")
@Component
@DependsOn({"dependC","dependB"})
public class DependA {public DependA() {System.out.println("DependA构造方法");}@PostConstructpublic void init() throws Exception {System.out.println("DependA执行初始化回调方法");}@PreDestroypublic void destroy() throws Exception {System.out.println("DependA执行销毁回调方法");}
}@Scope("singleton")
@Component
public class DependB {public DependB() {System.out.println("DependB构造方法");}@PostConstructpublic void init() throws Exception {System.out.println("DependB执行初始化回调方法");}@PreDestroypublic void destroy() throws Exception {System.out.println("DependB执行销毁回调方法");}
}@Scope("singleton")
@Component
public class DependC {public DependC() {System.out.println("DependC构造方法");}@PostConstructpublic void init() throws Exception {System.out.println("DependC执行初始化回调方法");}@PreDestroypublic void destroy() throws Exception {System.out.println("DependC执行销毁回调方法");}
}
/*** @Date: 2022/11/30* DependOn配置类*/
@Configuration
@ComponentScan("com.itan.componentscan")
public class DependOnConfig {}
@Test
public void test1() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);// 销毁容器context.close();
}/*** 运行结果如下:作用范围为singleton,可以看到初始化时优先初始化DependC,容器销毁时优先执行DependA的销毁回调* DependC构造方法* DependC执行初始化回调方法* DependB构造方法* DependB执行初始化回调方法* DependA构造方法* DependA执行初始化回调方法* DependA执行销毁回调方法* DependB执行销毁回调方法* DependC执行销毁回调方法*//*** 运行结果如下:若将Bean作用范围改为prototype,可以看到初始化时依然保证顺序执行初始化* DependC构造方法* DependC执行初始化回调方法* DependB构造方法* DependB执行初始化回调方法* DependA构造方法* DependA执行初始化回调方法*/
五、属性赋值相关注解
1、@Value注解
1、
@Value
注解可以用于字面量值的直接注入,或者获取外部配置文件(properties/yml)中的属性,也可以使用SPEL的语法,等价于XML配置中的value属性和标签。2、作用范围:
可以用在属性、方法(普通方法、@Bean标注方法都行)。
可以用在参数(普通方法、@Bean方法、构造方法)中的参数上。
用在方法上时,表示对方法参数都注入相同的值;用在方法中的具体参数上,表示对具体参数注入指定值
。
2、注入字面量值
1、
@Value
可以直接注入基本类型、包装类型、字符串类型
等字面量值,甚至可以通过Spring提供的内置转换器自动处理简单的类型转换(转换为数组、集合等复杂结构)。2、也可以通过SPEL操作字符串方法(如:大小写转换,截取、拼接等)。
3、基本类型:
byte、boolean、char、double、float、int、long、short
,对于boolean类型的可以使用true或false,也可以使用0或1。4、包装类型:
Byte、Boolean、Character、Double、Float、Integer、Long、Short
5、字符串类型:
String、CharSequence
/*** @Date: 2022/12/10* @value注解实体类*/
@Component("value")
public class ValueStr {/*基本类型*/@Value("1")private byte b;/*包装类型*/@Value("1")private Integer i;/*字符串类型*/@Value("我是字符串")private String str;/*整型基本类型的数组,使用","会自动拆分*/@Value("1,2,3,4")private byte[] bytes;@Value("1,2,3,4")private int[] ints;@Value("1,2,3,4")private long[] longs;@Value("1,2,3,4")private short[] shorts;/*SPEL表达式,#{'要分割的数据'.进行的操作}*/@Value("#{'1,0,1,1'.split(',')}")private char[] chars;@Value("#{'1,0,1,1'.split(',')}")private float[] floats;@Value("#{'1,0,1,1'.split(',')}")private double[] doubles;@Value("#{'1,0,1,1'.split(',')}")private boolean[] booleans;/*SPEL表达式,调用String类方法*/@Value("#{'abc'.toUpperCase()}")private String upStr;/*SPEL表达式进行计算*/@Value("#{25-2}")private int age;/*转集合类型*/@Value("#{'张三,李四,王五,张三'.split(',')}")private List<String> strList;// set类型会自动去重@Value("#{'张三,李四,王五,张三'.split(',')}")private Set<String> strSet;// Map类型 #{mapJsonValue}类似于JSON格式,会自动去重@Value("#{{'k1':100, 'k2':200}}")private Map<String, Integer> map;/*用在方法上,表示对方法上的所有参数注入相同值*/private int count;private int sum;@Value("200")private void setField(int count, int sum) {this.count = count;this.sum = sum;}/*用在方法的参数上,表示对指定的参数注入指定的值*/private String sex;public ValueStr(@Value("男") String sex) {this.sex = sex;}@Overridepublic String toString() {return "ValueStr{" +"b=" + b +", i=" + i +", str='" + str + '\'' +", bytes=" + Arrays.toString(bytes) +", ints=" + Arrays.toString(ints) +", longs=" + Arrays.toString(longs) +", shorts=" + Arrays.toString(shorts) +", chars=" + Arrays.toString(chars) +", floats=" + Arrays.toString(floats) +", doubles=" + Arrays.toString(doubles) +", booleans=" + Arrays.toString(booleans) +", upStr='" + upStr + '\'' +", age=" + age +", strList=" + strList +", strSet=" + strSet +", map=" + map +", count=" + count +", sum=" + sum +", sex='" + sex + '\'' +'}';}
}
/*** @Date: 2022/12/10* @value注解使用的配置*/
@Configuration
@ComponentScan("com.itan.beans")
public class AnnotationValueConfig {}
@Test
public void test1() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationValueConfig.class);ValueStr value = context.getBean("value", ValueStr.class);System.out.println(value.toString());
}/*** 运行结果:* ValueStr{b=1, i=1, str='我是字符串', * bytes=[49, 44, 50, 44, 51, 44, 52], * ints=[1, 2, 3, 4], * longs=[1, 2, 3, 4], * shorts=[1, 2, 3, 4], * chars=[1, 0, 1, 1], * floats=[1.0, 0.0, 1.0, 1.0], * doubles=[1.0, 0.0, 1.0, 1.0], * booleans=[true, false, true, true], * upStr='ABC', age=23, * strList=[张三, 李四, 王五, 张三], * strSet=[张三, 李四, 王五], * map={k1=100, k2=200}, * count=200, sum=200, sex='男'* }*/
3、注入外部配置文件中的属性
1、
@Value
可以获取外部properties或yml文件中的属性并注入:
注入一般属性的格式:
${key}
,如果配置文件中没有该key,则${key}本身作为值尝试注入;如果@Value引用到key不存在,也可以设置默认值,格式为${key:defaultValue},若只写:则表示注入空字符串
。注入单value集合的格式:
#{'${key}'.split(',')}
,可以使用String类提供的一些方法。注入Map集合的格式就类似JSON格式。
2、
注入外部配置文件中的属性的前提是必须将配置文件加载到当前应用程序运行的环境中
,可以通过XML和注解两种方式将文件加载到运行的环境中。3、基于XML方式将文件加载到运行环境中:
导入context名称空间:
xmlns:context="http://www.springframework.org/schema/context"
使用标签
<context:property-placeholder location="classpath:配置文件名称" />
4、基于注解方式将文件加载到运行环境中:
使用
@PropertySource
注解将文件加载到运行环境中。
@PropertySources
注解可以聚合多个@PropertySource
注解。
# 在resource目录下新建一个spring.properties配置文件b=1
i=1
str=我是字符串
# 指定分隔符号,然后使用split拆分即可
bytes=1,2,3,4
ints=1,2,3,4
longs=1,2,3,4
shorts=1,2,3,4
chars=1,0,1,1
floats=1,0,1,1
doubles=1,0,1,1
booleans=1,0,1,1
upStr=abcd
strList=张三,李四,王五,张三
strSet=张三,李四,王五,张三
map={'k1':100, 'k2':200}
/*** @Date: 2022/12/10* @value注解实体类*/
@Component("value1")
public class ValueStr1 {/*基本类型*/@Value("${b}")private byte b;/*包装类型*/@Value("${i}")private Integer i;/*字符串类型*/@Value("${str}")private String str;/*整型基本类型的数组,使用","会自动拆分*/@Value("#{'${bytes}'.split(',')}")private byte[] bytes;@Value("#{'${ints}'.split(',')}")private int[] ints;@Value("#{'${longs}'.split(',')}")private long[] longs;@Value("#{'${shorts}'.split(',')}")private short[] shorts;/*SPEL表达式,#{'要分割的数据'.进行的操作}*/@Value("#{'${chars}'.split(',')}")private char[] chars;@Value("#{'${floats}'.split(',')}")private float[] floats;@Value("#{'${doubles}'.split(',')}")private double[] doubles;@Value("#{'${booleans}'.split(',')}")private boolean[] booleans;/*SPEL表达式,调用String类方法*/@Value("#{'${upStr}'.toUpperCase()}")private String upStr;/*SPEL表达式进行计算*/@Value("#{${b} + ${i}}")private int age;/*转集合类型*/@Value("#{'${strList}'.split(',')}")private List<String> strList;// set类型会自动去重@Value("#{'${strSet}'.split(',')}")private Set<String> strSet;// Map类型 #{mapJsonValue}类似于JSON格式,会自动去重@Value("#{${map}}")private Map<String, Integer> map;// 默认值@Value("${defaultValue:我是默认值}")private String defaultValue;@Overridepublic String toString() {return "ValueStr1{" +"b=" + b +", i=" + i +", str='" + str + '\'' +", bytes=" + Arrays.toString(bytes) +", ints=" + Arrays.toString(ints) +", longs=" + Arrays.toString(longs) +", shorts=" + Arrays.toString(shorts) +", chars=" + Arrays.toString(chars) +", floats=" + Arrays.toString(floats) +", doubles=" + Arrays.toString(doubles) +", booleans=" + Arrays.toString(booleans) +", upStr='" + upStr + '\'' +", age=" + age +", strList=" + strList +", strSet=" + strSet +", map=" + map +", defaultValue='" + defaultValue + '\'' +'}';}
}
/*** @Date: 2022/12/10* @value注解使用的配置*/
@Configuration
@ComponentScan("com.itan.beans")
// 加载配置文件,并且设置字符编码
@PropertySource(value="classpath:spring.properties", encoding="UTF-8")
public class AnnotationValueConfig {}
@Test
public void test2() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationValueConfig.class);ValueStr1 value = context.getBean("value1", ValueStr1.class);System.out.println(value.toString());
}/*** 运行结果:* ValueStr{* b=1, i=1, str='我是字符串',* bytes=[1, 2, 3, 4],* ints=[1, 2, 3, 4],* longs=[1, 2, 3, 4],* shorts=[1, 2, 3, 4],* chars=[1, 0, 1, 1],* floats=[1.0, 0.0, 1.0, 1.0],* doubles=[1.0, 0.0, 1.0, 1.0],* booleans=[true, false, true, true],* upStr='ABCD', age=2,* strList=[张三, 李四, 王五, 张三],* strSet=[张三, 李四, 王五],* map={k1=100, k2=200},* defaultValue='我是默认值'* }*/
4、注入bean的属性
1、
@Value
可以获取其他Bean的属性值然后注入到自己的属性中。2、注意:
要求尝试获取属性的bean的属性有getter方法或者被public修饰
。
/*** @Date: 2022/12/10* @value注解注入其他bean属性值,* 要求获取属性bean的属性被public修饰,或者提供getter方法*/
@Component("value2")
public class ValueStr2 {/*基本类型*/@Value("#{value1.b}")private byte b;/*包装类型*/@Value("#{value1.i}")private Integer i;/*字符串类型*/@Value("#{value1.str}")private String str;/*数组*/@Value("#{value1.bytes}")private byte[] bytes;/*Map集合*/@Value("#{value1.map}")private Map<String, Integer> map;/*数组/集合某个索引/key的数据*/@Value("#{value1.strList[0]}")private String name;@Value("#{value1.map[k2]}")private Integer score;@Value("#{value1.booleans[1]}")private boolean flag;@Overridepublic String toString() {return "ValueStr2{" +"b=" + b +", i=" + i +", str='" + str + '\'' +", bytes=" + Arrays.toString(bytes) +", map=" + map +", name='" + name + '\'' +", score=" + score +", flag=" + flag +'}';}
}
@Test
public void test3() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AnnotationValueConfig.class);ValueStr2 value = context.getBean("value2", ValueStr2.class);System.out.println(value.toString());
}/*** 运行结果:* ValueStr2{* b=1, i=1, str='我是字符串',* bytes=[1, 2, 3, 4],* map={k1=100, k2=200},* name='张三', score=200, flag=false* }*/
六、自动注入相关注解
1、@Autowired依赖自动注入
1、
@Autowired
是Spring提供的一个依赖自动注入的注解,在基于XML的依赖注入详细配置文章中可以了解到自动注入,它是使用AutowiredAnnotationBeanPostProcessor
处理器来解析的。2、相比于XML配置的优势:XML的自动注入都是通过构造器或者setter方法实现,还存在某些限制;而
@Autowired
注解使用更加灵活方便。3、
@Autowired要求其所在的类被注册到IoC容器中
,bean注册的注解有@Controller、@Service、@Repository、@Component、@Bean
。4、作用范围:可以作用在
构造器、方法、属性、参数
上。5、
@Autowired的自动注入规则
:
默认优先按照
参数或属性对应的类型
在容器中查找对应类型的Bean(通过ioc.getBean(xxx.class)方式
),类型可以向下兼容。如果找到一个匹配的Bean,就将该Bean实例注入到对应的参数或属性中,没有则抛出异常。
如果找到多个相同类型的Bean,再将属性或参数的名称作为Bean的name在容器中查找(
通过ioc.getBean("xxx")方式
)。如果找到使用参数名或属性名作为name的Bean,就将该Bean实例注入进去到对应类型的参数或属性中;如果没有找到,就会抛出异常。
2、@Autowired标注在构造器上
1、
标注在构造器上时,表示对该构造器的所有参数都从IoC容器中查找匹配的Bean实例注入进去
。2、
查找的类型就是参数对应的类型,查找的名称就是参数名
。3、构造器上使用@Autowired注解自动注入的规则:
若一个Bean存在多个构造器,那么只能有一个构造器上使用@Autowired注解并设置required为true或只使用@Autowired注解(默认required=true),那么容器将会只使用该构造器进行依赖注入
。
若将@Autowired注解并设置required为false,那么可以在多个构造器上使用该注解,表示有多个构造器可以作为“候选构造器”。Spring会选择能够满足最多、最匹配参数的依赖项的构造器作为依赖注入的构造器
。
如果Spring最终选择其中一个候选构造器作为依赖注入的构造器,必须满足该构造器上的参数全部都能成功注入。如果所有的候选构造器中没有一个能满足构造器上的参数全部都能成功注入,那么会直接调用默认的无参构造器,所有的依赖项都不会注入
。
如果一个Bean存在多个构造器,但没有一个构造器标注@Autowired注解,则将使用无参构造器,这样的话就不会进行构造器依赖注入
。
如果一个Bean仅仅只有一个构造器,Spring将默认使用这个构造器进行依赖注入,即使没有标注@Autowired注解
。
@Autowired注解标注的构造器不必是public修饰的
。
/*** @Date: 2022/12/11* Autowired标注在构造上*/
@Component
public class AutowiredConstructor {private UserService userService;private UserDao userDao;@Autowiredpublic AutowiredConstructor(UserService userService, UserDao userDao) {this.userService = userService;this.userDao = userDao;}@Overridepublic String toString() {return "AutowiredConstructor{" +"userService=" + userService +", userDao=" + userDao +'}';}
}
/*** @Date: 2022/12/11* Autowired配置类*/
@Configuration
@ComponentScan({"com.itan.componentscan"})
public class AutowiredConfig {}
@Test
public void test1() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);AutowiredConstructor value = context.getBean(AutowiredConstructor.class);System.out.println(value.toString());
}/*** 运行结果如下:* Creating shared instance of singleton bean 'autowiredConfig'* Creating shared instance of singleton bean 'autowiredConstructor'* Creating shared instance of singleton bean 'userService'* Creating shared instance of singleton bean 'userDao'* Autowiring by type from bean name 'autowiredConstructor' via constructor to bean named 'userService'* Autowiring by type from bean name 'autowiredConstructor' via constructor to bean named 'userDao'* Creating shared instance of singleton bean 'userController'* AutowiredConstructor{* userService=com.itan.componentscan.service.UserService@3012646b,* userDao=com.itan.componentscan.dao.UserDao@4a883b15* }*/
1、扩展1:
如果将@Autowired注解注释掉,所依赖的对象照样被注入进来了,参考上面构造器注入规则的第5条。
2、扩展2:
@Autowired注解中包含一个required属性,默认值为true;为false就表示非必须的依赖,如果找不到依赖项也不会报错
。如果将required设置为false,并且将UserService上的@Service注解注释掉,启动程序测试,发现还是会报错。
No qualifying bean of type 'com.itan.componentscan.service.UserService' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
解决上面问题,添加无参构造器,或者添加其他参数的构造器即可。参考上面构造器注入规则的第2、3、4条。
3、构造器依赖项不能注入问题的解决方式
1、根据构造器自动注入的规则发现,即使设置了
required=false
,只要有一个依赖项不能注入,那么该构造器就不会使用。因此在构建对象时必须将可能的构造器参数组合都列出来,全部作为候选构造器,才能实现有就注入,没有就不注入
的功能,这样代码就非常臃肿,参数一多还非常麻烦。2、解决方式:
可以通过JDK8提供的
Optional
类来实现,防止空指针。可以使用
Spring5.0提供的@Nullable注解
或者JSR-305中的@Nullable注解
标注到参数字段上。
/*** @Date: 2022/12/11* 使用Optional实现有就注入,没有就不注入功能*/
@Component
public class AutowiredConstructorOptional {private UserService userService;private UserDao userDao;// 如果一个Bean仅仅只有一个构造器,Spring将默认使用这个构造器进行依赖注入,即使没有标注@Autowired注解// @Autowiredpublic AutowiredConstructorOptional(Optional<UserService> optionalUserService, Optional<UserDao> optionalUserDao) {optionalUserService.ifPresent(userService -> this.userService = userService);optionalUserDao.ifPresent(userDao -> this.userDao = userDao);}@Overridepublic String toString() {return "AutowiredConstructor{" +"userService=" + userService +", userDao=" + userDao +'}';}
}@Test
public void test2() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);AutowiredConstructorOptional value = context.getBean(AutowiredConstructorOptional.class);System.out.println(value.toString());
}/*** 运行结果如下:* AutowiredConstructor{userService=null, userDao=UserDao{label='@Repository注入的'}}*/
/*** @Date: 2022/12/11* 使用@Nullable注解实现有就注入,没有就不注入功能*/
@Component
public class AutowiredConstructorNullable {private UserService userService;private UserDao userDao;// 如果一个Bean仅仅只有一个构造器,Spring将默认使用这个构造器进行依赖注入,即使没有标注@Autowired注解// @Autowiredpublic AutowiredConstructorNullable(@Nullable UserService userService, @Nullable UserDao userDao) {this.userService = userService;this.userDao = userDao;}@Overridepublic String toString() {return "AutowiredConstructor{" +"userService=" + userService +", userDao=" + userDao +'}';}
}@Test
public void test3() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);AutowiredConstructorNullable value = context.getBean(AutowiredConstructorNullable.class);System.out.println(value.toString());
}/*** 运行结果如下:* AutowiredConstructor{userService=null, userDao=UserDao{label='@Repository注入的'}}*/
4、@Autowired标注在方法上
1、
标注在方法上时,表示对该方法的所有参数都从IoC容器中查找匹配的Bean实例注入进去
;与标注在构造器上差不多。2、
查找的类型就是参数对应的类型,查找的名称就是参数名
。
5、@Autowired标注在属性上
1、
标注在属性上时,表示该属性从IoC容器中查找匹配的Bean实例注入进去
;使用最多的一种方式。2、
查找的类型就是参数对应的类型,查找的名称就是参数名
。3、注意:
@Autowired是不能直接为静态属性注入值的,但是可以通过方法注入(构造方法或setter方法)或者使用@PostConstruct注解的方式在加载类的构造函数之后执行
。
/*** @Date: 2022/12/13* 为静态属性注入值*/
@Component
public class AutowiredStaticField {private static UserService userService;private static UserDao userDao;private static UserController userController;@Autowiredprivate AnnotationConfigApplicationContext context;// 标注在方法上@Autowiredpublic void setUserDao(UserDao userDao) {AutowiredStaticField.userDao = userDao;}// 标注在参数上public AutowiredStaticField(@Autowired UserService userService) {this.userService = userService;}// Bean创建完成并且属性注入值完成,执行初始化方法。@PostConstructpublic void init() {userController = context.getBean(UserController.class);}@Overridepublic String toString() {return "AutowiredConstructor{" +"userService=" + userService +", userDao=" + userDao +", userController=" + userController +'}';}
}
@Test
public void test4() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);AutowiredStaticField value = context.getBean(AutowiredStaticField.class);System.out.println(value.toString());
}/*** 运行结果如下:* AutowiredConstructor{userService=com.itan.componentscan.service.UserService@7d0b7e3c, * userDao=UserDao{label='@Repository注入的'}, * userController=com.itan.componentscan.controller.UserController@15bb5034}*/
6、@Autowired注入多个Bean
1、可以将注解标注在Bean对应类型的数组的字段或者方法上,就可以从IoC容器中获取到该Bean对应类型的所有Bean,且能向下兼容,同样也适用于集合(LIst、Map、Set…)。
2、对于有顺序的集合,还可以指定注入Bean的顺序,可以通过
@Order(XXX)注解设置依赖注入顺序,值越小,排序越靠前,@Order仅仅只能表示注入到集合的顺序
。3、
对于Map<String, XXX>集合,key为XXX类型的所有Bean的名字,value为实现类对象
。
// 定义一个接口
public interface UploadHandler {}// 实现类,并设置顺序
@Order(2)
@Component
public class ImgUpload implements UploadHandler{}// 实现类,并设置顺序
@Order(1)
@Component
public class PDFUpload implements UploadHandler {}
@Component
public class UploadFactory {private List<UploadHandler> uploadHandlerList;// 属性注入@Autowiredprivate Set<UploadHandler> uploadHandlerSet;@Autowiredprivate Map<String, UploadHandler> uploadHandlerMap;private UploadHandler[] uploadHandlers;// 方法注入@Autowiredpublic void setUploadHandlers(UploadHandler[] uploadHandlers) {this.uploadHandlers = uploadHandlers;}// 构造注入@Autowiredpublic UploadFactory(List<UploadHandler> uploadHandlerList) {this.uploadHandlerList = uploadHandlerList;}@Overridepublic String toString() {return "UploadFactory{" +"uploadHandlerList=" + uploadHandlerList +"\n, uploadHandlerSet=" + uploadHandlerSet +"\n, uploadHandlerMap=" + uploadHandlerMap +"\n, uploadHandlers=" + Arrays.toString(uploadHandlers) +'}';}
}
@Test
public void test5() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);UploadFactory factory = context.getBean(UploadFactory.class);System.out.println(factory.toString());
}/*** 运行结果如下:* 1、可以对比发现有序集合是按照@Order设置的顺序注入的。* 2、Map类型的key为实现类的名称,value是实现类对象* UploadFactory{uploadHandlerList=[com.itan.componentscan.service.PDFUpload@75d3a5e0, com.itan.componentscan.service.ImgUpload@2034b64c]* , uploadHandlerSet=[com.itan.componentscan.service.ImgUpload@2034b64c, com.itan.componentscan.service.PDFUpload@75d3a5e0]* , uploadHandlerMap={imgUpload=com.itan.componentscan.service.ImgUpload@2034b64c, PDFUpload=com.itan.componentscan.service.PDFUpload@75d3a5e0}* , uploadHandlers=[com.itan.componentscan.service.PDFUpload@75d3a5e0, com.itan.componentscan.service.ImgUpload@2034b64c]}*/
4、@Qualifier注解
1、@Autowired注解默认按照类型匹配注入Bean,如果存在多个类型兼容或者相同的Bean,搭配
@Qualifier
表明注入的是哪个实现类的Bean。2、
@Qualifier
注解可以设置一个value值,该value值表示一个Bean的name,强制指定需要注入的Bean
。
@Repository
public class UserDao {private String label = "@Repository注入的";public void setLabel(String label) {this.label = label;}@Overridepublic String toString() {return "UserDao{" +"label='" + label + '\'' +'}';}
}
@Configuration
@ComponentScan({"com.itan.componentscan"})
public class AutowiredConfig {// @Bean方式注入一个userDao,容器中存在两个相同类型的UserDao@Bean("userDao1")public UserDao userDao() {UserDao userDao = new UserDao();userDao.setLabel("使用@Bean注解注册的");return userDao;}
}
@Component
public class AutowiredConstructor {private UserService userService;private UserDao userDao;// 使用@Qualifier强制注入名称为userDao1的Bean@Autowired(required = false)public AutowiredConstructor(@Qualifier("userDao1") UserDao userDao) {this.userDao = userDao;}@Overridepublic String toString() {return "AutowiredConstructor{" +"userService=" + userService +", userDao=" + userDao +'}';}
}
@Test
public void test1() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AutowiredConfig.class);AutowiredConstructor value = context.getBean(AutowiredConstructor.class);System.out.println(value.toString());
}/*** 运行结果如下:* AutowiredConstructor{userService=null, userDao=UserDao{label='使用@Bean注解注册的'}}*/
5、@Primary注解
1、@Autowired在按照类型无法选择唯一的依赖注入时(比如存在多个类型兼容或者相同的Bean),可以使用@Primary注解。
2、
表示当有多个Bean是依赖项的候选对象时,应该优先考虑被@Primary标注的Bean
。
@Configuration
@ComponentScan({"com.itan.componentscan"})
public class AutowiredConfig {// @Bean方式注入一个userDao,容器中存在两个相同类型的UserDao,由于被@Primary标注,所以注入userDao1@Bean("userDao1")@Primarypublic UserDao userDao() {UserDao userDao = new UserDao();userDao.setLabel("使用@Bean注解注册的");return userDao;}
}
@Component
public class AutowiredConstructor {private UserService userService;private UserDao userDao;@Autowired(required = false)public AutowiredConstructor(UserDao userDao) {this.userDao = userDao;}@Overridepublic String toString() {return "AutowiredConstructor{" +"userService=" + userService +", userDao=" + userDao +'}';}
}/*** 运行结果如下:* AutowiredConstructor{userService=null, userDao=UserDao{label='使用@Bean注解注册的'}}*/
6、@Resource注解
1、
@Resource
注解类似@Autowired
注解,也可以实现依赖自动注入功能,它是JSR250提供的,位于javax.annotation.Resource。2、
@Resource注入规则
:
默认通过名称注入
,如果标注在字段上,且没有设置name属性,那么字段名就作为查找的beanName;如果标注在setter方法上,就将方法名set后面的部分作为查找的beanName。
如果没有设置name属性或者在IoC容器中没有找到同名的Bean,那么就通过类型注入
,如果没有找到该类型的Bean,或者找到多个(类型向下兼容),那么抛出异常。也可以指定name和type属性,表示只通过Bean name或者Bean type匹配依赖项,
如果两个属性都指定值,那么要求name和type都要匹配才行,type可以向上兼容
。3、注意:
@Resource注解只有标注在属性和单个参数的方法上才生效,多个参数则抛出异常:@Resource annotation requires a single-arg method。因此,如果注入目标是一个构造器或多参数方法,那么应该坚持使用@Autowired
。
@Resource也可以配合@Qualifier使用,但是@Resource的name优先级高于@Qualifier指定的值
。
@Resource标注的方法或者属性,默认表示依赖必须注入,并且没有requird选项,不支持null,但是支持Optional包装的null
。
/*** @Date: 2022/12/25* Resource注入Bean*/
@Component
public class ResourceBean {// 默认按照属性名注入,若没有找到userDao1,则按类型注入@Resourceprivate UserDao userDao1;// 按照beanName注入@Resource(name = "imgUpload")private UploadHandler uploadHandler1;// 按照beanType注入@Resource(type = PDFUpload.class)private UploadHandler uploadHandler2;// 按照beanName和beanType注入,必须两个都匹配才能注入@Resource(name = "txtUpload", type = TxtUpload.class)private UploadHandler uploadHandler3;@Overridepublic String toString() {return "ResourceBean{" +"userDao1=" + userDao1 +"\n, uploadHandler1=" + uploadHandler1 +"\n, uploadHandler2=" + uploadHandler2 +"\n, uploadHandler3=" + uploadHandler3 +'}';}
}
/*** @Date: 2022/12/25* resource注解配置类*/
@Configuration
@ComponentScan("com.itan.componentscan")
public class ResourceConfig {}
@Test
public void test1() {// 通过配置类创建容器对象AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ResourceConfig.class);ResourceBean bean = context.getBean(ResourceBean.class);System.out.println(bean);
}/*** 运行结果如下:* ResourceBean{userDao1=UserDao{label='@Repository注入的'}* , uploadHandler1=com.itan.componentscan.service.ImgUpload@8519cb4* , uploadHandler2=com.itan.componentscan.service.PDFUpload@35dab4eb* , uploadHandler3=com.itan.componentscan.service.TxtUpload@2d901eb0}*/
3、基于注解的IoC装配与依赖注入相关推荐
- java元婴期(18)----java进阶(spring(2)----DI(依赖注入)基于注解的IOC与DI配置properties)
1.依赖注入的概念 依赖注入:Dependency Injection.它是spring框架核心ioc的具体实现. 我们的程序在编写时,通过控制反转,把对象的创建交给了spring,但是代码中不可能出 ...
- Spring学习第6篇: 基于注解使用IOC
大家家好,我是一名网络怪咖,北漂五年.相信大家和我一样,都有一个大厂梦,作为一名资深Java选手,深知Spring重要性,现在普遍都使用SpringBoot来开发,面试的时候SpringBoot原理也 ...
- Spring基于注解的自动装配
Spring基于注解的自动装配 基于XML的自动装配是在配置文件的bean里设置autowire属性,有byType,byName的方式.而基于注解的自动装配同样是这样只不过我们直接在成员变量上直接标 ...
- 基于注解的 IOC 配置
基于注解的 IOC 配置 学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样 的,都是要降低程序间的耦合.只是配置的形式不一样. 关于实际的开发中 ...
- 基于注解的 IOC 配置——创建对象(Component、Controller、Service、Repository)注入数据(Autowired、Qualifier、Resource、Value)
基于注解的 IOC 配置 注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合.只是配置的形式不一样. XML的配置: 用于创建对象的 用于注入数据的 用于改变作用范围的 生命周期相 ...
- spring基于注解的IOC以及IoC的案例——概念
1.spring中ioc的常用注解 2.案例使用xml方式和注解方式实现单表的CRUD操作 持久层技术选择:dbutils 3.改造基于注解的ioc案例,使用纯注解的方式实现 spri ...
- java day58【 案例:使用 spring 的 IoC 的实现账户的 CRUD 、 基于注解的 IOC 配置 、 Spring 整合 Junit[掌握] 】...
第1章 案例:使用 spring 的 IoC 的实现账户的 CRUD 1.1 需求和技术要求 1.1.1 需求 1.1.2 技术要求 1.2 环境搭建 1.2.1 拷贝 jar 包 1.2.2 创建数 ...
- Spring Bean 装配和依赖注入
Spring IOC 容器的依赖注入工作可以分为两个阶段.第一个阶段可以认为是构建和收集 Bean 定义的阶段,在这个阶段中,我们可以通过 xml 或者 Java 代码的方式定义一些 Bean,然后通 ...
- spring源码分析第三天------spring核心IOC容器和依赖注入原理
基于XML的依赖注入 1.依赖注入发生的时间 当 Spring IOC 容器完成了 Bean 定义资源的定位.载入和解析注册以后,IOC 容器中已经管理类 Bean 定义的相关数据,但是此时 IOC ...
最新文章
- 报名 | 第三期医工结合系列研讨会:科研领域的知识产权保护
- 如何在Winform界面中设计图文并茂的界面
- Apache Flink 零基础入门【转】
- mac下用vs code编写c++程序
- 独家报道:众说纷纭 云安全究竟为何物?
- makefile运行_NVDIA TX2入门 系列之三:运行Yolov3
- 围观 Joomla, Wordpress 和 Drupal
- dom jaxp详解
- Linux驱动开发流程四步走
- python执行shellcode_python exec shellcode
- JS模拟实现数组的map方法
- Spark:通过start-slaves.sh脚本启动worker报错:Permission denied
- ajax上传图片 java_Java+ajax+图片上传
- 计算机在运行表格的时候很慢,win7系统打开excel2007表格很慢的解决方法
- linux tc工具,Linux TC软件系统损坏自助恢复的工具.TC刷机,TC修复,TCPUP,TCP-UP
- 邮箱客户端 gmail支持_如何在新的Gmail中启用离线支持
- 从“中国宙斯盾”亮相想到的
- android studio中的apk位置
- 网上发现的一个 《Flashflex大全》
- Word如何拆分单元格