文章目录
1.初识Spring
在正式开始学习Spring前,先了解下Spring是什么,它做了一些什么事情,它的功能(特性)是什么。
1.1Spring是什么
- Spring 是
分层的 Java SE/EE 应用 full-stack 轻量级开源框架
,以IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核
,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
1.2 Spring的发展历程
1997 年 IBM 提出了 EJB 的思想
1998 年,SUN 制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布
Rod Johnson(spring 之父)
Expert One-to-One J2EE Design and Development(2002)
阐述了 J2EE 使用 EJB 开发设计的优点及解决方案
Expert One-to-One J2EE Development without EJB(2004)
阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形)
2017 年 9 月份发布了 spring 的最新版本 spring 5.0 通用版(GA)
1.3Spring的优势
(1)方便解耦,简化开发
Spring就是一个大工厂,可以将所有对象创建和依赖的关系维护,交给Spring管理。
(2)AOP编程的支持
Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
(3)声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程。
(4)方便程序的测试
Spring对Junit4支持,可以通过注解方便的测试Spring程序。
(5)方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
(6)降低JavaEE API的使用难度
Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低
1.4 Spring的体系结构
2.IOC的概念和作用
2.1 什么是程序的耦合
耦合
:耦合指的就是程序间的依赖关系,在开发中我们应该尽量做到编译期不依赖,运行时才依赖。- 耦合的弊端:程序的独立性很差。
- 耦合的分类:
- 类之间的依赖(比如JDBC中,第一步必须要
com.mysql.jdbc.driver这个jar包
)- 方法之间的依赖
- 我们在开发中,有些依赖关系是必须的,有些依赖关系可以通过优化代码来解除的。下面举一个耦合的实例:
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
}
- 上面的代码表示:在业务层中来调用持久层,
业务层依赖于持久层的接口和实现类
。如果此时没有持久层的实现类,编译将不能通过。这显然不符合我们低耦合的原则,我们应该在实际开发中避免这种情况的出现。
2.2 解决程序的耦合思路
- 那么我们解决耦合的问题呢,此时想到了在JDBC操作注册驱动时,我们为什么不使用
DriverManager 的 register
方法,而是采用Class.forName
的方式?
@Test
public void testJdbc() throws Exception {
// 注册驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver")
// 后续操作...
}
- 对于
DriverManager.registerDriver(new com.mysql.jdbc.Driver())
来说,它在注册驱动时,依赖于MySQL的驱动ajr包- 对于
Class.forName("com.mysql.jdbc.Driver")
来说,这里的com.mysql.jdbc.Driver只是一串字符串,在编译器并不会真正的依赖
,所以即使我们没有这个驱动jar包,程序也不会报错,只有在运行过程中它才会报错,这样就解决了编译器依赖的问题。
-
上面的方式也产生了一个新的问题:
- mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码
-
为了解决这个问题,我们想到使用配置文件的方式来解决。
- 综上所述,我们通过工厂模式来解决耦合的问题:
- 1.使用反射来创建对象,而避免使用new关键字
- 2.通过读取配置文件。来获取要创建的对象的全限定类名。
- 具体来说,即
让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来
。在接下来的使用的时候,直接拿过来用就好了。那么读取配置文件,创建和获取对象的类就是工厂。
配置文件的内容:
accountService = service.impl.AccountServiceImpl
accountDao = dao.impl.AccountDaoImpl
内容为key、value的形式,即
唯一表示=全限定类名
BeanFactory类(即工厂类):
/**
* 一个创建Bean对象的工厂
*
* Bean:在计算机英语中,有可重用组件的含义
* JavaBean:用Java语言编写的可重用组件
* javabean > 实体类
*
* 它就是创建我们的service和dao对象的
*
* 第一步:需要一个配置文件来配置我们的service和dao
* 配置的内容:唯一标识=全限定类名(key=value)
* 第二部:通过读取配置文件的内容,反射创建对象
*/
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象,我们把它称为容器
private static Map<String,Object> beans;
//使用静态代码块为Properties对象赋值
static {
try {
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
beans = new HashMap<String, Object>();
//取出配置文件中所有的key
Enumeration<Object> keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
beans.put(key,value);
}
} catch (IOException e) {
throw new ExceptionInInitializerError("初始化失败");
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 根据Bean名称的名称来获取bean对象
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return beans.get(beanName);
}
}
- 工厂的类的作用就是读取配置文件的内容,
根据配置文件中的全限定类名来反射创建对象,并将创建的对象存储到一个容器中去。
容器为一个Map集合,key就是配置文件中的key,以后需要某个对象只需提供某个类的唯一标识即可
。
2.3 使用IOC来解决程序的耦合
- 首先我们需要明确一个问题,什么是IOC?
- 在上面的小节中,我们通过引入一个工厂的概念,
把对象的创建和查找都交给了这个工厂来做。
我们需要什么直接问工厂来要。是一种被动的方式。这种被动接受获取对象的方式就是控制反转(IOC)的思想
。
而在以前,我们需要什么是直接new出来的,是一种主动的方式。
-
由上,我们引出IOC的概念:
- 即把创建对象的权力交给框架,是框架的重要特性,主要作用就是削减计算机程序间的耦合。
-
下面我们就使用Spring的IOC来解决程序间的耦合。
-
首先准备一个maven工程,其pom.xml文件如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.itheima</groupId>
<artifactId>day01_eesy_03spring</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
</dependencies>
</project>
- 创建三层接口类和实现类的结构如下,模拟一个保存账户的服务
- 编写一个配置文件
bean.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">
<!--把对象的创建交给spring来管理-->
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="dao.impl.AccountDaoImpl"></bean>
</beans>
- 这里bean标签中
id就是一个唯一标识
,我们使用这个id就可以拿到该类。class就是全限定类名。
- 上面的内容配置完成后,在Client表现层中,写上如下测试代码:
public class Client {
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取bean对象
IAccountService as = (IAccountService) ac.getBean("accountService");
System.out.println(as);
as.saveAccount();
}
}
- 这里
ApplicationContext
就是一个接口,而后面ClassPathXmlApplicationContext是它的一个具体实现类。它就是Spring框架中的工厂,IOC的核心就是使用到了工厂模式
。
2.4Spring中基于XML的细节
- Spring中工厂的类结构图:
- 在开发中我们通常使用
BeanFactory
的子接口ApplicationContext
,它有三个常用的实现类,具体如下表所示:
类名 | 作用 |
---|---|
ClassPathXmlApplicationContext | 从类的根路径加载配置文件,推荐 |
FileSystemXmlApplicationContext | 从磁盘任意路径加载配置文件,必须要有访问权限,不推荐 |
AnnotationConfigApplicationContext | 使用注解配置容器对象时,需要使用此类来创建 Spring 容器。它用来读取注解 |
-
BeanFactory 和 ApplicationContext都是容器的接口,它们两个有什么区别呢?
-
BeanFactory
才是 Spring 核心容器的顶级接口。- 在构建核心容器时,创建对象的默认方式为
延迟加载
。也就是说,我们向容器获取对象时,容器才会去创建对象。
- 在构建核心容器时,创建对象的默认方式为
-
ApplicationContext
是BeanFactory
的子接口。- 在构建核心容器时,创建对象的默认方式为
立即加载
。也就是容器一旦创建完毕,就会创建配置的所有对象(singleton对象)。
- 在构建核心容器时,创建对象的默认方式为
3. Spring基于XML的IOC详解
3.1 bean标签
-
作用:
- 用于配置对象让 spring 来创建的。
默认情况下它调用的是类中的无参构造函数
。如果没有无参构造函数则不能创建成功。
- 用于配置对象让 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:指定类中销毁方法名称
factory-bean
:指定工厂 Bean 对象的唯一标识 (Id),将通过工厂创建对象。factory-method
:指定工厂中创建对象的方法,Spring 会通过该方法来创建对象。
3.2 Bean的作用范围与生命周期
-
单例对象:scope="singleton"
- 一个应用只有一个对象的实例。它的作用范围就是整个引用。
- 生命周期:
- 对象出生:当应用加载,创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象一直活着。
- 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
-
多例对象:scope="prototype"
- 每次访问对象时,都会重新创建对象实例。
- 生命周期:
- 对象出生:当使用对象时,创建新的对象实例。
- 对象活着:只要对象在使用中,就一直活着。
- 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
3.3 创建Bean的三种方式
- 第一种方式:使用默认的构造函数创建
<bean id="accountService" class="service.impl.AccountServiceImpl"></bean>
如果类中没有默认构造函数,则对象无法创建
- 第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
public class InstantFactory {
public AccountServiceImpl service(){
return new AccountServiceImpl();
}
}
<bean id="instantFactory" class="factory.InstantFactory"></bean>-->
<bean id="accountService" factory-bean="instantFactory" factory-method="service"></bean>
先把工厂的创建交给spring来管理,然后再使用工厂中的方法来创建对象。
- 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
public class Staticfactory {
public static AccountServiceImpl service(){
return new AccountServiceImpl();
}
}
<bean id="accountService" class="factory.Staticfactory" factory-method="service"></bean>
它与第二种方法的区别是:这里的工厂是静态的,无需创建工厂,直接通过静态工厂的方法创建对象。
4.Spring的依赖注入
4.1依赖注入的概念
- 依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。
- 通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。所以业务层中应包含持久层的实现类对象。
- 我们等待框架通过配置的方式将持久层对象传入业务层,而不是直接在代码中new某个具体的持久化层实现类,这种方式称为
依赖注入.
4.2 构造函数注入
顾名思义,就是
使用类中的构造函数,给成员变量赋值
。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date date;
public AccountServiceImpl(String name, Integer age, Date date) {
this.name = name;
this.age = age;
this.date = date;
}
@Override
public void saveAccount() {
System.out.println(name + ", " + age + ", " + date);
}
}
<!-- 使用Date类的无参构造函数创建Date对象 -->
<bean id="now" class="java.util.Date" scope="prototype"></bean>
<bean id="accountService" class="service.impl.AccountServiceImpl">
<constructor-arg name="name" value="test"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<!-- birthday字段为已经注册的bean对象,其id为now -->
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<constructor-arg>
标签用于构造函数注入,属性如下:
index
: 指定要注入的参数在构造方法参数列表中索引位置,索引从0开始。type
: 指定要注入的参数的数据类型,该数据类型也是构造方法中某个或某些参数的类型。name
: 指定要注入的参数的名称,这个是最方便也是最常用的。value
: 用于提供基本类型和 String 类型的数据。ref
: 用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。
4.3使用set方法注入(常用)
- 在类中提供需要注入成员的 set 方法,创建对象只调用要赋值属性的set方法。
public class AccountServiceImpl implements IAccountService {
private String name;
private Integer age;
private Date birthday;
public void setName(String name) {
this.name = name;
}
public void setAge(Integer age) {
this.age = age;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public void saveAccount()
{
System.out.println("保存成功" + name + "," + age + "," + birthday);;
}
}
<bean id="now" class="java.util.Date"></bean>
<bean id="accountService" class="service.impl.AccountServiceImpl">
<property name="name" value="test"></property>
<property name="age" value="18"></property>
<property name="birthday" ref="now"></property>
</bean>
<property>
标签用于 set 方法注入,属性如下:
name
: 指定注定时所调用的 set 方法名称。value
: 用于提供基本类型和 String 类型的数据。ref
: 用于指定其他 Bean 类型数据,该 Bean 必须是 IoC 容器中的。
4.4注入集合字段
- 集合字段及其对应的标签按照
集合的结构
分为两类: 相同结构的集合标签之间可以互相替换 - 只有键的结构:
- 数组字段:
<array>
标签表示集合,<value>
标签表示集合内的成员. - List字段:
<list>
标签表示集合,<value>
标签表示集合内的成员. - Set字段:
<set>
标签表示集合,<value>
标签表示集合内的成员.
- 数组字段:
- 其中
<array>,<list>,<set>
标签之间可以互相替换使用. - 键值对的结构:
- Map字段:
<map>
标签表示集合,<entry>
标签表示集合内的键值对,其key属性表示键,value属性表示值。 - Properties字段:
<props>
标签表示集合,<prop>
标签表示键值对,其key属性表示键,标签内的内容表示值。其中<map>,<props>
标签之间,<entry>,<prop>
标签之间可以互相替换使用。
- Map字段:
public class AccountServiceImpl implements IAccountService {
// 集合字段
private String[] myArray;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
// 集合字段的set方法
public void setMyStrs(String[] myArray) {
this.myArray = myArray;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
@Override
public void saveAccount() {
System.out.println(Arrays.toString(myArray));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
<bean id="accountService" class="cn.maoritian.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</array>
</property>
<property name="myList">
<list>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</list>
</property>
<property name="mySet">
<set>
<value>value1</value>
<value>value2</value>
<value>value3</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="key1" value="value1"></entry>
<entry key="key2">
<value>value2</value>
</entry>
</map>
</property>
<property name="myProps">
<props>
<prop key="key1">value1</prop>
<prop key="key2">value2</prop>
</props>
</property>
</bean>