参考文章: blog.csdn.net/cy973071263…
IOC概念
浅谈IOC
粗略概括:控制反转就是把创建和管理 bean 的过程转移给了第三方。而这个第三方,就是 Spring IoC Container,对于 IoC 来说,最重要的就是容器。
容器负责创建、配置和管理 bean,也就是它管理着 bean 的生命,控制着 bean 的依赖注入。
工厂模式:因为项目中每次创建对象是很麻烦的,所以我们使用 Spring IoC 容器来管理这些对象,需要的时候你就直接用,不用管它是怎么来的、什么时候要销毁,只管用就好了。
Bean 其实就是包装了的 Object,无论是控制反转还是依赖注入,它们的主语都是 object,而 bean 就是由第三方包装好了的 object。
IOC的好处
- 不用自己组装,拿来就用。
- 享受单例的好处,效率高,不浪费空间。
- 便于单元测试,方便切换mock组件。
- 便于进行AOP操作,对于使用者是透明的。
- 统一配置,便于修改。
深入ioc
Spring IoC 就是通过 javabean的set() 方法注入的
- 控制反转,控制了什么?反转是什么? bean 的创建、管理的权利,控制 bean 的整个生命周期。 把这个控制的权利交给了 Spring 容器,而不是自己去控制,就是反转。由之前的自己主动创建对象,变成现在被动接收别人给我们的对象的过程,这就是反转。
ListableBeanFactory(获取多个bean的工厂),这个Listable的意思就是,通过这个接口,我们可以获取多个 Bean,大家看源码会发现,最顶层BeanFactory接口的方法都是获取单个Bean的。ApplicationContext继承了HierarchicalBeanFactory(多层bean工厂),Hierarchical单词本身已经能说明问题了,也就是说我们可以在应用中起多个BeanFactory,然后可以将各个BeanFactory设置为父子关系。AutowireCapableBeanFactory(自动装配能力的bean工厂)这个名字中的Autowire大家都非常熟悉,它就是用来自动装配Bean用的,但是仔细看上图,ApplicationContext并没有继承它,不过不用担心,不使用继承,不代表不可以使用组合,如果你看到ApplicationContext接口定义中的最后一个方法getAutowireCapableBeanFactory()就知道了。DefaultListableBeanFactory,在Spring中,实际上是把DefaultListableBeanFactory作为一个默认的功能完整的IoC容器来使用的。包含了基本IoC容器所具有的重要功能。
ApplicationContext
ApplicationContext在BeanFactory的基础上集成了MessageSource, ApplicationEventPublisher, ResourcePatternResolver这几个接口,这些接口为ApplicationContext提供了以下BeanFactory不具备的新特性:
- 支持不同的信息源。我们看到ApplicationContext扩展了
MessageSource接口,这些信息源的扩展功能可以支持国际化的实现,为开发多语言版本的应用提供服务。 - 访问资源。这一特性体现在对
ResourceLoader和Resource的支持上,这样我们可以从不同地方得到Bean定义资源。这种抽象使用户程序可以灵活地定义Bean定义信息,尤其是从不同的I/O途径得到Bean定义信息。 - 支持应用事件。继承了接口ApplicationEventPublisher,从而在上下文中引入了事件机制。这些事件和Bean的生命周期的结合为Bean的管理提供了便利。
- 在ApplicationContext中提供的附加服务。这些服务使得基本IoC容器的功能更丰富。因为具备了这些丰富的附加功能,使得ApplicationContext与简单的BeanFactory相比,对它的使用是一种面向框架的使用风格,所以一般建议在开发应用时使用ApplicationContext作为IoC容器的基本形式。
BeanDefinition
BeanDefinition抽象了我们对Bean的定义,是让容器起作用的主要数据类型。对IoC容器来说,BeanDefinition就是对依赖反转模式中管理的对象依赖关系的数据抽象,也是容器实现依赖反转功能的核心数据结构,依赖反转功能都是围绕对这个BeanDefinition的处理来完成的。
ClassPathXmlApplicationContext 的容器初始化我们大致分为下面几步:
-
BeanDefinition的Resource定位 这里的Resource定位 是通过继承ResourceLoader 获得的,ResourceLoader代表了加载资源的一种方式,正是策略模式的实现。 -
从 Resource中解析、载入BeanDefinition
-
BeanDefinition 在IoC 容器中的注册
dependency injection (依赖注入)
什么是依赖? 什么是注入?
依赖:程序运行需要依赖外部的资源,提供程序内对象的所需要的数据、资源。
配置文件把资源从外部注入到内部,容器加载了外部的文件、对象、数据,然后把这些资源注入给程序内的对象,维护了程序内外对象之间的依赖关系
当然,IoC 也可以通过其他的方式来实现,而 DI 只是 Spring 的选择。
IoC 和 DI 也并非 Spring 框架提出来的,Spring 只是应用了这个设计思想和理念到自己的框架里去。
总结一句话:Spring是使用控制反转(IOC)思想,依赖注入(DI)实现的
spring八大模块
每个「绿框」,对应一个模块,总共8个模块;「黑色包」,表示要实现这个模块的 jar 包。
Core 模块是 Spring 的核心,Spring 的所有功能都依赖于这个 jar 包,Core 主要是实现 IoC 功能,那么说白了 Spring 的所有功能都是借助于 IoC 实现的。
快速使用ioc
- maven导入:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.3.RELEASE</version>
</dependency>
- 创建一个javabean类,生成get,set方法
public class AmdBean {
private String mainboard;
private String Cpu;
- 创建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="amd" class="com.hyx.bean.AmdBean" >
<property name="cpu" value="5800x"></property>
<property name="mainboard" value="5700xt"></property>
</bean>
</beans>
- 执行文件进行验证
@Test
public void AmdTest() {
// 加载配置文件,这里使用的是类路径加载
ApplicationContext context=new ClassPathXmlApplicationContext("services.xml");
// 获取bean对象
AmdBean amd = context.getBean("amd",AmdBean.class);
System.out.println(amd.getCpu());
System.out.println(amd.getMainboard());
}
推荐配置方法
1. xml文件配置bean
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml");
使用ApplicationContext引入配置,它默认不支持注解注入bean,可于xml文件中打开这一配置
2. 注解配置
新建配置类,于类上写入注解,指明需要扫描的位置
// 将该类设置为一个配置类,该类就相当于一个XML
@Configurable
@ComponentScan("com.hyx.bean")
public class Config {
}
使用AnnotationConfigApplicationContext注入注解的配置,它默认支持注解扫描
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
推荐注册bean的方式
1. xml文件注册bean
<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="amd" class="com.hyx.bean.AmdBean" >
<property name="cpu" value="5800x"></property>
<!-- 使用ref注入的话,后面写的是已经在bean上注册的对应id名称的对象,就是ref=(另一个bean的id) -->
<property name="mainboard" value="5700xt"></property>
</bean>
<bean id="amdService" class="com.hyx.bean.AmdService">
<property name="amdBean" ref="amd"></property>
</bean>
</beans>
XML中还有一个depends-on 这个可以规定bean在容器中的创建顺序。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
通过depends-on配置,那么manager就会先于beanOne创建,但是这两个bean之间是没有依赖关系的。这种操作的应用场景就是如果beanOne的初始化需要用到manager的一些信息或者beanOne中的一些静态方法需要用到manager的信息,那么就得保证manager先于beanOne创建。但是这两个bean之间并没有依赖关系,所以只能使用depends-on的方法
2. 使用注解注册bean
@Component("amd")
public class AmdBean {
@Value("@5700xt")
private String mainboard;
@Value("@5800x")
private String Cpu;
}
自动装配
什么是bean的自动装配?
Spring容器能够自动装配相互合作的bean,这意味着容器不需要和配置,能通过Bean工厂自动处理bean之间的协作。
自动装配的方法:
no/defaultbyNamebyTypeconstructor
使用XML来进行自动装配
1. byType
通过类型来查找相应的依赖关系
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">
<bean id="dao" class="priv.cy.dao.IndexDaoImpl"></bean>
<bean id="service" class="priv.cy.dao.IndexService"></bean>
</beans>
spring会进行扫描,如果一个bean内有依赖的对象,比如上面例子中,假设IndexService内有对于IndexDaoImpl的依赖对象,spring在扫描IndexService时,会先去看看有没有对于IndexDaoImpl的bean注册,如果有的话直接注入。这就是按照类型进行自动装配
该方法有一个问题:
在有多个可以装配的对象存在时,spring会报错,因为他不知道应该装配哪一个
注意:使用自动装配的时候默认使用的是无参构造方法来创建对象,所以要确保所有的bean都有无参构造方法,要么不显示地写构造方法,要么显示写出无参构造方法
2. byName
通过指定bean的Name来进行自动装配,如果bean没有设定name,默认name为bean的id
package priv.cy.dao;
public class IndexService {
private IndexDao dao;
public void service() {
dao.test();
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
}
这种情况下装配的方式为:
spring扫描到要装配对象的set方法,如此处为setDao(),他会将set去掉,将剩下的字符首字符小写,此方法中为dao,它现在就会去扫描对应name值为dao的bean,注入进该bean
3. no和default
两个都是一个意义: 手动装配,如果没有手动装配会报空指针错误
4. constructor
构造函数自动装配:
public class IndexService {
private IndexDao dao;
public IndexService (IndexDao dao) {
this.dao = dao;
}
public void service() {
dao.test();
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
}
该装配需要实现构造函数,构造函数内传入的值就是要注入的bean,spring会去容器里寻找这个bean,如果未实现构造函数则报错
局部自动装配
<bean id="amdService" class="com.hyx.bean.AmdService" autowire="byName">
<property name="amdBean" ref="amd"></property>
</bean>
局部装配最常用的是与全局装配配合使用,局部配置的优先级要高于全局配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-autowire="byType">
<bean id="dao" class="priv.cy.dao.IndexDaoImpl"></bean>
<bean id="dao1" class="priv.cy.dao.IndexDaoImpl1"></bean>
<bean id="service" class="priv.cy.dao.IndexService" autowire="byName"></bean>
</beans>
这种写法就可以实现service这个bean使用byName进行自动装配,其他的bean使用byType进行自动装配。这样就可以防止service的bean使用byType时发现有多个相同类型的bean都可以注入到service中,出现冲突报错了
bean命名规则
使用注解的时候,bean的命名规则:
- 在使用@Component、@Repository、@Service、@Controller等注解创建bean时,如果指定bean的name,则是指定的名称.
- 如果不指定bean的name,bean名称的默认规则是类名的首字母小写,如SysConfig - sysConfig,Tools - tools。
- 如果类名前两个或以上个字母都是大写,那么bean名称与类名一样,如RBACUserLog - RBACUserLog,RBACUser - RBACUser,RBACRole - RBACRole。
使用XML来配置spring容器的时候bean标签写上id,然后如果不指定name的话,那么bean的name默认和id一样
自定义命名规则
命名规则是通过
BeanNameGenerator这个接口来实现的,我们可以通过实现这个接口来完成自己的命名规则,编写好命名规则类,然后告诉在告诉spring要扫描位置的同时,指定命名规则类就可以了
指定扫描类:
注解:
@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
// ...
}
XML:
<beans>
<context:component-scan base-package="org.example"
name-generator="org.example.MyNameGenerator" />
</beans>
使用注解进行自动装配
@Autowired默认先使用的是byType,byType使用的是属性的类型,如果byType会出现报错的话,再就会再使用byName,这里使用的name也是属性的属性名,用属性的属性名来去spring容器中匹配查找相同的bean name@Resource默认是使用的是byName,但是这里的byName和之前讲XML自动装配的byName不太一样,之前的byName都是通过setter方法名来匹配name的,但是@Resource使用的是属性名来匹配name的,和setter方法没有关系。@Resource可以指定使用byName还是byType。
但是
@Resource可以指定使用bytype匹配:
@Resource(type = IndexDaoImpl1.class)
直接指定使用byType,并且使用IndexDaoImpl1.class这个类,这样不管怎么样service这个类注入的已经是IndexDaoImpl1这个实现类
也可以直接指定名字来查找装配
@Resource(name = "indexDaoImpl1")
spring懒加载(懒汉模式)
bean在调用的时候才会被加载
使用懒加载的前提是这个bean的作用域必须是单例的。
用lazy-init。告诉spring容器是否以懒加载的方式创造对象。用的时候才加载构造,不用的时候不加载
取值:true(懒,真正调用到的时候再加载)、false(非懒,已启动spring容器就创建对象)、default(懒)
<bean id="dao" class="cn.java.ioc.MW" lazy-init="default" ></bean>
注解配置:
@Lazy
@Bean("dao")
public Dao dao() {
System.out.println("dao");
return new Dao();
}
优缺点
-
懒加载:对象使用的时候才去创建,节省资源,但是不利于提前发现错误。
-
非懒加载:容器启动的时候立刻创建对象。消耗资源。利于提前发现错误。
-
当scope=“prototype” (多例)时,默认以懒加载的方式产生对象。
-
当scope=“singleton” (单例)时,默认以非懒加载的方式产生对象。
spring作用域
spring容器中的bean可以分为五个范围。所有范围的名称都是说明的,
1.singleton:这种bean范围是默认的,这种范围确保不管接受到多个请求,每个容器中有一个bean的实例,单例模式由bean factory自身来维护。
2.prototype:原先通过范围与单例范围相反,为每一个bean请求提供一个实例。
3.request:在请求bean范围内会为每一个来自客户端的网络请求创建一个实例,在请求完成之后,bean会失效并被垃圾回收器回收。该属性仅对HTT请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。
4.session:与请求范围类似,确保每个session中的bean的实例在session过期后bean会随之消失。该属性仅用于HTTP Session,同一个Session共享一个Bean实例。不同Session使用不同的实例。Session
5.global-session:global-session和portlet公用全局存储变量的话,那么这全局变量需要存储在global-session中。该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。
可以通过@Scope(" ")注解来设置
spring容器bean的作用域默认是单例,非懒加载的
作用域引发的问题
一个类依赖另一个类,但是这两个的作用域是不同的,就会出现问题。
@Service
@Scope("singleton")
public class IndexService {
@Autowired
private IndexDao dao;
public void service() {
System.out.println("service" + this.hashCode());
System.out.println("dao" + dao.hashCode());
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
}
@Repository
@Scope("prototype")
public class IndexDaoImpl implements IndexDao {
@Override
public void test() {
System.out.println("Impl 0");
}
}
service是单例的,而它依赖的dao是原型的,但是最终的结果确实dao的原型作用域失效,这就是作用域不同进行依赖错产生的问题。因为service是单例的,他在spring容器中只会创建一次,而它依赖的对象是随着它的加载而加载的,所以service只加载一次,即使dao是原型的,也只会被加载一次。
解决方式
- ApplicationContextAware接口
对象实现
ApplicationContextAware接口,这个还需要覆写一个set方法,和引入ApplicationContext对象在service类每一次需要使用依赖的这个dao时,就通过
ApplicationContext对象重新取一次这个bean,这样dao就可以依旧保持原型的作用域,每一次使用它就会是一个新bean,而service对象一直保持单例不变。 实现:
@Service
@Scope("singleton")
public class IndexService implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Autowired //加不加这个注解都无所谓了,反正最后使用的也不是这个dao对象 用ApplicationContext这个Autowired可加可不加
private IndexDao dao;
public void service() {
System.out.println("service" + this.hashCode());
//每次调用都使用applicationContext对象重新获取IndexDaoImpl的bean
System.out.println("dao" + applicationContext.getBean("indexDaoImpl").hashCode());
}
public void setDao(IndexDao dao) {
this.dao = dao;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
但是这种实现spring接口的方法,会大大增加项目的耦合度,侵入性太强,使代码严重spring的API,spring的设计理念之一就是非侵入性,所以spring还提供了Lookup Method Injection这个方法来解决问题
- Lookup Method Injection 实现:
@Service
@Scope("singleton")
public class IndexService {
// @Autowired 使用Lookup必须要将Autowired去掉,否则会出错
private IndexDao dao;
public void service() {
System.out.println("service" + this.hashCode());
System.out.println("dao" + getDao().hashCode());
}
@Lookup
public IndexDao getDao() {
return null;
}
}
这里只需要添加一个get方法,用来每一次调用时获取全新的dao对象,方法名不重要,自定义就行,返回值就是你要获取的对象类型,只需要在这个方法上面添加@Lookup注解,这个方法内部什么逻辑代码都不需要写,直接写一个return null就可以了。在每次获取dao对象的位置调用这个方法就可以了。还有一点就是因为是通过这个方法来获取dao的bean了,所以也就不需要@Autowired这个注解进行注入了,要将其删除,不删除的话会出现报错。
@Lookup是通过方法的返回值类型,根据这个类型从spring容器中查找这个bean,如果找到了就直接通过这个方法返回回来。但是这里就有可能出现前面讲的多个相同类型的bean冲突问题
可以通过指定bean的name来指定要注入的对象:
@Lookup("indexDaoImpl")
生命周期与回调
生命周期的回调
生命周期回调有三种:
Initialization Callbacks(初始化回调)Destruction Callbacks(销毁回调)Default Initialization and Destroy Methods(默认初始化和销毁方法)
初始化回调与销毁回调
初始化回调需要对象实现InitializingBean接口,并重写afterPropertiesSet方法,销毁回调需要对象实现DisposableBean接口,并且重写destroy方法
@Component("amd")
public class AmdBean implements InitializingBean , DisposableBean {
@Value("@5700xt")
private String mainboard;
@Value("@5800x")
private String Cpu;
// 初始化回调
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("初始化回调");
}
// 销毁回调
@Override
public void destroy() throws Exception {
System.out.println("销毁回调");
}
}
使用xml与注解实现回调
XML(init-method和destroy-method)
在xml文件中自定义回调方法名
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
default-init-method="init"
default-destroy-method="destory" >
<bean id="dao" class="priv.cy.dao.IndexDaoImpl1"></bean>
</beans>
default-init-method="init" --- 初始化回调
default-destroy-method="destory" --- 销毁回调
注解 (@PostConstruct和@PreDestory)
@PostConstruct 由JSR-250提供,在构造函数执行完之后执行,等价于xml配置文件中bean的initMethod
@PreDestory 由JSR-250提供,在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod
使用以上两个注解直接用在回调方法上,也能实现自定义的回调方法:
@Repository
public class IndexDaoImpl1 implements IndexDao {
public IndexDaoImpl1() {
System.out.println("Constructor");
}
@PostConstruct
public void init() {
System.out.println("init");
}
@PreDestory
public void destory() {
System.out.println("destory");
}
}
byType多个同类型的Bean冲突解决办法
@Primary
这个是主数据源标签,出现冲突的情况下优先使用这个bean进行装配
@Repository
@Primary
public class IndexDaoImpl1 implements IndexDao {
public IndexDaoImpl1() {
System.out.println("Constructor 1");
}
@PostConstruct
public void init() {
System.out.println("init 1");
}
public void destory() {
System.out.println("destory 1");
}
}
@Qualifier
@Qualifier这个注解可以直接指定要注入哪个bean,它通过name去指定bean
@Service
public class IndexService {
@Qualifier("indexDaoImpl1")
private IndexDao indexDao;
}
Java自带的一些注解可以替代spring的注解来完成相同的功能,比如@Resource,这个就属于jdk的注解,但是也能实现自动装配的功能。
索引加速spring扫描类的速度
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.4.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
添加了这个之后spring项目在编译的时候就会将所有要扫描的类添加到一个索引当中,以后再扫描就不需要使用类路径一个个扫描了,而是直接使用索引进行扫描,这样大大加快了扫描速度。