1. 什么是Spring
1.1 Spring的概述
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
1.2 Spring的核心
-
三大核心
-
IOC:Inverse Of Control: 反转控制
-
AOP:Aspect Oriented Programming:面向切面编程
-
DI:依赖注入 他是IOC的具体实现
-
1.3 Spring的发展历程
-
1997 年 IBM 提出了 EJB 的思想
-
1998 年,SUN 制定开发标准规范 EJB1.0
-
1999 年,EJB1.1 发布
-
2001 年,EJB2.0 发布
-
2003 年,EJB2.1 发布
-
2006 年,EJB3.0 发布
Rod Johnson(spring 之父)
2017 年 9 月份发布了 spring 的最新版本 spring 5.0 通用版(GA)
扩展
EJB是Enterprise Java Beans技术的简称, 又被称为企业Java Beans。这种技术最早是由美国计算公司研发出来的。EJB技术的诞生标志着Java Beans的运行正式从客户端领域扩展到服务器领域。在电子商务领域运用EJB技术可以简化应用系统的开发, 这是由该技术的结构和特点所决定的
EJB (Enterprise Java Beans) 是基于分布式事务处理的企业级应用程序的组件。Sun公司发布的文档中对EJB的定义是:EJB是用于开发和部署多层结构的、分布式的、面向对象的Java应用系统的跨平台的构件体系结构
spring是轻量级的 EJB是重量级的 轻量级的 操作简单 容易部署 轻量级的更安全
1.4 Spring的优势
-
方便解耦,简化开发 (IOC)
通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造 成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可 以更专注于上层的应用。
-
AOP 编程的支持
通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
-
声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
-
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
-
方便集成各种优秀框架
Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。
-
降低JavaEE API的使用难度
Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的 使用难度大为降低。
-
Java源码是经典学习范例
Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以 及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例
2. IOC的概念和作用
2.1 程序的耦合
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。 在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
2.2 耦合的分类
-
内容耦合
当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
-
公共耦合
两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
-
外部耦合
一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
-
控制耦合
一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
-
标记耦合
若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
-
数据耦合
模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
-
非直接耦合
两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
总结:
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
内聚与耦合:
内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。
耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。
内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
2.3 耦合的解决方案
一款健康的软件最好都应该是 高内聚 低耦合 。耦合简单来说就是程序之间依赖的关系,包括类之间、方法之间的依赖关系,
解耦:
降低程序之间的依赖关系,但是仅仅降低程序之间的依赖关系,并不能完全消除。实际开发中应该做到编译时不依赖,运行时才依赖。
解决类之间的依赖关系思路:
-
使用反射创建对象,尽量避免使用new关键字
-
通过读取配置文件来获取要创建对象的全限定类名、
解耦的解决方案:
-
通过反射降低耦合度
-
通过工厂模式降低耦合度
-
通过IOC容器降低耦合度
2.3.1 通过反射解耦
反射的核心在于不直接new对象,而是通过程序运行的时候加载使用Class类加载对象。如使用jdbc连接数据库操作 举例
public class AccountTest {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//注册驱动
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());//说明耦合度比较高
Class.forName("com.mysql.jdbc.Driver");
//获取连接对象
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db1","root","123456");
//获取PreparedStatement对象
PreparedStatement pst = conn.prepareStatement("select * from account");
//执行sql语句
ResultSet rs = pst.executeQuery();
//遍历结果集
while(rs.next()){
System.out.println(rs.getString("name"));
}
//关流
rs.close();
pst.close();
conn.close();
}
}
重点在于Class.forName("com.mysql.jdbc.Driver");加载mysql驱动对象,反射的方式可以解耦 但是写法比较复杂 也易出错
2.3.2 通过工厂模式解耦
工厂模式是一种设计模式,java常见的设计模式有很多,比如单例模式,工厂模式,建造者模式等等。工厂模式可以理解为编写一个创建对象的工厂,每次都通过工厂来创建,这样避免重复创建对象。
第一步:在resource包下创建bean.properties配置文件
student=com.demo.bean.Student
第二步:编写BeanFactory工厂类
public class BeanFactory {
private static Properties pro;
static {
try {
pro = new Properties();
//读取配置文件
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
pro.load(is);
} catch (IOException e) {
e.printStackTrace();
}
}
//获取对象的方法
public static Object getBean(String beanName) {
Object obj = null;
try {
obj = Class.forName(pro.getProperty(beanName)).newInstance();//每次通过构造方法来创建对象
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
第三步:测试类
@Test
public void factoryTest(){
//通过工厂获取对象
Student student = (Student) BeanFactory.getBean("student");
System.out.println(student);
student.study();
}
3. Spring的IOC容器
IOC控制反转:把创建对象的权力交给框架。它包含了依赖注入和依赖查找。 作用:削减计算机程序之间的 耦合,解除我们程序之间的依赖关系。
3.1 SpringIOC入门案例
快速入门步骤
-
导入spring的依赖
-
创建业务类 Service Dao
-
创建spring的配置文件
-
在配置问文件中对bean进行配置
-
bean的属性
-
id 是一个唯一标识,后期用于根据这个id从 ioc容器中 取出对象
-
class 是类的全路径类名 (包名 + 类名)
-
-
-
创建测试类
-
获取ioc容器
-
从容器中获取对象
-
3.1.1. 导入spring的依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
</dependencies>
3.1.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="student" class="com.doyens.bean.Student"></bean>
</beans>
3.1.4.创建测试类并测试
@org.junit.Test
public void factoryTest(){
//通过工厂获取对象
Student student = (Student) BeanFactory.getBean("student");
System.out.println(student);
student.study();
}
3.2 获取对象的三种方式
-
Object getBean(String beanId);
-
根据bean的id来获取 需要强转
-
-
T getBean(String beanId,Class<T> class)
-
根据bean的id来获取 不需要强转
-
-
T getBean(Class<T> class)
-
根据bean的类型来获取 不需要强转,需要保证ioc容器中只有唯一的一个该类型的bean,如果有多个该类型的bean,则会发生异常。不推荐
-
3.3 获取IOC容器的2种方式
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式
3.3.1 BeanFactory
这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。
//1.创建IOC容器
Resource resource = new ClassPathResource("beans.xml");
BeanFactory beanFactory = new XmlBeanFactory(resource);
//2.从ioc容器中获取对象
Student student = beanFactory.getBean("student", Student.class);
System.out.println("student = " + student);
3.3.2 ApplicationContext
BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。
ApplicationContext的三个实现类:
-
ClassPathXmlApplicationContext:
用于加载类路径下的配置文件。
-
FileSystemXmlApplicationContext:
用于加载磁盘路径下的配置文件。
-
AnnotationConfigApplicationContext:
用于读取注解 创建IOC容器。
小提示:
以后在 Spring 环境下看到一个类或接口的名称中包含 ApplicationContext,那基本就可以断定,这个类或接口与 IOC 容器有关。
@org.junit.Test
public void applicaitonContextTest_01(){
//1.获取IOC容器
ApplicationContext app = new ClassPathXmlApplicationContext("bean.xml");
//2.从容器中获取对象
Student student = app.getBean("student",Student.class);
System.out.println(student);
student.study();
}
@org.junit.Test
public void applicaitonContextTest_02(){
//1.获取IOC容器
ApplicationContext app = new FileSystemXmlApplicationContext("D:\\beans.xml");
//2.从容器中获取对象
Student student = app.getBean("student",Student.class);
System.out.println(student);
student.study();
}
3.3.3 BeanFactory 和 ApplicationContext的区别
BeanFactory 才是 Spring 容器中的顶层接口。 ApplicationContext 是它的子接口。
区别:创建对象的时间点不一样。
-
ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
-
BeanFactory:什么获取什么时候创建对象。
4. IOC中bean的管理
4.1 bean标签
作用:
用于配置对象让 spring 来创建的。 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性: id:给对象在容器中提供一个唯一标识。用于获取对象。 class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。 scope:指定对象的作用范围。
-
singleton :默认值,单例的.
-
prototype :多例的.
-
request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
-
session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
-
global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么 globalSession 相当于 session.
-
init-method:用于指定对象的初始化方法。当对象创建时,调用的方法。 作用:用于加载资源
-
destroy-method:用于指定对象销毁的方法。当对象销毁时,调用的方法 作用:用于释放资源
4.2 bean的作用范围和生命周期
单例对象:
-
scope="singleton"
-
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期:
-
对象出生:当应用加载,创建容器时,对象就被创建了。
-
对象活着:只要容器在,对象一直活着。
-
对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
多例对象:
-
scope="prototype"
-
每次访问对象时,都会重新创建对象实例。
生命周期:
-
对象出生:当使用对象时,创建新的对象实例。
-
对象活着:只要对象在使用中,就一直活着。
-
对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
5. Spring_DI 依赖注入
DI 是(Dependency Injection)的简称 就是依赖注入 什么是依赖注入呢?
IOC容器可以降低程序间的依赖关系,但是并不能完全消除。 Controller---->Service----->Dao:将依赖关系交由框架来维护。 所以依赖关系的维护,就成为依赖管理。 DI:就是IOC的具体体现。以一些预先定义好的方式,接收来自容器中的资源。
-
构造函数
-
setter方法 p命名空间
-
注解
5.1 依赖注入的三种方式
5.2 通过构造函数注入数据
创建一个Student类 其中地址的属性是一个对象
/**
* 类就是模板
* 每一个student对象都必须要有name age sex address
*/
public class Student {
private String name;
private int age;
private String sex;
private Address address;//地址中包含了省市区
//省略set和get方法
public class Address {
private String sheng;
private String shi;
private String qu;
}
在bean标签中 通过构方法的方式来给对象属性赋值
<bean id="student2" class="com.zrrd.bean.Student">
<constructor-arg name="name" value="李四"></constructor-arg>
<constructor-arg name="age" value="30"></constructor-arg>
<constructor-arg name="sex" value="男"></constructor-arg>
<constructor-arg name="address" ref="address2"></constructor-arg>
</bean>
<bean id="address2" class="com.zrrd.bean.Address">
<constructor-arg name="sheng" value="湖北省"></constructor-arg>
<constructor-arg name="shi" value="武汉市"></constructor-arg>
<constructor-arg name="qu" value="武昌区"></constructor-arg>
</bean>
<constructor-arg>标签:
-
name:属性名字
-
value:对应的属性值
注意:
使用有参构造方式 必须给所有字段都赋值 如果少字段 会报错
5.3 通过set方法注入数据
<bean id="address" class="com.zrrd.bean.Address">
<property name="sheng" value="贵州"></property>
<property name="shi" value="六盘水"></property>
<property name="qu" value="六枝"></property>
</bean>
<bean id="address2" class="com.zrrd.bean.Address">
<property name="sheng" value="河南"></property>
<property name="shi" value="安阳"></property>
<property name="qu" value="林州"></property>
</bean>
<!--配置student对象-->
<bean id="student" class="com.zrrd.bean.Student">
<property name="name" value="旺财"></property>
<property name="age" value="32"></property>
<property name="sex" value="男"></property>
<property name="address" ref="address2"></property>
</bean>
<<property>标签:
使用set方式给对象的属性赋值
属性
-
name:属性名称
-
value:对应的属性值
和有参构造的区别:
可以单独给某个属性赋值 不会报错
5.4 通过p命名空间注入数据
使用p命名空间配置一个地址对象,其实p命名空间注入数据也是走set方法
使用步骤
-
导入p命名空间 xmlns:p="http://www.springframework.org/schema/p"
-
在配置bean的时候 通过p:属性名来赋值
-
基本数据类型和string赋值:p:属性名="值"
-
其他bean赋值:p:属性名-ref = "其他bean的id"
<!--通过p命名空间配置student对象-->
<bean id="student" class="cn.doyens.domain.Student" p:name="张三" p:age="25" p:sex="男" p:address-ref="address1">
</bean>
5.5 给复杂类型注入数据
配置一个teacher对象 通过set方法来赋值
给数组赋值:array; 给list集合赋值:list 给set集合赋值:set。 他们都属于单列的集合,标签是可以混用的
给map集合赋值:map
结论:结构相同 标签可以混用
public class Teacher {
private String name;
private int age;
private String[] hobby;
private List<String> studentList;
private Map<Integer, Integer> money;
}
在bean标签中注入数据
<bean id="teacher" class="com.zrrd.bean.Teacher">
<property name="name" value="郭老师"></property>
<property name="age" value="18"></property>
<!--给数组赋值-->
<property name="hobby">
<array>
<value>学习</value>
<value>唱歌</value>
<value>打游戏</value>
</array>
</property>
<!--给List集合赋值-->
<property name="studentList">
<list>
<value>张三</value>
<value>李四</value>
</list>
</property>
<!--给Map集合赋值-->
<property name="money">
<map>
<entry key="4" value="4000"></entry>
<entry key="5" value="8000"></entry>
<entry key="6" value="1200"></entry>
</map>
</property>
</bean>
5.6 bean的外部注入和内部注入
如果一个bean的属性类型 是另外一个bean 可以通过 <property name="address" ref="address"></property> 引入过来注入数据。还可以直接在当前bean的内部直接注入
<!--配置一个student对象-->
<bean id="student2" class="com.zrrd.bean.Student">
<property name="name" value="李四"></property>
<property name="age" value="22"></property>
<property name="sex" value="女"></property>
<!--内部bean-->
<property name="address">
<bean class="com.zrrd.bean.Address">
<property name="sheng" value="贵州省"></property>
<property name="shi" value="贵阳市"></property>
<property name="qu" value="白云区"></property>
</bean>
</property>
</bean>
5.7 引入其他的配置文件
通过import标签来引入
<?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">
<import resource="bean.xml"></import>
</beans>
6. 基于注解的IOC配置
6.1 什么是注解
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率。
和xml配置文件一样的。它 本身不能执行,仅仅是一个标记。框架检测到这个标记的位置,根据这个标记的位置和标记的功能来完成对象的代码。本质上也是执行java代码。
6.2 Spring中注解的分类
6.2.1 原始注解
作用:Spring原始注解主要是替代<Bean>的配置
| 注解 | 说明 |
|---|---|
| @Component | 使用在类上用于实例化Bean |
| @Controller | 使用在web层类上用于实例化Bean |
| @Service | 使用在service层类上用于实例化Bean |
| @Repository | 使用在dao层类上用于实例化Bean |
| @Autowired | 使用在字段上用于根据类型依赖注入 |
| @Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
| @Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
| @Value | 注入普通属性 |
| @Scope | 标注Bean的作用范围 |
| @PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 |
| @PreDestroy | 使用在方法上标注该方法是Bean的销毁方法 |
6.2.1.1 用于创建对象的
-
@Component
-
用于替代bean标签 创建当前类的对象 并存入到IOC容器中
-
id默认就是类名,并且首字母小写
-
id也可以通过value属性来指定
-
如果注解有且仅有一个value属性时 value可以省略不写
-
-
@Controller
-
于注解控制器的 也就是表现层。 就是controller层的注解
-
-
@Service
-
用于注解业务逻辑层 就是service层的注解
-
-
@Repository
-
用于注解持久层 就是dao层
-
如果类不属于以上三层 那么就使用这个注解
四个注解的作用是一样的 仅仅是供程序员区分具体的类属于那一层。
注意:
使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。
<!--注解的组件扫描-->
<context:component-scan base-package="com.zrrd"></context:component-scan>
6.2.1.2 用于注入数据的
他们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的
-
Autowired
-
作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
-
如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
-
-
Qualifier
-
作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以
-
-
Resource
-
作用:直接按照bean的id注入。它可以独立使用
注意事项:
@Resource javax.annotation包中的, 而Spring项目中缺少javax.annotation包的依赖, 所以我们把这个依赖加上就可以了
<dependency> <groupId>javax.annotation</groupId> <artifactId>javax.annotation-api</artifactId> <version>1.2</version> </dependency>以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
-
另外,集合类型的注入只能通过XML来实现。
-
6.2.1.3 改变作用范围的
他们的作用就和在bean标签中使用scope属性实现的功能是一样的
-
Scope
-
作用:用于指定bean的作用范围
-
value:指定范围的取值。常用取值:singleton(默认值) prototype
-