控制反转(IoC)
控制反转是spring框架的两个核心理念之一。
因为SpingBoot是基于注解开发的Spring IoC,所以我们使用全注解的方式来了解Spring IoC技术。
IoC是一种通过描述来创建或者获取对象的技术。
什么是Bean
在Spring中,Bean是指交给SpringIoC容器管理的对象,一般来说,该对象是单例的。
IoC容器
基本功能:通过描述来管理Bean,包括发布和获取Bean;通过描述来完成Bean之间的依赖关系。在Spring中,他要求所有的IoC容器都需要实现接口BeanFactory,它是一个顶级接口。
我们了解一下BeanFactory的源码:
package org.springframework.beans.factory;
import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;
public interface BeanFactory {
String FACTORY_BEAN_PREFIX = "&";
Object getBean(String var1) throws BeansException;
<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);
boolean containsBean(String var1);
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;
String[] getAliases(String var1);
}
这段代码中,有多个getBean方法,这也是IoC容器最重要的方法之一,这里有按类型(by type)从容器中获取bean,有按名称(by name)获取bean的。这对我们理解后面的依赖注入(Depedency Injection,DI)是十分重要的。
isSingleton方法判断Bean是否在Spring IoC中为单例,默认情况下,Bean都是以单例存在的。也就是说getBean方法返回的是同一个对象。与isSingleton方法相反,isPrototype方法,如果它返回true,那么使用getBean方法获取对象的时候,Spring IoC会创建一个新的Bean返回给调用者。
由于BeanFactory的功能不够强大,Spring在BeanFactory的基础上,还设计了一个更加高级的接口ApplicationContext,它是BeanFactory的子接口之一,我们使用的大部分SpringIoc容器都是ApplicationContext接口的实现类。
一个基于注解的IoC容器-AnnotationConfigApplicationContext
为了学习Spring Boot装配和获取Bean,来看代码:
先创建一个User类
public class User {
private Long id;
private String name;
private int age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
然后写一个简单的配置文件
@Configuration
public class AppConfig {
@Bean(name = "user")
public User initUser() {
User user = new User();
user.setAge(12);
user.setId(100L);
user.setName("java");
return user;
}
}
最后通过AnnotationConfigApplicationContext来获取这个Bean
public class IoCTest {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
User user = ctx.getBean(User.class);
System.out.println(user.getAge());
System.out.println(user.getId());
System.out.println(user.getName());
}
}
控制台打印了
12
100
java
显然,通过这种方式,代码将AppConfig传递给了AnnotationConfigApplicationContext的构造方法,这样它就能读取配置,然后将配置里面的Bean装配到IoC容器中。
装配你的Bean
在Spring中,可以通过xml或者注解装配Bean到IoC容器中,而SpringBoot是基于注解的方式。
通过扫描装配你的Bean
如果使用@Bean注解装配多个Bean时,就会比较麻烦,Spring提供了扫描Bean到IoC容器中,@Component是表明哪个类被扫描到IoC容器中,@ComponentScan则是标明采用何种策略去扫描装配Bean。
这里我们改造一下之前的代码,将User类放到config包下:
package com.awa.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("user")
public class User {
@Value("10")
private Long id;
@Value("java")
private String name;
@Value("18")
private int age;
}
这里的注解@Component表明这个类将被Spring IoC容器装配,其中配置“user”将作为Bean的名称,如果不指定这个字符串,那么IoC容器会把类名称的第一个字母小写,其他不变作为Bean的名称。注解@Value则是指定具体值,是的SpringIoC给予对应属性注入对应值。为了让Spring IoC容器装配这个类,需要改造AppConfig,
package com.awa.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class AppConfig {
}
这里加入了@Configuration注解,它会扫描AppConfig当前类所在的包和子包,通常情况下,User类不应该放到config包下,所以使用@ComponentScan自定义扫描路径。首先看一下@ComponentScan的源码:
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;
@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,他们都需要一个注解@Filter去定义,他只有一个type类型。
修改AppConfig中的注解为
@ComponentScan(“com.awa.*”)或@ComponentScan(basePackages = {“com.awa.pojo”})或@ComponentScan(basePackageClasses = {User.class})。
有时候我们只需要包里面的一部分类被装配,而不是整个包里的所有类。例如现在有个UserService类,为了将它标注为服务类,需要注解@Service,该标准注入了@Component,
package org.springframework.stereotype;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
所以在默认情况下,它也会被Spring扫描到IoC容器里。
于是针对上面的配置可以采用注解@ComponentScan(basePackages = “com.awa.*”,excludeFilters = {@ComponentScan.Filter(classes = Service.class)}),
这样,使用@Service注解的类不会被装配到IoC容器里。