Spring-Ioc基础知识

Spring-Ioc基础知识
Moxy1 简介
1.1 什么叫Spring
- 狭义的Spring:
Spring Framework
- 广义的Spring:指的是整个Spring生态,包含
Spring Framework,Spring boot,Spring cloud,Spring Security
等在内的一站式开发框架。
1.2 Spring架构图
2 初识Spring IOC
- IOC (Inversion of Control): 控制反转,是一种设计理念。将实例对象的权利转移到IOC容器管理,使用时不再需要new对象,直接在IOC容器里面拿。
- DI (dependency injection): 依赖注入,是实现IOC理念的一种方式。
- 核心作用就是:由Spring IOC容器创建和管理对象(Beans)
2.1 传统方式
当使用一个类的时候都需要先去实例这个类对象,每多一个,多实例一个,这样的方式代码耦合性相当高,当需求改变时,需要修改代码中对应的实例对象。
2.2 IOC思想
全部统一由一个容器去管理这些需要的类,当需要使用时,直接在容器中拿,这样减少了代码的耦合性。
2.3 DI实现
DI依赖注入,在容器中可以将类中需要的其他类注入。
3 SpringIOC初体验
3.1 maven导入
1 | <!--spring-context--> |
3.2 准备entity类
每一个entity类都需要遵循javaBean规范,必须书写get和set方法。
3.2.1 woman
1 | public class Woman { |
3.2.2 Man
1 | public class Man { |
3.3 书写配置文件
在配置文件中可以添加多个bean
- id:bean的定义name
- class:需要ioc管理的entity类路径
- property:配置参数
- value:设置基本数据类型的参数
- ref:引用类型的参数引用
1 |
|
3.4 书写测试类
1 |
|
4 基于XML创建Bean
4.1 基于构造方法
4.1.1 无参构造
默认的就是无参构造
Women在无参构造输出
1 | public Woman() { |
配置bean
1 | <bean id="lili" class="com.moon.entity.Woman"> |
创建IOC
1 | public static void main(String[] args) { |
结果
4.1.2 有参数构造
在使用有参构造器创建时,也需要同时将依赖注入。
1 | <bean id="lili" class="com.moon.entity.Woman"> |
4.2 基于工厂模式
隐藏创建的细节
4.2.1 静态工厂
java静态工厂类
1 | public class WomanStaticFactory { |
配置静态工厂
1 | <!-- 通过class定位到静态工厂类,使用factory-menthod定位到方法 --> |
4.2.2 工厂实例
java实例工厂类
1 | public class WomanFactory { |
配置实例工厂
1 | <!-- 添加工厂的bean(使用工厂类的无参创建) --> |
4.3 配置文件书写规范
1、**
<bean/>
**标签中的id和name都是用来设置对象在IOC中标识。区别如下:
- 在**==同一个配置文件==中==不可以重复==**
- 如果在不同配置文件中出现相同的==ID==和**==Name==的==bean==,排序在后面的bean会覆盖前面的==bean==**。
- 在**==同一个bean==中同时配置了==id==和==name==,那么==id为容器中的唯一标志==**,而name为别名,如果配置了多个name,那么==name全部是别名==。
- 如果**==这个bean==只==配置了name==,==没有id==,那么name为唯一标志。如果name配置了多个,那么==第一个name是唯一标志,剩余的name为别名==**。
- 在一个bean中,name可以写多个标志,id只能设置唯一标志。
2、如果没有配置name和id的话,ioc容器会给bean设置一个默认标识,该标识是bean的class+#数字标识,数字从0开始。
1 com.moon.entity.Woman#0如过根据标识去获取类,只写了全类名,ioc会去获取全类名+#0的bean,也就是第一个bean。
5 DI依赖注入
在一个类里面需要依赖另一个类,之前使用时是在类中去new另外的类。而使用了spring后,spring使用依赖注入(DI)的方式,将bean或者参数注入到另一个bean中。
- 注入基本数据类型和字符串时使用 value
- 注入引用类型,引用其他bean使用 ref
5.1 基于构造器注入
通过构造器注入
和前面的创建一样,通过构造器创建时注入,注入的方法有三种。
通过参数name赋值(常用)
1
2
3
4
5 <bean id="lili" class="com.moon.entity.Woman">
<constructor-arg name="name" value="莉莉"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="outLook" value="面容较好的"/>
</bean>通过下标赋值(下标从0开始,下标对应的属性由构造器决定)
1
2
3
4
5 <bean id="lili" class="com.moon.entity.Woman">
<constructor-arg index="0" value="莉莉"/>
<constructor-arg index="1" value="18"/>
<constructor-arg index="2" value="面容较好的"/>
</bean>通过类型赋值
1
2
3
4
5 <bean id="lili" class="com.moon.entity.Woman">
<constructor-arg type="java.lang.String" value="莉莉"/>
<constructor-arg type="int" value="18"/>
<constructor-arg type="java.lang.String" value="面容较好的"/>
</bean>
5.2 基于Setter方法注入
通过set方法注入,通过该方法,需要确保属性全部有对应的set方法
涉及到的标签是
<property>
原理:ioc容器会先去调用bean的无参构造器或者工厂模式去实例一个bean,然后再调用这个bean的set方法,将属性注入。
1
2
3
4
5
6 <bean id="lili" class="com.moon.entity.Woman">
<!--调用set方法注入属性-->
<property name="name" value="莉莉"/>
<property name="age" value="18"/>
<property name="outLook" value="面容较好的"/>
</bean>
- 注入基本数据类型加String 使用value
- 注入引用数据类型使用ref来引用
5.2.1 循环依赖
如果出现循环依赖的问题,俩个bean相互依赖,那么就一定需要使用set的方式注入,如果使用构造器方式注入的话,就会出现异常。
原理:在sring中,会先全部实例对应的bean,全部实例号以后,才会对对应的bean进行注入。如果使用构造器注入的话,在实例的时候就需要寻找其他的bean,而此时其他的bean可能还没有实例。
1 | <!-- bean A依赖于B的实例 --> |
5.2.2 内部注入
可以在一个bean的property中,将property标签不自闭合,然后在property标签中可以声明一个bean
注意:
- 内部注入的bean,不会受ioc容器接管,在ioc容器中是取不到这个类的,类似于java的内部类。
- 内部bean仅作用在这个bean的property中,不归容器所有。
SpringIOC容器在处理inner bean时,只负责创建,不负责管理。
1 | <bean id="peter" class="com.moon.entity.Man"> |
5.2.3 数组的注入
数组的注入,可以将property标签不去自闭合,在标签中使用array标签进行该数组的值的赋值
1 | <bean id="fierceMan" class="com.moon.entity.FierceMan"> |
5.2.4 List注入
List的注入和array一样,需要在property标签中引用list标签
默认的实现是ArrayList
1 | <bean id="fierceMan" class="com.moon.entity.FierceMan"> |
5.2.5 Set注入
和List注入的方式基本一致,需要在property标签中引用set标签
默认的实现类是LinkedHashSet
1 | <bean id="fierceMan" class="com.moon.entity.FierceMan"> |
5.2.6 Map注入
map的注入和之前一样,在property标签先使用map标签,不同的是map标签下注入的是entry,key的声明是在entry标签的属性上。
1 | <bean id="fierceMan" class="com.moon.entity.FierceMan"> |
5.2.7 Properties注入
在spring配置文件也可以注入prop信息,注入的方式和集合的注入基本类似
1 | <bean id="fierceMan" class="com.moon.entity.FierceMan"> |
6 扩展命名空间
6.1 c命名空间
xml配置文件头文件中导入c命名空间约束
1 | xmlns:c="http://www.springframework.org/schema/c" |
c 命名空间的使用就是基于构造器注入的方法,需要bean中有有参构造器
1 | <bean name="mary3" class="com.moon.entity.Woman" |
1 | <bean id="jackMa" class="com.moon.entity.Man" |
6.2 p命名空间
xml配置文件头文件中导入pmm空间约束
1 | xmlns:p="http://www.springframework.org/schema/p" |
p命名空间的使用就是基于set方法注入
1 | <bean id="peter" class="com.moon.entity.Man" |
7 bean的属性
==scope== | 作用域 |
---|---|
id、name | 定义bean的唯一标识 |
class | bean的全类名 |
factory-bean | 工厂对象 |
factory-method | 工厂方法 |
lazy-init | 懒加载 |
depends-on | 依赖某个实例(在生命周期用到) |
==init-methid== | Bean初始化执行的方法 |
==destory-metho== | bean销毁时执行的方法 |
autowire | 自动装配(依赖注入) |
7.1 Scope属性
bean的作用域
7.1.1 属性值说明
Scpoe | 描述 |
---|---|
singleton | 单例模式创建bean,==默认的==scope就是单例模式 |
prototype | 原型,指定单个bean的实例对象数量为任意多个 |
request | web环境下,每一次独立请求都存在唯一实例,存在单个HTTP请求 中,bean 的作用域限于 HTTP请求 范围 |
session | web环境下,每一次会话都存在唯一的实例,存在单个的会话中,bean的作用域限于单次的会话范围 |
application | web环境下,这里的作用域时在servletContext上下文中的唯一的实例 |
websocket | web环境下,将单个bean的作用域限定为websocket 的生命周期 |
7.1.2 singleton和prototype的区别
- singleton的实例对象数量时唯一的,而prototype的实例对象是多个的
- singleton在容器初始化时就会实例化一个对象放在容器中,而prototype是在使用getBean方法是才会去创建这个bean的实例,每一次的实例都不一样。
- 效率的不同,singleton的效率会高一些,因为只会实例一次
- 线程的安全问题,prototype的线程会安全一些
8 bean的生命周期
生命周期就是bean的实例在IOC容器中从创建到销毁的过程
在spring中bean的生命周期很复杂,但是可以简单的理解为以下周期:
- 实例化
- 注入属性
- 调用实现接口方法
- 初始化
- 就绪
- 销毁
8.1 自定义初始化方法
在配置文件中,可以在bean的属性配置中使用
init-method
来指定自定义的初始化方法比如在实体类中添加了以下方法:
1
2
3 private void init(){
System.out.println("custom-init");
}那么就可以在配置文件中去指定该方法为初始化方法
1 <bean id="man" class="com.moon.entity.FierceMan" init-method="init"/>
8.2 自定义销毁方法
销毁方法的自定义和初始化的一样
在实体类添加以下销毁方法:
1
2
3 private void destroy(){
System.out.println("custom-destroy");
}在配置文件中配置:
1 <bean id="man" class="com.moon.entity.FierceMan" destroy-method="destroy"/>
8.3 BeanPostProcessor的实现
如果实现了该接口,那么在容器初始化bean的前后会调用BeanPostProcessor的对应方法
实现自己的BeanPostProcessor
1 | public class MyBeanPostProcessor implements BeanPostProcessor { |
在配置文件中配置
1 |
|
结果
9 基于注解开发
好处:
摆脱繁琐的xml文件的配置和属性注入
可读性好,声明式的开发风格更符合中小型项目,很适合轻量级的开发
使用注解的前提,需要在配置文件中开启组件扫描
1
2
3
4
5 <!-- 需要导入xml头文件 -->
<context:component-scan base-package="com.moon">
<!--排除不需要扫描的bean目录或者bean-->
<context:exclude-filter type="regex" expression="com.moon.pojo.*"/>
</context:component-scan>
9.1 注解的分类
根据**==Bean的类型==**分类:来标注和管理bean
@Component
: 组件注解,是一个通用注解,意思是交给IOC容器来管理
@Repository
: 一般标注在Dao层/持久层,也是把类交给IOC容器管理
@Service
: 标注在业务层/逻辑层
@Controller
: 在Web环境下标注在控制层/控制器类上根据如何==注入属性==来分类
按照类型
@Autowired
:由IOC容器按照管理bean的类型去注入
@Inject
:基于JSR-330(java依赖注入标准的330号文件),也是通过类型去注入,需要额外引入依赖按名称
@Named
: 基于JSR-330(java依赖注入标准的330号文件),一般和Inject配合一起使用,也需要引入依赖
@Qualifier
: 和Autowired配合使用,通过name指定对应的bean
@Resource
: 基于JSR-250,先按照名称注入,如果没有再按照类型注入元数据注解:辅助容器更好的管理bean
@Primary
: 主要用在根据类型装配的时候,ioc容器存在多个统一类型的Bean造成装配失败,标识当前的类为主要bean,在多个bean冲突的时候,会优先注入该类。
1
2
3
4
5
6
7
8
public class GardenBabyCartoonDao implements CartoonDao{
public void rentCD() {
System.out.println("我租到了花园宝宝的盘");
}
}
@PostConstruct
:相当于xml配置文件中的init-method的自定义初始化方法
1
2
3
4
public void annotationInit(){
System.out.println("猛男爱看的动漫被初始化了");
}
@PreDestroy
:相当于xml配置文件中的destroy-method的自定义销毁方法
1
2
3
4
public void annotationDestroy(){
System.out.println("猛男爱看的动漫被销毁了");
}
@Scope
:设定一个bean的作用域属性,默认的情况下是singlten,标识在类上
1
2
3
public class CartoonService {}
读取外部的配置文件
1
2 user=root
password=123在spring的配置中添加如下
1
2 <!--让IOC容器读取外部文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>使用Value注解去换取配置文件的值
1
2
3
4
5
private String url;
private String password;
@Value
:为类中的属性注入静态数据,如果引用外部的数据,需要使用表达式${}
填写key去获取Value。如果Value中填写正常字符串,则可以注入基本数据类型。
1
2
3
4
private String name;
private int age;小结:多个同一类型的Bean根据类型装配(Autowired),会报错,那么就需要去指定一个bean去注入,这时候可以通过Qualifier注解去指定名字,或者使用直接使用Resource注解去指定name,也可以通过在Bean组件的类上加Primary注解来标识这个类为主要的类。
9.2 注解标记bean
在需要交给ioc管理的类的上面加管理bean的注解
1 |
|
默认的beanName是该类的首字母小写后的类名,在注解中可以输出值,输入的值就是beanName
1 |
|
9.3 注解注入
注入的时候,是通过暴力反射,将属性直接赋值,并没有使用set方法,所以不会调用set方法
如果需要强行使用set方法,可以将注解放在set方法上
1 | // 将注解放在set方法上,使用set注入 |
注入引用属性
1 | // 先通过type注入,如果有多个bean,通过name指定对应的bean |
1 | // 和上面的一样,但是需要导入额外的包 |
1 | // 先通过类型注入,然后通过name注入,相当于上面俩个注解的结合,比较强大 |
1 | // 如果设计的只有一个实现类,就可以通过一个自动注入的注解进行注入 |
9.4 上下文创建
和配置文件搭配使用的时候,还是使用的ClassPathXmlApplicationContext(),然后在构造器传入配置文件
需要注意的是,该方式,需要在配置文件中开启组件扫描
1 | public class SpringAnnotationTest { |
使用注解的上下文,AnnotationConfigApplicationContext(),然后在构造器中传入需要扫描的包
该方式会去什么指定的包,不再需要配置文件
1 | public class SpringAnnotationTest { |
和java配置文件搭配使用,还是使用AnnotationConfigApplicationContext(),但是需要传入java配置文件类
在java配置文件中需要通过注解去开启扫描
10 基于JavaConfig开发
通过JavaConfig进行管理bean,需要和注解搭配使用
10.1 核心注解
@Configuration
: 类级别注解,被它所修饰的类就代表一个配置文件,相当于配置文件中的beans@Bean
: 方法级别注解,修饰配置类中的每一个方法,代表配置文件中的每一个bean方法名代表id,返回值代表bean的类型,传入的参数可以传入其他bean,和注解注入一样,先通过类型,再通过name
@ComponentScan
:类级别注解,再配置类中添加该注解,可以对指定的类进行扫描,可以扫描出全部的组件,该注解后面加s后的注解可以传入多个扫描
1 |
|
10.2 上下文创建
上下文使用的是AnnotationConfigApplicationContext,这里可以在构造器中传入配置文件的类
1 | AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(IocConfig.class); |
1 | public class SpringAnnotationTest { |