Spring学习——IOC控制反转、DI依赖注入(上)
一、Spring简介
- Spring是一个开源框架
- Spring为简化企业级开发而生,使用Spring,JavaBean就可以实现很多以前要靠EJB才能实现的功能。同样的功能,在EJB中要通过繁琐的配置和复杂的代码才能够实现,而在Spring中却非常的优雅和简洁。
- Spring是一个IOC(DI)和AOP容器框架。
- Spring的优良特性:
- 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
- 依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现。
- 面向切面编程:Aspect Oriented Programming——AOP
- 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
- 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
- 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。
官网: https://spring.io
文档:https://docs.spring.io/spring/docs/5.2.5.RELEASE/spring-framework-reference/core.html#spring-core
下载地址: https://repo.spring.io/libs-release-local/org/springframework/spring/
下载的文件名spring-framework-x.x.x.RELEASE-dist.zip
libs目录下的jar包说明
总结:Spring是一个轻量级的、控制反转和面向切面编程的框架
1.1 Spring体系结构
Test:Spring的单元测试模块
spring-test-4.0.0.RELEASE
Core Container:核心容器(IOC),包括4部分:
spring-core:提供了框架的基本组成部分,包括 IoC 和依赖注入功能。
spring-beans:提供 BeanFactory,
spring-context:模块建立在由core和 beans 模块的基础上建立起来的,它以一种类似于JNDI注册的方式访问对象。Context模块继承自Bean模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过Servelet容器)等功能
spring-expression:提供了强大的表达式语言,用于在运行时查询和操作对象图。它是JSP2.1规范中定义的统一表达式语言的扩展,支持set和get属性值、属性赋值、方法调用、访问数组集合及索引的内容、逻辑算术运算、命名变量、通过名字从Spring IoC容器检索对象,还支持列表的投影、选择以及聚合等
spring-beans-4.0.0.RELEASE spring-core-4.0.0.RELEASE spring-context-4.0.0.RELEASE spring-expression-4.0.0.RELEASE
AOP+Aspects:面向切面编程模块
spring-aop-4.0.0.RELEASE spring-aspects-4.0.0.RELEASE
Data Access:数据访问模块
spring-jdbc-4.0.0.RELEASE spring-orm(Object Relation Mapping)-4.0.0.RELEASE spring-ox(xml)m-4.0.0.RELEASE spring-jms-4.0.0.RELEASE spring-tx-4.0.0.RELEASE(事务)
Web:Spring开发Web引用模块
spring-websocket-4.0.0.RELEASE spring-web-4.0.0.RELEASE ------原生web相关(servlet) spring-webmvc-4.0.0.RELEASE------开发web项目(web) spring-webmvc-portlet-4.0.0.RELEASE—开发web应用的组件集成
用哪个模块导入哪个包(建议)
使用maven导入依赖
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.0.RELEASE</version>
</dependency>
二、入门案例
-
导包
核心容器
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
commons-logging-1.1.3.jar
Spring运行的时候依赖一个日志包;没有就报错; -
新建一个Person类,添加set、get方法
-
在classpath下创建一个Spring配置文件ApplicationContext.xml,注册bean。
使用bean标签注册一个Person对象,Spring会自动创建这个Person对象
class:写要注册的组件的全类名
id:这个对象的唯一标识
使用property标签为Person对象的属性值,name:指定属性名;value:指定属性值<?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"> <!-- 注册一个Person对象,Spring会自动创建这个Person对象 --> <!-- 一个Bean标签可以注册一个组件(对象、类) class:写要注册的组件的全类名 id:这个对象的唯一标示; --> <bean id="person01" class="com.zb.bean.Person"> <!--使用property标签为Person对象的属性赋值 name="lastName":指定属性名 value="张三":为这个属性赋值 --> <property name="lastName" value="张三"></property> <property name="age" value="18"></property> <property name="email" value="zhangsan@zb.com"></property> <property name="gender" value="男"></property> </bean> </beans>
-
通过Spring的IOC容器创建Person类实例
@Test public void test() { //ApplicationContext:代表ioc容器 //ClassPathXmlApplicationContext:当前应用的xml配置文件在 ClassPath下 //根据spring的配置文件得到ioc容器对象 ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml"); //容器帮我们创建好对象了 Person bean = (Person) ioc.getBean("person01"); System.out.println(bean);//Person [lastName=张三, age=18, gender=男, email=zhangsan@zb.com] }
2.1 几个细节
-
关于类路径
src,源码文件夹开始的路径,称为类路径的开始,所有源码文件夹里面的东西都会被合并放在类路径里面;
Java文件的类路径为:/bin/
JavaWeb文件的类路径为:/WEB-INF/classes/
参考博客:JAVA项目——项目编译后的类路径和源码文件夹图解 -
ApplicationContext(IOC容器的接口)
new ClassPathXMlApplicationContext(“ioc.xml”);ioc容器的配置文件在类路径下;
FileSystemXmlApplicationContext(“F://ioc.xml”);ioc容器的配置文件在磁盘路径下; -
Person对象是什么时候创建好了呢?
容器中对象的创建在容器创建完成的时候就已经创建好了 -
同一个组件在ioc容器中是单实例的;容器启动完成都已经创建准备好的。
-
容器中如果没有这个组件,获取组件?报异常
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named ‘person03’ is defined -
IOC容器用property标签创建这个组件对象的时候,会利用setter方法为其属性赋值,注意属性名是set方法后的那串的首字母小写
三、IOC和DI介绍
-
IOC(Inversion of Control):反转控制
其本意就是将
原本在程序中手动创建对象的控制权,交给Spring来管理
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
使用 IOC 目的:为了耦合度降低
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂IOC的底层原理:xml 解析、工厂模式、反射
-
DI(Dependency Injection):依赖注入,就是注入属性
IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
-
IOC容器在Spring中的实现
1、在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
2、Spring提供了IOC容器的两种实现方式
(1)BeanFactory
:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。加载配置文件时候不会创建对象,在获取对象(使用)才去创建对象
(2)ApplicationContext
:BeanFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory。加载配置文件时候就会把在配置文件对象进行创建 -
ApplicationContext的主要实现类
1、ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件
2、FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件
3、在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。 -
ConfigurableApplicationContext
1、是ApplicationContext的子接口,包含一些扩展方法
2、refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。 -
WebApplicationContext
专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
四、从IOC容器中获取bean的实例
- Object getBean (String beanName); // 根据bean对象在容器中的名称来获取
- <T> T getBean(Class<T> requiredType) // 按照指定的类型去寻找bean对象
- <T> T getBean(String name,@Nullable Class<T> requiredType): 根据bean的类型 + ID 去寻找.
@Test
public void test02(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//方法一:
//通过bean的id获取bean的实例,需要强转
Person bean1 = (Person)ioc.getBean("person01");
System.out.println(bean1);
//方法二:
//根据bean的类型从IOC容器中获取bean的实例,但如果同一个类型的bean在XML文件中配置了多个,则获取时会抛出异常
Person bean2 = ioc.getBean(Person.class);
System.out.println(bean2);
//方法三:
//通过bean的id和bean的类型获取bean的实例
Person bean3 = ioc.getBean("person01",Person.class);
System.out.println(bean3);
}
五、属性的注入方法
-
通过bean的setXxx()方法赋值
需要借助set方法,使用propetry标签。(保证bean对象中有无参构造方法)
<bean id="book" class="com.atguigu.spring5.Book"> <!--使用 property 完成属性注入 name:类里面属性名称 value:向属性注入的值 --> <property name="bname" value="易筋经"></property> <property name="bauthor" value="达摩老祖"></property> </bean>
-
通过构造器注入
使用constructor-arg
标签,则调用构造器进行属性注入,需要借助有参构造-
通过构造函数中的参数名称注入
<bean id="person03" class="com.zb.bean.Person"> <!-- 调用有参构造器进行创建对象并赋值 --> <!-- public Person(String lastName, Integer age, String gender, String email) --> <constructor-arg name="lastName" value="小明"></constructor-arg> <constructor-arg name="email" value="xiaoming@atguigu.com"></constructor-arg> <constructor-arg name="gender" value="男"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> </bean>
-
只写value属性,会默认按顺序寻找构造方法进行匹配
<bean id="person04" class="com.zb.bean.Person"> <!-- 调用有参构造器进行创建对象并赋值 --> <!-- public Person(String lastName, Integer age, String gender, String email) --> <constructor-arg value="小明"></constructor-arg> <constructor-arg value="18"></constructor-arg> <constructor-arg value="男"></constructor-arg> <constructor-arg value="xiaoming@atguigu.com"></constructor-arg> </bean>
-
通过构造函数参数索引,如果有多个重载的构造函数时也可以配合type一起使用
<bean id="person05" class="com.zb.bean.Person"> <!-- 通过索引值指定参数位置 --> <!-- public Person(String lastName, Integer age, String gender, String email) --> <constructor-arg value="男" index="2"></constructor-arg> <constructor-arg value="xiaoming@atguigu.com" index="3"></constructor-arg> <constructor-arg value="小明" index="0"></constructor-arg> <constructor-arg value="18" index="1"></constructor-arg> </bean>
<!-- 重载的情况下type可以指定参数的类型 --> <bean id="person05" class="com.atguigu.bean.Person"> <constructor-arg value="小丽"></constructor-arg> <constructor-arg value="10" index="1" type="java.lang.Integer"></constructor-arg> <constructor-arg value="男"></constructor-arg> </bean>
-
-
p名称空间注入(了解)
本质上是调用的set方法
导入头文件约束
<!-- set方法注入属性--> <bean id="person06" class="com.zb.bean.Person" p:age="18" p:email="xiaoming@atguigu.com" p:lastName="哈哈" p:gender="男"> </bean>
-
c命名空间注入(了解)
c(构造: Constructor)命名空间注入,本质上使用的是构造器注入
导入头文件约束: xmlns:c=“http://www.springframework.org/schema/c”<bean id="person05" class="com.xiao.bean.Person" c:lastName="zhangsan" c:age="30" c:email="zhangsan@qq.com" c:gender="1"> </bean>
六、注入不同类型的属性值
新建一个Person类:
public class Person {
private String lastName = "张三";
private Integer age;
private String gender;
private String email;
private Car car;
private List<Object> objs;
private Map<String,Object> maps;
private Properties properties;
}
6.1 字面量
可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
注意:若字面值中包含特殊字符,可以使用<![CDATA[ 字面值 ]]>把字面值包裹起来
<bean id="user" class="com.zb.spring5.User">
<!--属性值包含特殊符号
方法一: 把<>进行转义 < >
方法二: 把带特殊符号内容写到 CDATA
-->
<!--方法一-->
<property name="name" value="<<南京>>"></property>
<!--方法二-->
<!-- <property name="name">-->
<!-- <value><![CDATA[<<南京>>]]></value>-->
<!-- </property>-->
</bean>
6.2 注入null
如果有属性给了初始值,想注入为null,则在property内部需要使用null标签:
<bean id="person01" class="com.zb.bean.Person">
<property name="lastName">
<null/>
</property>
</bean>
6.3 注入bean
-
可以使用ref引用外部的bean
<!-- 先注册一个Car对象 --> <bean id="car01" class="com.zb.bean.Car"> <property name="carName" value="宝马"></property> <property name="color" value="绿色"></property> <property name="price" value="30000"></property> </bean> <bean id="person01" class="com.zb.bean.Person"> <!-- ref:代表引用外面的一个值 引入其他bean --> <property name="car" ref="car01"></property> </bean>
注意:ref是严格的引用,通过容器拿到的Car实例就是Person实例中的Car属性
ApplicationContext ioc = new ClassPathXmlApplicationContext("ApplicationContext2.xml"); Person bean = (Person)ioc.getBean("person01"); Car car01 = (Car)ioc.getBean("car01"); System.out.println(car01 == bean.getCar());//true
-
也可以引用内部bean
内部bean声明直接包含在<property>或<constructor-arg>元素里,不需要设置任何id或name属性内部bean只能内部使用,外面获取不到<bean id="person02" class="com.zb.bean.Person"> <property name="lastName" value="张三"></property> <property name="car"> <!-- 对象我们可以使用bean标签创建 car = new Car() 引入内部bean--> <bean id="car02" class="com.zb.bean.Car"> <property name="carName" value="自行车"></property> </bean> </property> </bean>
6.4 集合类型赋值
-
数组
array标签+value标签<property name="books"> <array> <value>西游记</value> <value>红楼梦</value> <value>水浒传</value> </array> </property>
-
List
list标签+value标签:<bean id="person03" class="com.zb.bean.Person"> <property name="books"> <!-- 相当于new ArrayList<Book>(); --> <list> <bean class="com.zb.bean.Book"> <property name="bookName" value="马克思"></property> </bean> <ref bean="book01"/> </list> </property> </bean>
-
Map
<map>标签里可以使用多个<entry>作为子标签。每个条目包含一个键和一个值。<bean id="person04" class="com.zb.bean.Person"> <property name="maps"> <!-- 相当于new LinkedHashMap<>(); --> <map> <entry key="key01" value="value01"></entry> <entry key="key02"> <value>value02</value><!-- 两种方式相同 --> </entry> <entry key="key03" value="18"></entry> <entry key="key04" value-ref="book01"></entry> <entry key="key05"> <bean class="com.zb.bean.Car"> <property name="carName" value="宝马"></property> </bean> </entry> <entry key="key06" value-ref="car01"> </entry> </map> </property> </bean>
-
Properties
props标签:<bean id="person05" class="com.zb.bean.Person"> <property name="properties"> <!-- properties = new Properties();所有的k=v都是String --> <props> <!-- k=v都是string;值直接写在标签体中 --> <prop key="username">root</prop> <prop key="password">123456</prop> </props> </property> </bean>
-
在集合里面设置对象类型值
<!--创建多个 course 对象--> <bean id="course1" class="com.atguigu.spring5.collectiontype.Course"> <property name="cname" value="Spring5 框架"></property> </bean> <bean id="course2" class="com.atguigu.spring5.collectiontype.Course"> <property name="cname" value="MyBatis 框架"></property> </bean> <!--注入 list 集合类型,值是对象--> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property>
-
util名称空间
如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用。
配置集合类型的bean需要引入util名称空间头文件约束:xmlns:util="http://www.springframework.org/schema/util" <!-- util名称空间创建集合类型的bean;方便别人引用 --> <!-- 相当于new LinkedHashMap<>() --> <util:map id="myMap"> <entry key="key01" value="value01"></entry> <entry key="key02"> <value>value02</value><!-- 两种方式相同 --> </entry> <entry key="key03" value="18"></entry> <entry key="key04" value-ref="book01"></entry> <entry key="key05"> <bean class="com.zb.bean.Car"> <property name="carName" value="宝马"></property> </bean> </entry> </util:map> <!-- 提取 map 集合类型属性注入使用--> <bean id="person04" class="com.zb.bean.Person"> <property name="maps" ref="myMap"></property> </bean>
6.5 级联属性赋值
propetry标签中的name标签,可以使用级联属性,修改属性的属性,但是原来属性的值会被修改
<bean id="person07" class="com.zb.bean.Person">
<property name="car" ref="car01"></property>
<!-- 通过级联属性赋值修改car对象中的价格 -->
<property name="car.price" value="900000"></property>
</bean>
七、通过工厂创建bean
Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
-
普通 bean:在配置文件中定义 bean 类型就是返回类型
-
工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样(FactoryBean)
第一步 创建类,让这个类作为工厂 bean,实现接口 FactoryBean 第二步 实现接口里面的方法,在实现的方法中定义返回的 bean 类型
7.1 静态工厂(了解)
-
工厂本身不用创建对象,通过静态方法调用,对象 = 工厂类.工厂方法名( )
-
声明通过静态方法创建的bean需要在bean的class属性里指定静态工厂类的全类名,同时在factory-method属性里指定工厂方法的名称。最后使用元素为该方法传递方法参数。
public class AirPlaneStaticFactory {
//AirPlaneStaticFactory.getAirPlane()
public static AirPlane getAirPlane(String jzName){
System.out.println("AirPlaneStaticFactory...正在为你造飞机");
AirPlane airPlane = new AirPlane();
airPlane.setFdj("太行");
airPlane.setFjsName("lfy");
airPlane.setJzName(jzName);
airPlane.setPersonNum(300);
airPlane.setYc("198.98m");
return airPlane;
}
}
<!-- 1、静态工厂(不需要创建工厂本身)
factory-method="getAirPlane":指定哪个方法是工厂方法
class:指定静态工厂全类名
constructor-arg:可以为方法传参
-->
<bean id="airPlane01" class="com.zb.factory.AirPlaneStaticFactory"
factory-method="getAirPlane">
<!-- 可以为方法指定参数 -->
<constructor-arg value="李四"></constructor-arg>
</bean>
7.2 实例工厂(了解)
-
工厂本身需要创建对象,先创建工厂对象,再通过工厂对象创建所需对象
-
实现方式:
1、配置工厂类实例的bean 2、在factory-method属性里指定该工厂方法的名称 3、使用 construtor-arg 元素为工厂方法传递方法参数
public class AirPlaneInstanceFactory {
// new AirPlaneInstanceFactory().getAirPlane();
public AirPlane getAirPlane(String jzName){
System.out.println("AirPlaneInstanceFactory...正在造飞机");
AirPlane airPlane = new AirPlane();
airPlane.setFdj("太行");
airPlane.setFjsName("lfy");
airPlane.setJzName(jzName);
airPlane.setPersonNum(300);
airPlane.setYc("198.98m");
return airPlane;
}
}
<!-- 实例工厂使用 -->
<bean id="airPlaneInstanceFactory" class="com.zb.factory.AirPlaneInstanceFactory">
</bean>
<!-- factory-bean:指定当前对象创建使用哪个工厂
1、先配置出实例工厂对象
2、配置我们要创建的AirPlane使用哪个工厂创建
1)、factory-bean:指定使用哪个工厂实例
2)、factory-method:使用哪个工厂方法
-->
<bean id="airPlane02" class="com.zb.bean.AirPlane"
factory-bean="airPlaneInstanceFactory" factory-method="getAirPlane">
<constructor-arg value="王五"></constructor-arg>
</bean>
7.3 FactoryBean
-
实现了FactoryBean接口的类,是Spring可以认识的工厂类,Spring会自动调用工厂方法创建对象。
-
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂bean的getObject方法所返回的对象。
/**
* 实现了FactoryBean接口的类是Spring可以认识的工厂类;
* Spring会自动调用工厂方法创建实例
* @author ZB
*
*/
public class MyFactoryBeanImple implements FactoryBean<Book> {
/**
* getObject:工厂方法;
* 返回创建的对象
*/
@Override
public Book getObject() throws Exception {
System.out.println("MyFactoryBeanImple..帮你创建对象..");
Book book = new Book();
book.setBookName(UUID.randomUUID().toString());
return book;
}
/**
* 返回创建的对象的类型;
* Spring会自动调用这个方法来确认创建的对象是什么类型
*/
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Book.class;
}
/**
* isSingleton:是否是单例
* false:不是单例
* true:是单例
*/
@Override
public boolean isSingleton() {
// TODO Auto-generated method stub
return false;
}
}
@Test
public void test01() {
//1 加载 spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
Book book = context.getBean("myFactoryBeanImple", Book.class);
System.out.println(book);//Book [bookName=87404f3e-3b55-476d-9107-2fb95c1bc667, author=null]
}
在配置文件中注册工厂对象
<!-- FactoryBean★(是Spring规定的一个接口);
只要是这个接口的实现类,Spring都认为是一个工厂;
1、ioc容器启动的时候不会创建实例
2、FactoryBean;获取的时候的才创建对象,而且为多实例类型
-->
<bean id="myFactoryBeanImple" class="com.zb.factory.MyFactoryBeanImple" >
</bean>
注意:IOC容器启动时不会创建实例,使用getBean时才会创建