1. 概述
-
Spring是一个开源框架。
-
Spring为简化企业级开发而生,使用Spring,JavaBean就可以实现很多以前要靠EJB才能实现的功能。同样的功能,在EJB中要通过繁琐的配置和复杂的代码才能够实现,而在Spring中却非常的优雅和简洁。[EJB是Enterprise Java Beans技术的简称, 又被称为企业Java Beans]
-
Spring是一个IOC(DI)和AOP容器框架
一句话总结就是:Spring是分层的JavaSE/EE应用full=stack轻量级开源框架.
2. Spring的特点和优点
特点
(1)、方便解耦、简化开发
通过Spring提供的IOC容器,我们可将对象之间的依赖关系交由Spring进行控制,避免了硬编码所造成的过度程序耦合,有了Spring,用户不必在为实现单例、属性文件解析等这些需求而编写代码,可以使开发者更加关注上层应用开发。
(2)、AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程。
(3)、声明式事务的支持
在Spring中,我们可以通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
(4)、方便程序的测试
Spring对Junit4支持,可通过注解方便的测试Spring程序
(5)、方便集成各种优秀的框架
Spring不排斥各种优秀的开源框架,Spring可以降低各种框架的使用难度。
优点
- 非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
- 依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现
- 面向切面编程:Aspect Oriented Programming——AOP
- 容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
- 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象
- 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)
3. 什么是耦合和内聚
耦合性(Coupling):也叫耦合度,是对模块间关联程度的度量。
- 在软件工程中,耦合指的就是对象之间的依赖性,对象之间的耦合度越高,维护成本就越高,因此对象的设计应使类和构件之间的耦合最小。
- 软件设计中通常用耦合度和内聚度来衡量模块独立程序的标准。
- 划分模块的一个准则就是高内聚低耦合
- 内聚:标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。
- 内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事
- 内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其它模块之间是低耦合
- 在进行软件设计使,应做到高内聚、低耦合
4. Spring的版本说明
- Spring 1.x
- 在Spring 1.x中,都是通过xml文件配置bean,随着项目的不断扩大,需要将xml配置分放到各个配置文件中,需要频繁的在Java类和XML配置文件中切换。
- Spring 2.x
- 在Spring 2.x中,因为JDK1.5带来了注解的支持,在之中Spring可以使用注解对Bean进行声明和注入,大大减少了XML配置文件,简化了开发
- Spring 3.x 和Spring 4.x
- 从Spring 3.x开始提供了Java配置方式,使用Java配置方式可以更好的理解配置的Bean,Spring4.x和Spring boot都推荐使用Java配置方式
- Spring 5.x
- Spring 5 Framework 基于一种反应式基础而构建,而且是完全异步和非阻塞的。只需少量的线程,新的事件循环执行模型就可以垂直扩展
-
5. Spring的体系结构解析
- Core Container:Spring的核心容器
- 中间层技术:AOP 、Aspects【AOP是规范思想、Aspects是思想的实现】
- 应用层技术:Data Access(数据访问)Integration(数据集成)Web集成、Web实现等
- Test:测试
从这个框架我们可以看出,Spring主要做的事情是集成,将其他东西集成进来成为一个整体。
二、IOC 控制反转
IOC(Inversion Of Control)控制反转,Spring反向控制应用程序所需要使用的外部资源
Spring控制的资源全部放置在Spring容器中,该容器称为IOC容器
Spring控制了资源,我们创建对象从主动创建到被动使用,由Spring提供给我们
IOC体验
1、创建Maven工程,导入Spring坐标
2、模拟业务层与表现层,抒写接口和实现类
3、建立Spring配置文件,配置放在IOC容器的资源
4、测试数据
1、创建Maven工程,导入Spring坐标
导入Spring5.x依赖
<dependencies>
<!-- spring上下文依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
</dependencies>
2、模拟业务层与表现层,抒写接口和实现类
创建持久层层接口、接口实现类
/**
* 持久层接口
*/
public interface UserDao {
/**
* 保存数据
*/
public void save();
}
/**
* 持久层接口实现类
*/
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDaoImpl 持久层保存数据");
}
}
创建业务层接口、接口实现类
/**
* 业务层接口
*/
public interface UserService {
public void save();
}
/**
* 业务层接口实现类
*/
public class UserServiceImpl implements UserService {
// 传统的话,我们需要 UserDao userDao = new UserDaoImpl();
// new的方式实例化对象,现在我们使用Spring,对象实例的操作交给Spring,我们只需要声明成员变量就可以了
// 将对象的管理权力交给Spring
private UserDao userDao;
/**
* 提供set方法,等下我们要注入依赖
*/
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
3、建立Spring配置文件,配置放在IOC容器的资源
spring的配置文件一般命名为:applicationContext.xml
下面是Spring5.1.9的文档支持,在这里可以找到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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 引入业务层接口实现类的资源 -->
<bean id="userService" class="cn.guan.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="cn.guan.dao.impl.UserDaoImpl"/>
</beans>
4、测试数据
创建UserTest测试类
/**
* 测试数据
*/
public class UserTest {
public static void main(String[] args) {
// 1、使用上下文程序对象ApplicationContext加载配置文件
// 因为ApplicationContext是接口,所以用实现类ClassPathXmlApplicationContext来实例化对象
// 参数传入Spring配置文件的名称
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 2、获取定义的bean资源,根据id ,第二个参数是返回的类型
UserService userService = applicationContext.getBean("userService", UserService.class);
// 3、使用对象执行方法
userService.save();
}
}
执行结果
接下来我们对上面迷惑的知识点进行解读!
IoC 反转控制基于XML的使用
bean标签 <bean> </bean>
- bean标签的作用:用于配置被Spring容器管理的bean的信息
- 默认情况下它调用的是类中的 无参数构造函数,如果没有无参构造则不能创建成功
bean标签的属性
- id:给对象在容器中提供一个唯一标识,用于获取对象
- class:指定类的全限定类名,用于反射创建对象,默认情况下调用无参数构造函数。
- init-method:指定类中的初始化方法名称
- destory-method:指定类中销毁方法名称,比如DataSource的配置中一般需要指定destory-method="close".
- scope:指定对象的作用范围。取值有两个【singleton | prototype】,默认情况下是singleton单例的
- singleton,生命周期如下
- 对象出生:当应用加载,创建容器时候,对象就被创建了。
- 对象活着:只要容器存在,对象一直活着。
- 对象死亡:当应用卸载,销毁容器的时候,对象就被销毁了。
- prototype:多例的,每次访问对象的时候,都会重新创建对象实例。生命周期如下
- 对象出生:当使用对象的时候,创建新的对象实例
- 对象活着:只要对象在使用中,就一直活着
- 对象死亡:对对象长时间不用时候,就被Java的垃圾回收器回收了
- request:将Spring创建的Bean对象存入到request域中
- session:将Spring创建的Bean对象存入到session域中
- global session:WEB项目中,应用在Protlet环境,如果没有Portlet环境,那么globalSession相当于session.
- singleton,生命周期如下
bean标签的属性属性测试
测试bean标签的scope属性
基于上面的环境,我们将userService的bean的scope修改为singleton,看下创建的对象地址
测试
/**
* 测试数据
*/
public class UserTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
UserService userService1 = applicationContext.getBean("userService", UserService.class);
UserService userService2 = applicationContext.getBean("userService", UserService.class);
System.out.println(userService);
System.out.println(userService1);
System.out.println(userService2);
}
}
测试结果,创建的地址都相同,单例模式
当我们将scope修改为prototype多例的,看看测试结果
测试结果:对象地址都不同。多例模式
测试bean标签的生命周期属性
bean的生命周期属性有:init-method、destory-method,定义bean对象在初始化或销毁时候完成的工作
1、我们在UserServiceImpl业务层接口实现类编写init初始化方法和destory销毁方法
2、在bean标签指定初始化方法
3、测试
/**
* 测试数据
*/
public class UserTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
}
测试结果:destory没有执行,对象实例化后在执行init方法
由于程序执行的太快,destory方法执行的时候虚拟机就已经结束了,所以使用ClassPathXmlApplicationContext对象强制关闭容器
添加这两行代码,容器关闭交由Spring控制,即可调用销毁方法
细节
- 当scope="singleton"时候,Spring容器中有且仅有一个对象,init方法在创建容器时候仅仅执行一次
- 当scope="prototype"时候,Spring容器要创建同一类型的多个对象,init方法在每个对象创建的时候均执行一次
- 当scope="singleton"时候,关闭容器会导致bean实例的摧毁,调用destory方法一次
- 当scope="prototype"时候,对象的销毁由垃圾回收机制gc()控制,destory方法将不会执行
IOC反转控制 - bean实例化的三种方式
第一种:使用默认无参数构造方法
在默认情况下,它会根据默认无参数构造方法来创建类对象
如果bean中没有默认无参数构造方法,将会创建bean失败
测试
我们将业务层接口实现类的无参数构造方法替换成有参数,我们在测试数据
我们在业务层接口实现类写有参数构造方法,不写无参数构造方法【依托之前代码】
测试数据
/**
* 测试数据
*/
public class UserTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
}
测试结果
会报BeanInstantiationException 异常,提示No default constructor found;没有发现默认的构造方法
所以我们在默认使用默认无参数构造方法实例化对象的时候,需要给出无参数构造方法
解决方案:给出无参数构造方法
测试结果
执行成功
第二种:静态工厂实例化对象
使用staticFactory静态工厂类中的静态方法createUserService创建对象,并存入Spring容器
1、创建静态工厂类
创建静态工厂类
/**
* 静态工厂
* 提供静态方法实例化对象
*/
public class UserServiceStaticFactory {
public static UserService getUserService() {
return new UserServiceImpl();
}
}
2、编写bean,将UserServiceImpl接口实现类的class指向改为工厂,然后使用factory-method工厂方法属性指向静态方法
<!-- 引入业务层接口实现类的资源 -->
<bean id="userService" scope="singleton"
class="cn.guan.factory.UserServiceStaticFactory"
factory-method="getUserService"
>
</bean>
这个了解不太深,大家了解即可
第三种 - 实例工厂实例化对象
实例工厂用的是普通方法
public class UserServiceFactory2 {
public UserService getUserService() {
System.out.println("实例工厂创建对象执行了......");
return new UserServiceImpl();
}
}
编写bean
<!-- 实例工厂要造个bean -->
<bean id="factoryBean" class="cn.guan.factory.UserServiceFactory2"/>
<bean id="userService" factory-bean="factoryBean" factory-method="getUserService"/>
这个了解不太深,大家了解即可
DI 依赖注入
依赖注入概述
DI(Dependency Injection)依赖注入,应用程序运行依赖的资源由Spring为其提供,资源进入应用程序的方式称为注入
DI依赖注入和IOC控制反转相伴而行
IOC(Inversion Of Controller)控制反转,Spring反向控制应用程序所需要使用的外部资源
依赖注入和控制反转是在不同的角度看的,但是发生是同时发生的
在Spring角度上看是IOC模式
在应用程序角度看是DI模式
什么是依赖?
依赖指的就是Bean实例中的属性,依赖(属性)分为:简单类型(8中基本类型和String类型)的属性、POJO类型的属性、集合数组类型的属性
为什么要进行依赖注入?
我们的程序在编写过程中,通过控制反转,把对象的创建交给了Spring,但是代码中不可能出现没有依赖的情况。
如果一个bean实体类中包含了一些属性,那么Spring帮我们实例化了bean对象之后,也需要将对应的属性信息进行赋值操作,这种属性赋值操作,就是所谓的依赖注入(获取值,注入属性)
依赖注入的两种方式
set方法注入(重要)构造方法注入(次要)
set方法注入
手动装配方式(XML方式)
1、需要配置bean标签的子标签property
2、需要配置的bean中指定setter方法
set方法注入案例演示
1、创建Maven工程,导入Spring-context上下文依赖
2、模拟业务层接口和业务层接口实现类以及持久层接口和持久层接口实现类
/**
* 持久层接口
*/
public interface UserDao {
public void save();
}
----------------------------------
/**
* 持久层接口实现类
*/
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("持久层保存数据成功!!");
}
}
------------------------
/**
* 业务层接口
*/
public interface UserService {
public void save();
}
----------------------
/**
* 业务层接口实现类
*/
public class UserServiceImpl implements UserService {
/**
* 提供私有属性
*/
private UserDao userDao;
/**
* 提供set方法
*/
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void save() {
userDao.save();
}
}
3、编写配置文件,通过set方法注入业务层接口实现类的dao对象
【注意:属性必须为私有属性,然后提供它的set方法,在bean的name属性填写setXxxXX 的set后面的英文,第一个字母小写】
property属性解读:
name:对应bean中的属性名,要求该属性必须提供可访问的set方法
value:设定非引用类型对应的值,不能与ref同时使用
ref:设定引用类型对应的bean的id,不能与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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="cn.guan.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="cn.guan.dao.impl.UserDaoImpl"/>
</beans>
4、测试数据
public class App {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();
}
}
测试结果:持久层对象实例化成功,调用方法成功
构造方法注入
使用类中的构造方法,给成员变量赋值。赋值的操作用过配置的方式,让Spring给属性注入数据
在bean标签下使用constructor-arg标签
格式:
<bean>
<constructor-arg />
</bean>
构造方法注入案例演示
1、修改业务层接口实现类,注入基本数据类型和引用类型
/**
* 业务层接口实现类
*/
public class UserServiceImpl implements UserService {
/**
* 引用数据类型
*/
private UserDao userDao;
/**
* 基本数据类型
*/
private String username;
private int age;
/**
* 提供无参数构造方法
*/
public UserServiceImpl() {
}
/**
* 提供有参数构造方法
* 将要注入的数据通过形参指名
*/
public UserServiceImpl(UserDao userDao, String username, int age) {
this.userDao = userDao;
this.username = username;
this.age = age;
}
@Override
public void save() {
userDao.save();
System.out.println("username:"+username);
System.out.println("age:"+age);
}
}
2、修改Spring配置文件的bean标签
constructor-arg标签属性解读
index:指定参数在构造函数参数列表的索引位置
name:指定参数在构造函数中的名称
value:它能赋的值是基本数据类型和String类型
ref:它能赋的值是其他bean类型,必须的是配置文件中配置过的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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="cn.guan.service.impl.UserServiceImpl">
<constructor-arg name="username" value="guan"/>
<constructor-arg name="age" value="18"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="cn.guan.dao.impl.UserDaoImpl"/>
</beans>
3、测试,测试成功
了解内容 - 使用p名称空间注入数据
本质上还是调用set方法
1、在配置文件引入p名称空间
xmlns:p="http://www.springframework.org/schema/p"
2、使用p名称的空间的语法
基本数据类型 p:属性名 = "值"
引用类型: p:属性名-ref = "值"
在UserServiceImpl提供set方法
/**
* 业务层接口实现类
*/
public class UserServiceImpl implements UserService {
/**
* 引用数据类型
*/
private UserDao userDao;
/**
* 基本数据类型
*/
private String username;
private int age;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setUsername(String username) {
this.username = username;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void save() {
userDao.save();
System.out.println("username:"+username);
System.out.println("age:"+age);
}
}
修改bean,使用p属性
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="cn.guan.service.impl.UserServiceImpl"
p:username="刘小华"
p:age="13"
p:userDao-ref="userDao"
>
</bean>
<bean id="userDao" class="cn.guan.dao.impl.UserDaoImpl"/>
</beans>
3、测试数据,测试成功
集合类型、数组类型等数据类型的注入
数组类型和集合类型的注入,注入配置方式是一样的
首先我们在UserDao定义属性,提供对应的set方法
/**
* 持久层接口实现类
*/
public class UserDaoImpl implements UserDao {
/**
* 定义集合
*/
private ArrayList<String> arrayList;
/**
* 定义Properties集合
*/
private Properties properties;
/**
* 定义数组
*/
private int[] arr;
/**
* 定义set集合
*/
private HashSet<String> hashSet;
/**
* 定义Map集合
*/
private HashMap<String, String> hashMap;
// 提供对应的set方法
public void setArrayList(ArrayList<String> arrayList) {
this.arrayList = arrayList;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public void setArr(int[] arr) {
this.arr = arr;
}
public void setHashSet(HashSet<String> hashSet) {
this.hashSet = hashSet;
}
public void setHashMap(HashMap<String, String> hashMap) {
this.hashMap = hashMap;
}
@Override
public void save() {
System.out.println("持久层保存数据成功!!");
// 输出集合的数据
System.out.println("List单列集合:"+arrayList);
System.out.println("Properties集合:"+properties);
System.out.println("数组:"+arr);
System.out.println("Set集合:"+hashSet);
System.out.println("Map集合:"+hashMap);
}
}
其次编写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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userService" class="cn.guan.service.impl.UserServiceImpl"
p:username="刘小华"
p:age="13"
p:userDao-ref="userDao"
>
</bean>
<bean id="userDao" class="cn.guan.dao.impl.UserDaoImpl">
<!-- List集合注入数据 name写属性的名称,List集合要在property中使用list子标签 -->
<property name="arrayList">
<list>
<!-- value是要给集合注入的值 -->
<value>List1</value>
<value>List2</value>
<value>List3</value>
<value>List4</value>
</list>
</property>
<!-- 数组注入数据 -->
<property name="arr">
<array>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
</array>
</property>
<!-- properties集合注入数据 -->
<property name="properties">
<!-- 一个key对应一个value -->
<props>
<prop key="name">梅州</prop>
<prop key="msg">非常漂亮</prop>
</props>
</property>
<!-- set注入数据 使用set子标签-->
<property name="hashSet">
<set>
<!-- 如果集合内是简单类型,使用value子标签,如果是POJO引用类型,使用bean表情那 -->
<value>set1</value>
<value>set2</value>
<value>set3</value>
<value>set4</value>
</set>
</property>
<!-- Map注入数据 -->
<property name="hashMap">
<map>
<entry key="city" value="梅州"/>
<entry key="city2" value="广州"/>
</map>
</property>
</bean>
</beans>
测试数据,成功注入数据
后续更新