一、介绍
1、官网介绍:
IOC与大家熟知的依赖注入同理,这是一个通过依赖注入对象的过程。也就是说,它们所使用的对象,是通过构造函数参数,工厂方法的参数或这是从工厂方法的构造函数或返回值的对象实例设置的属性,然后容器在创建bean时注入这些需要的依赖。 这个过程相对普通创建对象的过程是反向的(因此称之为IoC),bean本身通过直接构造类来控制依赖关系的实例化或位置,或提供诸如服务定位器模式之类的机制。
理解:
IOC是一种设计思想,在Java开发中,将你设计好的对象交给容器控制,而不是显示地用代码进行对象的创建。
把创建和查找依赖对象的控制权交给 IoC 容器,由 IoC 容器进行注入、组合对象之间的关系。这样对象与对象之间是松耦合、功能可复用(减少对象的创建和内存消耗),使得程序的整个体系结构可维护性、灵活性、扩展性变高。
所谓IOC ,就简短一句话:对象由spring来创建、管理,装配!
2、为什么使用IOC:
- 1、接口分离原则ISP(the Interface Segregation Principle ISP):模块间要通过抽象接口隔离开,而不是通过具体的类强耦合起来
- 2、依赖倒置原则DIP(the Dependency Inversion Principle DIP) :具体实现依赖抽象,下层依赖上层
ioc优点
- 1、集中管理
- 2、功能可复用(减少对象的创建和内存消耗)
- 3、使得程序的整个体系结构可维护性、灵活性、扩展性变高
- 4、解耦
3、IOC与DI区别
很多人把IOC和DI说成一个东西,笼统来说的话是没有问题的,但是本质上还是有所区别的,希望大家能够严谨一点,IOC和DI是从不同的角度描述的同一件事,IOC是从容器的角度描述,而DI是从应用程序的角度来描述,也可以这样说,IOC是依赖倒置原则的设计思想,而DI是具体的实现方式.所谓IOC,就简短一句话:对象由spring来创建、管理,装配!
4、容器
ApplicationContext是SpringIoC容器实现的代表,它负责实例化,配置和组装Bean。容器通过读取配置元数据获取有关实例化、配置和组装哪些对象的说明。配置元数据可以使用XML、Java注解或Java代码来呈现。它允许你处理应用程序的对象与其他对象之间的互相依赖关系。
4.1 容器的实例化
对象在Spring容器创建完成的时候就已经创建完成,不是需要用的时候才创建
4.2 容器的使用
ApplicationContext是能够创建bean定义以及处理相互依赖关系的高级工厂接口,使用方法T getBean(String name, Class<T> requiredType)获取容器实例。
// 创建spring上下文 加载所有的bean
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// 获取bean
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// 使用bean的对象
List<String> userList = service.getUsernameList();
4.3 容器的初始化过程
- 1、准备上下文(new springApplication())
- 2、解析xml配置文件路径
- 3、创建Bean工厂
- 4、加载bean定义 到BeanDefinitionMap
- 5、调用了bean工厂的后置处理器:invokeBeanFactoryPostProcessors()
- 6、判断是否符合生产标准(是不是抽象、懒加载、单例)
- 7、真正的生产Bean, 去单例池中获取看是否已经创建,如果已经创建则直接返回, 如果单例池没有就需要重新创建(为了解决循环依赖,将当前Bean加入到正在创建的标识中singletonsCurrentlyInCreation)
- 8、可以使用Bean的后置处理器直接返回自定义的Bean实例
- 9、调用doCreateBean开始真正创建Bean
- 10、例化Bean(通过工厂方法、Supplier、Bean后置处理器:SmartInstantiationAwareBeanPostProcessor.determineCandidateConstructors,BeanDefinition的ConstructorArgumentValues) 默认调用无参构造函数来实例化
- 11、注入属性值
- 12、初始化Bean 调用Awea 调用@PostConstrut Aop的动态代理也是在初始完进行生成的
- 13、最终加入到单例池中
5、bean
5.1 命名bean
<bean class="com.three.entity.User" id="user" name="user2 user3,user4;user5"></bean>
5.2 为外部定义的bean起别名
<alias name="user" alias="user6"></alias>
5.3 实例化bean
5.3.1 使用构造器实例化:默认,无法干预实例化过程
5.3.2 使用静态工厂方法实例化
<bean class="com.test.service.impl.UserServiceImpl" id="userService2"
factory-method="createUserServiceInstance" >
</bean>
public static UserServiceImpl createUserServiceInstance(){
return new UserServiceImpl();
}
5.3.3 使用实例工厂方法实例化
<bean class="com.test.service.impl.UserServiceImpl" id="userService"
factory-bean="serviceFactory"
factory-method="createUserService" >
</bean>
public class createUserService{
public UserServiceImpl createUserFactory(){
return new UserServiceImpl();
}
}
5.3.4 bean作用域
Singleton(单例)的作用域
Prototype(原型)的作用域
<!--作用域scope
singleton 默认:单例 只会在Ioc容器种创建一次
prototype 多例(原型bean) 每次获取都会new一次新的bean-->
<bean class="cn.beans.Person" id="person3" scope="prototype">
<property name="id" value="1"></property>
</bean>
5.3.5 自定义bean的特性
(1)生命周期回调
- 初始化方法回调
- 销毁方法回调
- 在非Web应用中优雅地关闭Spring IoC容器
/**
* 生命周期回调
* 1. 使用接口实现的方式来实现生命周期的回调:
* 1.1 初始化方法: 实现接口: InitializingBean 重写afterPropertiesSet方法 初始化会自动调用的方法
* 1.1 销毁的方法: 实现接口: DisposableBean 重写destroy 方法 销毁的时候自动调用方法
* 什么时候销毁:在spring容器关闭的时候 close()
* 或者 使用ConfigurableApplicationContext.registerShutdownHook方法优雅的关闭
*
* 2. 使用指定具体方法的方式实现生命周期的回调:
* 在对应的bean里面创建对应的两个方法
* init-method="init" destroy-method="destroy"
*/
(2)ApplicationContextAware 和 BeanNameAware
(3)其他的 Aware 接口
5.3.6 bean的继承
可以使用parent属性指定父类bean,如果想让父类bean不能被实例化 abstract="true"
6、三种配置方式
1、使用xml的配置
简单、直观 适合入门
2、基于注解的配置: @Compont(@serivce @controller @repository) @Autowride
Spring2.5 支持基于注解的元数据配置.SSM框架开发中的使用
3、基于Java的配置: @Confiration @Bean @Import
从Spring3.0开始, 由SpringJavaConfig项目提供的功能已经成为Spring核心框架的一部分。因此,可以使用Java配置来代替XML配置定义外部bean.
从spring4.0开始支持springboot1.0 之后springboot完全采用javaConfig的方式进行开发
二、具体实现
1、基于xml的配置
导入jar包:spring仓库https://repo.spring.io/list/libs-snapshot-local/org/springframework/spring/ 下载任意版本的spring
注意:只需要下载dist.zip 包含了docs,schema,然后直接导入到项目中
1.1 依赖注入
1.1.1 基于setter方法的依赖注入
<!--
1. 属性必须声明了set方法
2. name是根据set方法的名字来的,比如方法名字是:setIdxx->name="idxx"
-->
<bean class="cn.beans.User" id="user2">
<property name="id" value="1"></property>
<property name="username" value="zhangsan"></property>
<property name="realname" value="张三"></property>
</bean>
1.1.2 基于构造函数的依赖注入
<!--
1. 将会调用自定义构造函数来实例化对象,就不会调用默认的无参构造函数
2. name是根据构造函数的参数名来的,比如:User(String idxx) -> name="idxx"
3. name属性可以省略,但是要注意参数的位置
4. 如果非要把位置错开,可以使用name或者index或者type
5. index 是下标从0开始
6. type 在位置错开情况下只能在类型不一样的时候指定才有明显效果
-->
<bean class="cn.beans.User" id="user3">
<constructor-arg name="username" value="lisi"></constructor-arg>
<constructor-arg name="id" value="1"></constructor-arg>
<constructor-arg name="realname" value="李四"></constructor-arg>
</bean>
1.1.3 复杂数据类型
- 直接值(基本类型,String等)
- 对其他bean的引用(装配)
- 内部bean
- 集合
- null和空的字符串值
- 使用p命名空间简化基于setter属性注入XML配置
- 使用c命名空间简化基于构造函数的XML
1.1.4 depends-on属性
<!--使用depends-on可以设置先加载的Bean,也就是控制bean的加载顺序-->
<bean class="cn.beans.Person" id="person" depends-on="wife"></bean>
<bean class="cn.beans.Wife" id="wife"></bean>
1.1.5 懒加载bean
<!--使用lazy-init设置懒加载,默认为false;在spring容器创建的时候加载(实例化)true: 在使用的时候(getBean)才会去加载(实例化)-->
<bean class="cn.beans.Person" id="person2" lazy-init="true">
<property name="id" value="1"></property>
</bean>
1.2 自动注入
当一个对象中需要引用另外一个对象的时候,在之前的配置中我们都是通过property标签来进行手动配置的,其实在spring中还提供了一个非常强大的功能就是自动装配,可以按照我们指定的规则进行配置
<!--
当一个对象中需要引用另外一个对象的时候,在之前的配置中我们都是通过property标签来进行手动配置的,其实在spring中还提供了一个非常强大的功能就是自动装配,可以按照我们指定的规则进行配置,配置的方式有以下几种:
1、default/no:不自动装配
2、 bytype 根据类型自动注入,spring会根据bean里面的所有对象属性的类型,只要它匹配到bean里面某一个类型跟属性类型吻合就会自动注入
3、 byname 会根据属性setxxx的名字来自动匹配 ,spring会根据bean里面的所有对象属性的set的名字,只要它匹配到bean里面某一个名字跟属性名字吻合就会自动注入
4、 constructor 优先根据名字来找, 如果名字没有匹配到根据类型来匹配,如果类型匹配到多个则不会自动注入
注意:
bytype 如果匹配到两个同样的类型会出现错误,所以一定要保证ioc容器里面只有一个对应类型的bean
byname 最能匹配到唯一的那个bean
constructor 保证构造函数不能包含多余的其他参数
default 不会进行自动注入
-->
<bean class="cn.beans.Person" id="person6" autowire="constructor" >
<property name="id" value="1"></property>
</bean>
1.3 SpEL的使用
SpEL:Spring Expression Language,spring的表达式语言,支持运行时查询操作对象
使用#{...}作为语法规则,所有的大括号中的字符都认为是SpEL.
<bean id="user" class="cn.test.entity.User">
<!--支持任何运算符-->
<property name="id" value="#{12*2}"></property>
<!--可以引用其他bean的某个属性值-->
<property name="name" value="#{address.province}"></property>
</bean>
1.4 spring创建第三方bean对象(数据库连接池)
1.4.1 导入数据库连接池的pom文件
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
1.4.2 编写配置文件
ioc.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">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
<property name="url" value="jdbc:mysql://localhost:3306/demo"></property>
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
</bean>
</beans>
1.4.3 编写测试文件
public class MyTest {
public static void main(String[] args) throws SQLException {
ApplicationContext context = new ClassPathXmlApplicationContext("ioc.xml");
DruidDataSource dataSource = context.getBean("dataSource", DruidDataSource.class);
System.out.println(dataSource);
System.out.println(dataSource.getConnection());
}
}
1.5 spring引用外部配置文件
1.5.1 dbconfig.properties
username=root
password=123456
url=jdbc:mysql://localhost:3306/demo
driverClassName=com.mysql.jdbc.Driver
1.5.2 编写配置文件
<?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">
<!--加载外部配置文件 在加载外部依赖文件的时候需要context命名空间-->
<context:property-placeholder location="classpath:dbconfig.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${username}"></property>
<property name="password" value="${password}"></property>
<property name="url" value="${url}"></property>
<property name="driverClassName" value="${driverClassName}"></property>
</bean>
<!-- spring容器提供了一个JdbcTemplate类,用来方便操作数据库 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!-- 使用具备具名函数的JdbcTemplate -->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
</beans>
2、基于注解的配置
导入jar包
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
(1)使用注解的方式注册bean到IOC容器中
applicationContext.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"
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对象添加到IOC容器中,需要在类上添加某些注解
Spring中包含4个主要的组件添加注解:
@Controller:控制器,推荐给controller层添加此注解
@Service:业务逻辑,推荐给业务逻辑层添加此注解
@Repository:仓库管理,推荐给数据访问层添加此注解
@Component:给不属于以上基层的组件添加此注解
注意:我们虽然人为的给不同的层添加不同的注解,但是在spring看来,可以在任意层添加任意注解
spring底层是不会给具体的层次验证注解,这样写的目的只是为了提高可读性,最偷懒的方式
就是给所有想交由IOC容器管理的bean对象添加component注解
使用注解需要如下步骤:
1、添加上述四个注解中的任意一个
2、添加自动扫描注解的组件,此操作需要依赖context命名空间
3、添加自动扫描的标签context:component-scan
注意:当使用注解注册组件和使用配置文件注册组件是一样的,但是要注意:
1、组件的id默认就是组件的类名首字符小写,如果非要改名字的话,直接在注解中添加即可
2、组件默认情况下都是单例的,如果需要配置多例模式的话,可以在注解下添加@Scope注解
-->
<!--
定义自动扫描的基础包:
base-package:指定扫描的基础包,spring在启动的时候会将基础包及子包下所有加了注解的类都自动扫描进IOC容器
-->
<context:component-scan base-package="cn.test"></context:component-scan>
</beans>
(2)定义扫描包时要包含的类和不要包含的类
applicationContext.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"
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">
<context:component-scan base-package="cn.test" use-default-filters="false">
<!--
当定义好基础扫描的包之后,可以排除包中的某些类,使用如下的方式:
type:表示指定过滤的规则
annotation:按照注解进行排除,标注了指定注解的组件不要,expression表示要过滤的注解
assignable:指定排除某个具体的类,按照类排除,expression表示不注册的具体类名
aspectj:后面讲aop的时候说明要使用的aspectj表达式,不用
custom:定义一个typeFilter,自己写代码决定哪些类被过滤掉,不用
regex:使用正则表达式过滤,不用
-->
<!--
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
-->
<!--指定只扫描哪些组件,默认情况下是全部扫描的,所以此时要配置的话需要在component-scan标签中添加 use-default-filters="false"-->
<context:include-filter type="assignable" expression="cn.test.service.PersonService"/>
</context:component-scan>
</beans>
(3)使用@AutoWired进行自动注入
注意:当使用AutoWired注解的时候,自动装配的时候是根据类型实现的。
1、如果只找到一个,则直接进行赋值,
2、如果没有找到,则直接抛出异常,
3、如果找到多个,那么会按照变量名作为id继续匹配,
1、匹配上直接进行装配
2、如果匹配不上则直接报异常
4、还可以使用@Qualifier注解来指定id的名称,让spring不要使用变量名,当使用@Qualifier注解的时候找不到也会报错
5、@AutoWired定义在方法上:1、此方法在bean创建的时候会自动调用;2、这个方法的每一个参数都会自动注入值
(4)自动装配的注解@AutoWired,@Resource
在使用自动装配的时候,出了可以使用@AutoWired注解之外,还可以使用@Resource注解,大家需要知道这两个注解的区别。
1、@AutoWired:是spring中提供的注解,@Resource:是jdk中定义的注解,依靠的是java的标准
2、@AutoWired默认是按照类型进行装配,默认情况下要求依赖的对象必须存在,@Resource默认是按照名字进行匹配的,同时可以指定name属性。
3、@AutoWired只适合spring框架,而@Resource扩展性更好
3、基于JavaConfig的配置
(1)绑定Java与XML配置
db.properties
mysql.username=XX
mysql.password=ZZZ
mysql.url=http
mysql.driverClassName=name
@Configuration // 就相当于创建了一个xml 文件 <beans></beans>
@ComponentScan("cn.test") //<context:component-scan base-package="cn.test" >
@PropertySource("classpath:db.properties")
public class MainConfiration {
@Value("${mysql.username}")
private String name;
@Value("${mysql.password}")
private String password;
@Value("${mysql.url}")
private String url;
@Value("${mysql.driverClassName}")
private String driverName;
// <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"></bean>
// 可以干预Bean实例化过程!
@Bean
public DruidDataSource dataSource(){
DruidDataSource dataSource=new DruidDataSource();
dataSource.setName(name);
dataSource.setPassword(password);
dataSource.setUrl(url);
dataSource.setDriverClassName(driverName);
return dataSource;
}
//init-method="initByConfig" destroy-method="destroyByConfig"
@Bean(initMethod = "initByConfig",destroyMethod = "destroyByConfig")
public User userconf(){
return new User();
}
}
(2)AnnotationConfigApplicationContext初始化Spring容器
@Test
public void test01(){
ApplicationContext ioc=new AnnotationConfigApplicationContext(MainConfiration.class);
UserController bean = ioc.getBean(UserController.class);
bean.getUser();
}
(3)@Bean注解
@Bean是一个方法级别的注解,它与XML中的 <bean/>元素类似。注解支持 <bean/>提供的一些属性,例如 * init-method * destroy-method * autowiring * name开发者可以在@Configuration类或@Component类中使用@Bean注解。
@Configuration
public class AppConfig {
@Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
以上写法完全等同于以下Spring XML
<beans>
<bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>
Bean的继承
@Bean
public TransferService transferService(AccountRepository accountRepository) {
return new TransferServiceImpl(accountRepository);
}
Bean生命周期回调
@Bean(initMethod = "initByConfig",destroyMethod = "destroyByConfig")
public User userconf(){
return new User();
}
Bean作用域
@Bean
@Scope("prototype")
自定义Bean的名字
//默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。 但是可以使用name属性覆盖此功能
//多个别名:@Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" })
@Bean(name = "myThing")
(4)@Import 注解
//就像在Spring XML文件中使用<import/>元素来帮助模块化配置一样,
//@Import 注解允许从另一个配置类加载@Bean定义
@Configuration
@Import(ConfigA.class)
public class ConfigB {
@Bean
}
总结
将一个类注入到ioc中的方法:
1.xml:<bean>
2.@Component (@Controller,@Service,@Repository)
3.@Bean
4.@Import