一. 在java开发中要将一个java bean注入到spring上下文中总体有两种方式:
1. 通过xml配置方式,最初始的方式,就是在xml配置文件中通过<bean><bean/>标签的方式,在加载spring上下文时加载spring的配置文件即可将配置文件中的所有bean加载到spring上下文中:如下:
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<bean id="person" class="com.atguigu.bean.Person" scope="prototype" >
<property name="age" value="${}"></property>
<property name="name" value="zhangsan"></property>
</bean>
</beans>
2.通过注解方式,这种方式在springboot开始后使用尤为重要,从springboot之后spring开发都是基于注解。那么第一步必须要告知spring满足那些条件的bean是需要加载到spring上下文的,方式是定义一个java类,通过@Configuration告诉spring这是一个配置类,如下:
//配置类==配置文件
@Configuration //告诉Spring这是一个配置类
@ComponentScans(
value = {
@ComponentScan(value="com.atguigu",includeFilters = {
@Filter(type=FilterType.ANNOTATION,classes={Controller.class}),
@Filter(type=FilterType.ASSIGNABLE_TYPE,classes={BookService.class}),
@Filter(type=FilterType.CUSTOM,classes={MyTypeFilter.class})
},useDefaultFilters = false)
}
)
//@ComponentScan value:指定要扫描的包
//excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件
//includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件
//FilterType.ANNOTATION:按照注解
//FilterType.ASSIGNABLE_TYPE:按照给定的类型;
//FilterType.ASPECTJ:使用ASPECTJ表达式
//FilterType.REGEX:使用正则指定
//FilterType.CUSTOM:使用自定义规则
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
}
}
以上两种方式可以单独使用,也可以两种方式结合使用,结合使用形式在springboot流行之前是最方便、最常用的方式。
二. 下面对注解方式进行详细记录:
1. 包扫描+组件标注注解(@Controller/@Service/@Repository/@Component)【一般用于自己写的类】
@ComponentScan(value="com.atguigu")
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01(){
return new Person("lisi", 20);
}
}
包扫描规则配置:
1) excludeFilters = Filter[] :指定扫描的时候按照什么规则排除那些组件;
2) includeFilters = Filter[] :指定扫描的时候只需要包含哪些组件;
① FilterType.ANNOTATION:按照注解;
高于jdk8支持同时写多个@ComponentScan,如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
...
}
@ComponentScan 用法如下:
@Configuration //告诉Spring这是一个配置类
// 排除单个注解类型
@ComponentScan(value = "com.atguigu", excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = Controller.class)})
// 包含单个注解类型
@ComponentScan(value = "com.atguigu", includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = Controller.class)}, useDefaultFilters = false)
// 排除多个注解类型
@ComponentScan(value = "com.atguigu", excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class,Service.class})})
// 包含多个注解类型
@ComponentScan(value = "com.atguigu", excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class,Service.class})}, useDefaultFilters = false)
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01() {
return new Person("lisi", 20);
}
}
低于jdk8使用@ComponentScans,因为@ComponentScans源码实现内部包含的是@ComponentScan数组,实现如下:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface ComponentScans {
ComponentScan[] value();
}
@ComponentScans 用法如下:
@ComponentScans(value = {
// 排除单个注解类型
@ComponentScan(value = "com.atguigu", excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = Controller.class)})
// 包含单个注解类型
@ComponentScan(value = "com.atguigu", includeFilters = {@Filter(type = FilterType.ANNOTATION, classes = Controller.class)}, useDefaultFilters = false)
// 排除多个注解类型
@ComponentScan(value = "com.atguigu", excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class,Service.class})})
// 包含多个注解类型
@ComponentScan(value = "com.atguigu", excludeFilters = {@Filter(type = FilterType.ANNOTATION, classes = {Controller.class,Service.class})}, useDefaultFilters = false)
})
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01() {
return new Person("lisi", 20);
}
}
② FilterType.ASSIGNABLE_TYPE:按照给定的类型;
@Configuration //告诉Spring这是一个配置类
// 只有制定的UserService类型的才能被扫描到spring上下文中
@ComponentScan(value = "com.atguigu", includeFilters = {@Filter(type = FilterType.ASSIGNABLE_TYPE, classes = UserService.class)}, useDefaultFilters = false)
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01() {
return new Person("lisi", 20);
}
}
③ FilterType.ASPECTJ:使用ASPECTJ表达式;
④ FilterType.REGEX:使用正则指定;
⑤ FilterType.CUSTOM:使用自定义规则;FilterType.CUSTOM源码如下,从下面源码中可以得知,自定义的类型需要实现org.springframework.core.type.filter.TypeFilter接口
public enum FilterType {
...
/** Filter candidates using a given custom
* {@link org.springframework.core.type.filter.TypeFilter} implementation.
*/
CUSTOM
}
自定义过滤类型实现,如下:
public class MyTypeFilter implements TypeFilter {
/**
* metadataReader:读取到的当前正在扫描的类的信息
* metadataReaderFactory:可以获取到其他任何类信息的
*/
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
//获取当前类注解的信息
AnnotationMetadata annotationMetadata = metadataReader.getAnnotationMetadata();
//获取当前正在扫描的类的类信息
ClassMetadata classMetadata = metadataReader.getClassMetadata();
//获取当前类资源(类的路径)
Resource resource = metadataReader.getResource();
// 此处可自定义规则来过滤
String className = classMetadata.getClassName();
if (className.contains("er")) {
return true;
}
return false;
}
}
注入实现,如下:
@Configuration //告诉Spring这是一个配置类
@ComponentScan(value = "com.atguigu", includeFilters = {@Filter(type = FilterType.CUSTOM, classes = MyTypeFilter.class)}, useDefaultFilters = false)
public class MainConfig {
//给容器中注册一个Bean;类型为返回值的类型,id默认是用方法名作为id
@Bean("person")
public Person person01() {
return new Person("lisi", 20);
}
}
2. @Bean注解方式【一般导入的第三方包里面的组件】
@Configuration // 告知spring此类是一个配置类
public class RestTemplateWapperConfig {
@Bean
public RestTemplateWrapper restTemplateWrapper() {
// 在实践开发中此处可加入大量自定义业务逻辑处理以实现不同的业务场景
return new RestTemplateWrapper();
}
}
3. @Conditional 按照自定义条件判断,满足则注册bean
【springboot中有大量使用,重点】,源码如下:
/**
* A single {@code condition} that must be {@linkplain #matches matched} in order for a component to be registered.
*
* <p>Conditions are checked immediately before the bean-definition is due to be registered and are
* free to veto registration based on any criteria that can
* be determined at that point.
* 将在bean定义即将注册之前执行 boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata)进行检查,
* return true则进行注册初始化注入,否则不注册
*
* <p>Conditions must follow the same restrictions as {@link BeanFactoryPostProcessor} and take care to never interact with bean instances.
* For more fine-grained control of conditions that interact with {@code @Configuration} beans consider the {@link ConfigurationCondition} interface.
*
* @author Phillip Webb
* @since 4.0
* @see ConfigurationCondition
* @see Conditional
* @see ConditionContext
*/
public interface Condition {
/**
* Determine if the condition matches.
* @param context the condition context
* @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class}
* or {@link org.springframework.core.type.MethodMetadata method} being checked.
* @return {@code true} if the condition matches and the component can be registered
* or {@code false} to veto registration.
*/
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
自定义条件需要实现接口 Condition,并在matches方法中实现具体的条件业务逻辑
//判断是否linux系统
public class LinuxCondition implements Condition {
/**
* ConditionContext:判断条件能使用的上下文(环境)
* AnnotatedTypeMetadata:注释信息
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// TODO是否linux系统
//1、能获取到ioc使用的beanfactory
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//2、获取类加载器
ClassLoader classLoader = context.getClassLoader();
//3、获取当前环境信息
Environment environment = context.getEnvironment();
//4、获取到bean定义的注册类
BeanDefinitionRegistry registry = context.getRegistry();
String property = environment.getProperty("os.name");
//可以判断容器中的bean注册情况,也可以给容器中注册bean
boolean definition = registry.containsBeanDefinition("person");
if(property.contains("linux")){
return true;
}
return false;
}
}
//判断是否windows系统
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Environment environment = context.getEnvironment();
String property = environment.getProperty("os.name");
if(property.contains("Windows")){
return true;
}
return false;
}
}
注入实现方式
//类中组件统一设置。满足当前条件,这个类中配置的所有bean注册才能生效;
@Conditional({WindowsCondition.class})
@Configuration
public class MainConfig {
/**
* @Conditional({Condition}) : 按照一定的条件进行判断,满足条件给容器中注册bean
*
* 如果系统是windows,给容器中注册("bill")
* 如果是linux系统,给容器中注册("linus")
*/
@Conditional(WindowsCondition.class)
@Bean("bill")
public Person person01(){
return new Person("Bill Gates",62);
}
@Conditional(LinuxCondition.class)
@Bean("linus")
public Person person02(){
return new Person("linus", 48);
}
@Bean
public ColorFactoryBean colorFactoryBean(){
return new ColorFactoryBean();
}
}
4. @Import 注解方式【一般用于快速给容器中导入一个组件】
@Import 源码解读
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* {@link Configuration}, {@link ImportSelector}, {@link ImportBeanDefinitionRegistrar}
* or regular component classes to import.
*/
Class<?>[] value();
}
1)、@Import:(要导入到容器中的组件);容器中就会自动注册这个组件,id默认是全类名
@Configuration
@Import(Color.class)
//@Import导入组件,id默认是组件的类的全限定名:com.test.config.Color
public class MainConfig2 {
//其他注入方式、或业务处理逻辑方法
}
2)、ImportSelector:实现方式返回需要导入的组件的全类名数组;
从注释@link ImportSelector得知,自定义ImportSelector方式需实现ImportSelector接口,实现如下:
//自定义逻辑返回需要导入的组件
public class MyImportSelector implements ImportSelector {
//返回值,就是到导入到容器中的组件全类名
//AnnotationMetadata:当前标注@Import注解的类的所有注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//importingClassMetadata
// 注意:方法不要返回null值
return new String[]{"com.atguigu.bean.Blue","com.atguigu.bean.Yellow"};
}
}
注入实现,如下:
@Configuration
@Import({MyImportSelector.class,MyImportBeanDefinitionRegistrar.class})
//@Import导入组件,id默认是组件的全类名
public class MainConfig2 {
...
}
3)、ImportBeanDefinitionRegistrar:手动注册bean到容器中
从注释@link ImportBeanDefinitionRegistrar 得知,手动注册bean到容器中需实现ImportBeanDefinitionRegistrar接口,实现如下,注入方式如上ImportSelector方式:
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
/**
* AnnotationMetadata:当前类的注解信息
* BeanDefinitionRegistry:BeanDefinition 注册类;
* 把所有需要添加到容器中的bean;调用 BeanDefinitionRegistry.registerBeanDefinition手工注册进来
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean definition = registry.containsBeanDefinition("com.atguigu.bean.Red");
boolean definition2 = registry.containsBeanDefinition("com.atguigu.bean.Blue");
if (definition && definition2) {
//指定Bean定义信息;(Bean的类型,Bean。。。)
RootBeanDefinition beanDefinition = new RootBeanDefinition(RainBow.class);
//注册一个Bean,指定bean名
registry.registerBeanDefinition("rainBow", beanDefinition);
}
}
}
5. 使用Spring提供的 FactoryBean(工厂Bean);
1)、默认获取到的是工厂bean调用getObject创建的对象
2)、要获取工厂Bean本身,我们需要给id前面加一个“&”,即
Object bean = applicationContext.getBean("&colorFactoryBean");
自定义实现 FactoryBean<Color>,其中<Color>为需要返回的类型
//创建一个Spring定义的FactoryBean
public class ColorFactoryBean implements FactoryBean<Color> {
//返回一个Color对象,这个对象会添加到容器中
@Override
public Color getObject() throws Exception {
System.out.println("ColorFactoryBean...getObject...");
return new Color();
}
@Override
public Class<?> getObjectType() {
return Color.class;
}
//是单例?
//true:这个bean是单实例,在容器中保存一份
//false:多实例,每次获取都会创建一个新的bean;
@Override
public boolean isSingleton() {
return false;
}
}
三. Bean实例的作用域
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
/**
* Alias for {@link #scopeName}.
* @see #scopeName
*/
@AliasFor("scopeName")
String value() default "";
/**
* Specifies the name of the scope to use for the annotated component/bean.
* <p>Defaults to an empty string ({@code ""}) which implies
* {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
* @since 4.2
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
* @see #value
*/
@AliasFor("value")
String scopeName() default "";
/**
* Specifies whether a component should be configured as a scoped proxy
* and if so, whether the proxy should be interface-based or subclass-based.
* <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
* that no scoped proxy should be created unless a different default
* has been configured at the component-scan instruction level.
* <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
* @see ScopedProxyMode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
从上面@Scope源码得知Spring IOC容器创建一个Bean实例时,可以使用@Scope为Bean指定实例的作用域:
singleton(单例模式):IOC容器在初始化时默认仅会创建一个Bean实例,任何时候从容器获取的都是用一个实例;
prototype(原型模式):IOC容器在初始化时不会创建Bean实例,每次在获取时实时的创建bean实例;
request(HTTP请求):该属性仅对HTTP请求产生作用,使用时每次HTTP请求都会创建一个新的Bean,适用于当前WebApplicationContext环境;
session(会话):该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例;
global-session(全局会话):该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。
// 通过修改@Scope value属性修改bean的作用域
@Scope("prototype")
@Lazy
@Bean("person")
public Person person(){
System.out.println("给容器中添加Person....");
return new Person("张三", 25);
}
四. Bean实例加载
// 通过修改@Lazy标注当前类是采用懒加载方式
@Lazy
@Bean("person")
public Person person(){
System.out.println("给容器中添加Person....");
return new Person("张三", 25);
}