点个关注吧
如果对你有帮助,给博主一个免费的点赞
博客主页 秋日的晚霞
⭐️ 更多文章 请关注主页
一起走上java学习之路!
欢迎各位点赞评论收藏⭐️
IOC ( Inversion of Control ) 是Spring的控制反转 容器 也被称为 DI (依赖注入) ,负责实例化、配置和组装 Bean。容器通过读取配置元数据来获取有关要实例化、配置和组装哪些对象的说明
控制反转 : 控制了 Bean的生命周期 反转了 对象的依赖获取的方式
IOC容器管理着一个或者多个Bean 这些Bean是使用开发者提供给容器的元数据创建的 . 在容器中 Bean的定义信息用一个对象来描述 这个对象 就是 BeanDefinition
常使用的有两种 基于类路径下的spring配置文件和基于注解开发的spring核心配置类
在web环境下,通常不需要使用者显性的去创建Spring容器,springmvc通过监听器监听servlet容器,当容器启动时,创建spring容器
contextConfigLocation
spring.xml
springmvc
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
/WEB-INF/springmvc.xml
1
springmvc
/
org.springframework.web.context.ContextLoaderListener
显性创建容器的例子如下
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
我们可以通过容器对象获取一个bean
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List userList = service.getUsernameList();
BeanDefinition 的属性
/**
* Constant for the default scope name: {@code ""}, equivalent to singleton
* status unless overridden from a parent bean definition (if applicable).
* Bean默认的scope
*/
public static final String SCOPE_DEFAULT = "";
/**
* Constant that indicates no external autowiring at all.
* @see #setAutowireMode
* Bean的注入模式:默认的Bean注入模式,使用这种注入模式时,在依赖其他Bean时,需要使用注解@Autowired进行Bean的注入
*/
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
/**
* Constant that indicates autowiring bean properties by name.
* @see #setAutowireMode
* Bean的注入模式:按照Bean的名称进行注入
*/
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
/**
* Constant that indicates autowiring bean properties by type.
* @see #setAutowireMode
* Bean的注入模式:按照Bean的类型进行注入
*/
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;
/**
* Constant that indicates autowiring a constructor.
* @see #setAutowireMode
* Bean的注入模式:按照构造函数进行注入
*/
public static final int AUTOWIRE_CONSTRUCTOR = AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR;
/**
* Constant that indicates determining an appropriate autowire strategy
* through introspection of the bean class.
* @see #setAutowireMode
* @deprecated as of Spring 3.0: If you are using mixed autowiring strategies,
* use annotation-based autowiring for clearer demarcation of autowiring needs.
*/
@Deprecated
public static final int AUTOWIRE_AUTODETECT = AutowireCapableBeanFactory.AUTOWIRE_AUTODETECT;
/**
* Spring在进行自动装配时,使用的依赖检查模式。分为四种
* 自动装配时不进行依赖检查
*/
public static final int DEPENDENCY_CHECK_NONE = 0;
/**
* 自动装配时只对依赖的对象类型做检查
*/
public static final int DEPENDENCY_CHECK_OBJECTS = 1;
/**
* 自动装配时只对原始的简单类型做检查(包括:集合类,String,基本类型)
*/
public static final int DEPENDENCY_CHECK_SIMPLE = 2;
/**
* 自动装配时对所有的类型都做检查,包括: 对象类型和简单类型.
*/
public static final int DEPENDENCY_CHECK_ALL = 3;
/**
* Constant that indicates the container should attempt to infer the
* {@link #setDestroyMethodName destroy method name} for a bean as opposed to
* explicit specification of a method name. The value {@value} is specifically
* designed to include characters otherwise illegal in a method name, ensuring
* no possibility of collisions with legitimately named methods having the same
* name.
* Currently, the method names detected during destroy method inference
* are "close" and "shutdown", if present on the specific bean class.
*/
public static final String INFER_METHOD = "(inferred)";
/**
* Bean对应的class路径
*
* 使用volatile保证了beanClass的可见性和有序性。
*/
@Nullable
private volatile Object beanClass;
/**
* bean对应的作用域
* prototype
* singleton
* request
* session
*/
@Nullable
private String scope = SCOPE_DEFAULT;
/**
* 类是否为抽象类
*/
private boolean abstractFlag = false;
/**
* 是否懒加载
*/
private boolean lazyInit = false;
/**
* 自动注入模型0. 0表示不支持外部注入
*/
private int autowireMode = AUTOWIRE_NO;
/**
* 默认的依赖检查模式,默认是不检查依赖。
*/
private int dependencyCheck = DEPENDENCY_CHECK_NONE;
/**
* 用来控制Bean的初始化顺序。
* 比如Bean A依赖Bean B,而Bean B中需要初始化一些缓存,初始化缓存的时候又使用了单例模式,没有注入到Spring容器中。而A中需要使用该缓存,这个时候需要指定B应该先被初始化。
* 可以再Bean A中声明该属性,表示依赖的Bean,这样Spring在初始化A的时候,会先去初始化B。防止因为B未初始化而导致使用出现未知问题
*/
@Nullable
private String[] dependsOn;
/**
* 该变量表示Spring在依赖注入时,默认注入所有声明的Bean
*
* 有一种情况,需要设置某些Bean不参与注入:
* 当某一个接口有多个实现类时,这些实现类中如果有某些bean不想参与自动注入,可以通过在bean的定义中声明该属性的值为false,表示不参与自动注入.
*/
private boolean autowireCandidate = true;
/**
* 当发现多个相同的Bean,比如一个接口有多个实现类,多个实现类对应的Bean就属于重复的Bean,这个时候直接使用会抛出bean重复定义异常。
* 需要在某一个Bean上面指定为primary,表示如果发现多个重复的Bean,会优先加载.
*/
private boolean primary = false;
/**
* 用来保存Qualifiers,对应Bean的属性Qualifier
* 此处这个字段目前【永远】不会被赋值(除非我们手动调用对应方法为其赋值)
*
* 注意:该属性的值并不是使用 @Qualifier("a") 注解进行的赋值.
*/
private final Map qualifiers = new LinkedHashMap<>();
/**
* 通过这个函数的逻辑初始化Bean
* 而不是构造函数或是工厂方法(相当于自己去实例化,而不是交给Bean工厂)
*/
@Nullable
private Supplier> instanceSupplier;
/**
* 允许访问非public的构造器和方法
*/
private boolean nonPublicAccessAllowed = true;
/**
* 调用Bean的构造方法时,是否使用宽松匹配模式
*/
private boolean lenientConstructorResolution = true;
/**
* 工厂Bean的名称,工厂Bean即用来创建Bean的工厂对应的Bean
*/
@Nullable
private String factoryBeanName;
/**
* 工厂方法的名称
*/
@Nullable
private String factoryMethodName;
/**
* 用来控制Spring在通过构造参数实例化对象时,调用哪个构造函数。会根据构造器的参数类型去适配合适的构造函数.
*/
@Nullable
private ConstructorArgumentValues constructorArgumentValues;
/**
* 表示的是可变的属性值。key-value类型,表示这些属性值在被赋值给Bean之前是可以修改的.
*/
@Nullable
private MutablePropertyValues propertyValues;
/**
* 记录哪些方法被覆写了
*/
private MethodOverrides methodOverrides = new MethodOverrides();
/**
* 初始化方法的名称 init-method指定的方法名称
*/
@Nullable
private String initMethodName;
/**
* 销毁Bean方法的名称,destroy-method指定的方法名称
*/
@Nullable
private String destroyMethodName;
/**
* 是否执行init-method方法
*/
private boolean enforceInitMethod = true;
/**
* 是否执行destroy-method方法
*/
private boolean enforceDestroyMethod = true;
/**
* 是否是合成类(是不是应用自定义的,例如生成AOP代理时,会用到某些辅助类,这些辅助类不是应用自定义的,这个就是合成类)
* 创建AOP时候为true
*/
private boolean synthetic = false;
/**
* Bean的角色,初始化时为ROLE_APPLICATION,表示默认为应用bean
* Spring中还存在着一些比较特殊的Bean,比如:基础设施Bean、支持Bean
*/
private int role = BeanDefinition.ROLE_APPLICATION;
/**
* Bean定义的描述信息。可以写一些中文描述
*/
@Nullable
private String description;
/**
* Bean定义的资源.
*/
@Nullable
private Resource resource;
BeanDefinition 只是一个Bean创建时的 设计图纸 具体实例化 还是需要容器来做 IOC 容器实例化一个Bean对象有三种方式
在java中每个类都有一个默认的无参构造函数 (开发者未自己声明一个构造函数时 ) 所以通常你只需要提供 类的全限定类名
列如一下例子基于 XML 实例化一个 Bean
如果是一个带参的构造方法 那么你需要向 IOC容器 提供 需要的 材料
例如定义一个类
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
使用静态工厂实例化一个Bean时,你需要指定 工厂方法所在的类 以及工厂方法名
例如
实例工厂方法与静态工厂方法类似,不同的是你需要先实例化工厂类 在指定工厂Bean的BeanName与工厂方法
如下定义个工厂类
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
配置xml
一个工厂类可以包含多个工厂方法
例如
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
上述类 只有一个 带参的构造方法 因此实例化需要提供 一个 MovieFinder
如果构造方法需要的参数不存在潜在的多义性 无需显式指定参数的类型 但是如果有多意性 则需要 指定类型 或者 使用索引 甚至是 参数名
如下面的例子
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
上述 ExampleBean 类实例化需要 两个参数 一个 为 int类型的 years 一个是 String 类型的 ultimateAnswer
如果是指定类型 你需要这样配置
如果使用索引 也可以这样
最后是使用参数名
但使用参数名你必须先使用 @ConstructorProperties显式命名构造函数参数。
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于Setter的依赖注入是指在调用无参构造函数或者工厂方法实例化一个Bean后在Bean上调用Setter完成依赖注入
例如定义一个如下的SimpleMovieLister类
注意 setMovieFinder 不是无参构造方法
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
注意一点 : 构造函数循环依赖 Spring 无法解决
类 A 需要通过构造函数注入的类 B 的实例,而类 B 需要通过构造函数注入的类 A 的实例。如果将类 A 和类 B 的 Bean 配置为要相互注入,则 Spring IoC 容器会在运行时检测到此循环引用,并抛出 .BeanCurrentlyInCreationException
如果 A 实例化 B ,Spring IOC容器将在调用Setter 方法之前 完成实例化 B
或者使用P 命名空间
class="org.springframework.aop.framework.ProxyFactoryBean">
内置Bean不需要定义ID 或者 名称 及时自动也不会生效 因为 内部Bean 是匿名的 容器在创建BeanDifition时会忽略该标志 因为内部Bean始终是匿名的,并且始终是依赖于外部Bean创建的 不能独立创建
[email protected]
[email protected]
[email protected]
just some string
Spring容器支持 合并集合 让 子类 继承 父类的的属性 如果设置同一个值 子类将覆盖父类设置的值
子集合的值是合并父集合和子集合的元素的结果,子集合元素将覆盖父集合中指定的值
[email protected]
[email protected]
[email protected]
[email protected]
上述例子中 子类bean child 的属性将为
[email protected]
[email protected]
[email protected]
等效于 JAVA 代码
exampleBean.setEmail("");
等效于JAVA代码
exampleBean.setEmail(null);
在xml中 可以设置 lazy-init 的属性 如下
当lazy-init 为true时 仅在被需要时初始化
注意: 如果一个懒加载的 Bean 被其他 非懒加载的Bean 依赖 则 在非懒加载的Bean 初始化时 会先完成初始化
也就是说 懒加载将失效
Scope | Description |
---|---|
singleton | 默认值 将单个 Bean 定义限定为每个 Spring IoC 容器的单个对象实例。 |
prototype | 将单个 Bean 定义的作用域限定为任意数量的对象实例。 |
request | 将单个 Bean 定义的作用域限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有自己的 Bean 实例 |
session | 将单个 Bean 定义限定为 HTTP 的生命周期 |
application | 将单个 Bean 定义的作用域限定为 application 的生命周期 |
websocket | 将单个 Bean 定义的作用域限定为 websocket 的生命周期 |
Spring 自对 单例Bean的生命周期进行管理 对于单例Bean 当需要获取的bean 的ID 与单例Bean定义的ID一致时 无论获取多少次 都是同一个Bean对象
也就是说 单例Bean初始化后 会放在 单例缓存池中 SingletonObjects ( ConcurrentHashMap ) 获取的对象都是缓存中的Bean对象
对于原型Bean来说 Spring不会对其进行管理 仅仅负责创建 与 单例Bean不同的是不会对其进行缓存
每次向容器中获取一个原型Bean时,它都是一个刚刚创建的全新Bean对象
接口
org.springframework.beans.factory.InitializingBean
需要重写的方法
void afterPropertiesSet() throws Exception;
该方法将在Bean的初始化方法执行前执行
接口
org.springframework.beans.factory.DisposableBean
需要重写的方法
void destroy() throws Exception;
该方法将在Bean销毁的时候被调用
@PostConstruct
afterPropertiesSet()
回调接口定义的InitializingBean
init()
@PreDestroy
destroy()
回调接口定义的DisposableBean
destroy()
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
获取应用上下文对象,来获取其他的bean 常用来做Spring容器工具类
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
在属性填充后,初始化执行前执行 能获取实现了该接口的Bean在容器中的name
名字 | 注入的依赖项 |
---|---|
ApplicationContextAware |
获取ApplicationContext |
ApplicationEventPublisherAware |
所附事件的事件发布者 。ApplicationContext |
BeanClassLoaderAware |
用于装入 Bean 类的类装载器。 |
BeanFactoryAware |
获取BeanFactory |
BeanNameAware |
获取Bean的名称。 |
LoadTimeWeaverAware |
定义了用于在加载时处理类定义的编织器。 |
MessageSourceAware |
配置了解析消息的策略(支持参数化和国际化)。 |
NotificationPublisherAware |
spring JMX 通知发布者。 |
ResourceLoaderAware |
配置了加载程序以对资源进行低级访问。 |
ServletConfigAware |
当前容器在其中运行。仅在网络感知的spring 有效。ServletConfig``ApplicationContext |
ServletContextAware |
当前容器在其中运行。仅在网络感知的spring 有效。ServletContext``ApplicationContext |
BeanPostProcessor
Bean后置处理器
该扩展点有两个抽象方法
public Object postProcessBeforeInitialization(Object bean, String beanName)
public Object postProcessAfterInitialization(Object bean, String beanName)
postProcessBeforeInitialization 在初始化方法执行前生效
postProcessAfterInitialization 在初始化方法执行后生效
注意 : 当实现了该接口的类注册为后置处理器后 对容器中的所有Bean生效
后置处理器可以对Bean实例进行任何操作 Spring的AOP就是通过后置处理器实现的 提供代理包装逻辑
BeanFactoryPostProcessor
BeanFactory后置处理器
该扩展点有一个抽象方法
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
可以拿到Bean工厂对象 然后通过BeanName 拿到 BeanDefinition对象 对其元数据进行修改
例如类名替换 PropertySourcesPlaceholderConfigurer 和属性重写 PropertyOverrideConfigurer
两者本质上都是 BeanFactoryPostProcessor
BeanFactoryPostProcessor 的 执行时机 是 创建 beanFactory 工厂 ,生成BeanDefition 对象 之后 创建Bean之前
而创建bean又是通过 BeanDefition 所以我们可以通过 BeanFactoryPostProcessor 扩展点 实现对 Bean属性的修改
如果你需要往容器中添加一个特别复杂的bean对象 适合用JAVA去创建这个对象 你可以通过实现这个接口,重写 getObject() 返回这个对象
FactoryBean 有三个抽象方法
T getObject()
:返回此工厂创建的对象的实例。实例可以共享,具体取决于此工厂返回的是单例还是原型。boolean isSingleton()
:是否返回单例 默认为 true 也就是返回单例bean 如果是false则为原型Class> getObjectType()
:返回方法返回的对象类型,按类型自动装配Bean 如果有两个 按名字找 如果名字不能确定唯一 抛出异常
如果一个没有 也将抛出异常
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
注意 : Spring 从 4.3开始 如果目标Bean只有一个构造函数 则无需对构造函数进行注释 但是如果有多个构造函数,且没有指定主或者默认的构造函数,则必须使用 @Autowired 注释其中一个构造函数,以便告知容器使用哪一个构造函数创建bean
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
注意: 如果 @Autowired 在容器中找不到对应的属性Bean 将抛出异常 你可以设置 required = false 来避免这个问题
例如下面的例子 在容器中找不到需要的对象时将不填充属性,保持它的默认值 不会抛出异常
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
当使用 @Autowired 注解自动装配Bean 而容器中有两个同类型的bean 且名字都不匹配时, 将抛出异常
而使用@Primary 能很好的解决这个问题 被 @Primary 注释的Bean优先级更高
例如
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
此时将自动装配 firstMovieCatalog() 方法中返回的Bean 因为使用了 @Primary 注解 优先选择此 Bean
同样是容器中有两个相同类型的bean对象的场景 且不能通过名字确定唯一的时候 除了@Primary注解外
还可以使用 @Qualifier 注解 指定要自动装配的容器中beanName匹配的Bean
例如
配置了两个相同类型的bean 但 一个名字为 main 一个名字为 action
使用@@Qualifier() 就能解决自动装配时容器中有两个同类型的bean且名字不能确定唯一的问题
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
@Resource
与 @Autowired不同的是 @Resource注解默认是 byName 而 @Autowired 默认是 ByType
例子如下
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果未指定name的值 将默认使用方法的bean属性名 或者 字段名称 作为name
如下 name属性值为 movieFinder
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Value
@Value
通常用于注入外部属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
创建一个配置类,使用@PropertySource注解引入外部资源 application.properties
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
创建一个配置文件application.properties
catalog.name=MovieCatalog
在这种情况下,catalog 参数的值将等于 MovieCatalog
Spring提供的内置转换器支持允许自动处理简单的类型转换 例如 多个逗号分隔的值可以自动转换为数组
如果想自定义类型转换器 可以往容器中添加 ConversionService接口实现类 对象
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
public interface ConversionService {
// 判断能否进行类型转换
boolean canConvert(@Nullable Class> sourceType, Class> targetType);
boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
// 类型转换,获取合适的转换器进行类型的转换,默认是DefaultConversionService,也可以是自定义的
@Nullable
T convert(@Nullable Object source, Class targetType);
@Nullable
Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
}
@PostConstruct
与 @PreDestroy
可以使用上述两个注解来回调bean的声明周期中的实例化后方法 @PostConstruct 和 销毁前方法 @PreDestroy
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}