Spring:全功能栈的应用程序框架

各模块介绍

image-20240824163015790

Test

对应 spring-test.jar。 Spring 提供的测试工具,可以整合 Junit 测试,简化测试环节。

Core Container

Spring 的核心组件,包含了 Spring 框架最基本的支撑。

Beans,对应 spring-beans.jar,Spring 进行对象管理时依赖的jar包。

Core,对应 spring-core.jar,Spring 核心 jar 包。

Context,对应 spring-context.jar,Spring 容器上下文对象。

SpEL,对应 spring-expression.jar,Spring 表达式语言。

AOP

面向切面编程,对应 spring-aop.jar。

Aspects

AspectJ 的具体实现,面向切面编程的另一种实现。对应 spring-aspects.jar。

Instrumentation

服务器代理接口的具体实现。对应 spring-instrument.jar。

Messaging

集成 messaging api 和消息协议提供支持。对应 spring-messaging.jar。

Data Access/Integration

Spring 对数据访问层的封装。

JDBC,对应 spring-jdbc.jar。Spring 对 jdbc 的封装,当需要使用 spring 连接数据库时使用。 spring-jdbc.jar 需要依赖 spring-tx.jar。

Transactions,对应 spring-tx.jar。事务管理。

ORM,对应 spring-orm.jar。spring 整合第三方 orm 框架需要使用的 jar 包,例如 Hibernate 框架。

Web

Spring 对 javax 下的接口或类做的扩展功能。

spring-web.jar,对Servlet,filter,Listener等做的增强。

spring-webmvc.jar,实际上就是 SpringMVC 框架。需要依赖 spring 环境和 spring-web.jar。

Spring IoC/DI

简介

IoC(Inversion of Control)中文名称:控制反转。也被称为DI(dependency injection)依赖注入。

属于同一件事情的两个名称。

IoC/DI 是指一个过程:对象的创建仅仅通过 Spring 容器负责,Spring 容器可以通过对象的构造方法或工厂方法进行实例化对象。在创建对象过程中,如果对象需要依赖其他对象,也可以直接在 Spring 容器中注入到当前对象。

整个过程中对象本身在容器中控制自己的实例化(所以叫做控制反转),通过构造方法或 setter 方法把依赖对象注入到自己(所以又叫做依赖注入)。

相关术语

容器(Container):放置所有管理对象的对象。其本质是在容器对象里面有一个全局 Map 对象,map 对象中放置所有被管理的对象。Spring 中容器是指 ApplicationContext 接口及子接口或实现类。

beans:容器中所有被管理的对象称为 beans。如果单说其中一个对象可称为 bean。

创建项目

创建普通 Maven 项目,并命名为 MySpring。

添加依赖

image-20240824164824818

在项目的 pom.xml 中添加 Spring 项目的最基本依赖。

Spring 项目想要运行起来必须包含:

  • spring-context.jar。spring 上下文依赖,它依赖了下面的四个 jar。
  • spring-core.jar。Spring 核心 jar 包。它依赖了 spring-jcl.jar
  • spring-aop.jar。Spring AOP 基本支持。
  • spring-expression.jar。Spring 的表达式语言支持。
  • spring-beans.jar。Spring 容器的 bean 管理。
  • spring-jcl.jar。Spring 4 版本时是 common-logging.jar。从 5 开始 Spring 自己对日志进行了封装。

所以在 Maven 中想要使用 Spring 框架只需要在项目中导入 spring-context 就可以了,其他的 jar 包根据 Maven 依赖传递性都可以导入进来:

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.20</version>
</dependency>
<!-- 为了后续的测试也导入 junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
</dependencies>

创建 Spring 配置文件

在 src/main/resources 下新建 applicationContext.xml 文件。

文件名称没有强制要求。官方示例中配置文件名称叫做 applicationContext.xml,所以我们也把 Spring 配置文件叫做 applicationContext.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">
<!--
使用 xsd 来约束 XML 配置文件的写法,是在根标签上写 xmlns 相关配置。
xml name space 名字空间。
xmlns:xsi 一般都是固定的,不需要改变的。
xsi:schemaLocation 当前的 XML 配置文件,使用哪一个 xsd 文件来约束。
内容是唯一的名称(一般是一个 http 地址)这个 xsd 文件的在线访问地址
-->
</beans>

创建类

在 src/main/java 下新建 pojo.Student.java 文件。

package pojo;

import java.io.Serializable;
import java.util.*;

public class Student implements Serializable {
private Integer id;
private String name;
private int age;
private List<String> books;
private String[] hobbies;
private Map<String, Object> friends; // key是唯一标记,value是朋友的姓名
private Set<String> games; // 喜欢的游戏
private Phone phone;
private Properties info;

public Student() {
System.out.println("创建一个Student对象,使用无参构造方法创建对象。");
id = 1;
name = "张三";
age = 20;
}

/**
* 有参构造
* @param id
* @param name
* @param age
*/
public Student(Integer id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
System.out.println("有三个参数(Integer id, String name, int age)的构造方法运行");
}

public Properties getInfo() {
return info;
}

public void setInfo(Properties info) {
this.info = info;
}

public Phone getPhone() {
return phone;
}

public void setPhone(Phone phone) {
System.out.println("setPhone方法运行,参数是:" + phone);
this.phone = phone;
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", books=" + books +
", hobbies=" + Arrays.toString(hobbies) +
", friends=" + friends +
", games=" + games +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(id, student.id) &&
Objects.equals(name, student.name) &&
Objects.equals(books, student.books) &&
Arrays.equals(hobbies, student.hobbies) &&
Objects.equals(friends, student.friends) &&
Objects.equals(games, student.games);
}

@Override
public int hashCode() {
int result = Objects.hash(id, name, age, books, friends, games);
result = 31 * result + Arrays.hashCode(hobbies);
return result;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
System.out.println("setId()");
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
System.out.println("setName()");
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
System.out.println("setAge()");
}

public List<String> getBooks() {
return books;
}

public void setBooks(List<String> books) {
this.books = books;
}

public String[] getHobbies() {
return hobbies;
}

public void setHobbies(String[] hobbies) {
this.hobbies = hobbies;
}

public Map<String, Object> getFriends() {
return friends;
}

public void setFriends(Map<String, Object> friends) {
this.friends = friends;
}

public Set<String> getGames() {
return games;
}

public void setGames(Set<String> games) {
this.games = games;
}
}

同理创建 Phone.java

package pojo;

import java.io.Serializable;
import java.util.Objects;

public class Phone implements Serializable {
private Integer id;
private String phoneNo;

public Phone() {
}

@Override
public String toString() {
return "Phone{" +
"id=" + id +
", phoneNo='" + phoneNo + '\'' +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Phone phone = (Phone) o;
return Objects.equals(id, phone.id) &&
Objects.equals(phoneNo, phone.phoneNo);
}

@Override
public int hashCode() {
return Objects.hash(id, phoneNo);
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getPhoneNo() {
return phoneNo;
}

public void setPhoneNo(String phoneNo) {
this.phoneNo = phoneNo;
}
}

Bean 实例化的两种方式

在 Spring 中实例化 Bean 有两种方式:

  • 通过构造方法进行实例化。默认使用无参构造。这种方式和以前 new 的方式是等效的。
  • 通过工厂进行实例化。可以通过静态工厂和实例工厂进行实例化。这种方式完全是根据设计模式中工厂模式的思想而研发出的。Spring 考虑到如果需要频繁实例化某个类的对象,工厂模式无疑是一个好选择。

构造方法实例化

在 applicationContext.xml 中配置:

<!-- bean标签
代表配置一个类型,并基于这个类型,让spring的容器管理一个bean对象。
属性:
id:这个bean的全局唯一标记。
name:这个bean的别名,可以有若干各,用逗号分隔。要求全局唯一。
class:这个bean对象的具体类型是什么。 使用包名.类名赋值
-->
<bean id="student1" name="stu1, stu2,stu3" class="pojo.Student"></bean>
<bean id="student2" name="stu4, stu5,stu6" class="pojo.Student"></bean>
<!-- 注意这里的 name 不能重复,否则会报错 -->

在 src/test/java 中创建 TestSpring.java 从而进行测试:

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;

public class TestSpring {
@Test
public void testContainer(){

// 基于配置文件创建容器对象
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("========== 容器创建完毕 ==========");

// 从容器中获取需要的 bean 对象。根据 bean 的名字获取。唯一名称或别名都可以
// getBean 方法的返回值类型是 Object
// 强制转换
Student s1 = (Student) context.getBean("student1");

// 基于getBean的重载方法,指定要的bean对象类型是什么。
Student s2 = context.getBean("stu1", Student.class);

// 测试其他类型的获取
// Object obj = context.getBean("stu1", Integer.class);

Student s3 = context.getBean("stu2", Student.class);

// 根据 bean 对象的类型,从容器中获取对象。
// 如果这个类型的对象不存在:抛出异常
// 如果这个类型的对象有一个:返回这个对象
// 如果这个类型的对象有多个:抛出异常
Student s4 = context.getBean("student2", Student.class);
//Integer i = context.getBean(Integer.class);

System.out.println("s1 : " + s1);
System.out.println("s2 : " + s2);
System.out.println("s3 : " + s3);
System.out.println("s4 : " + s4);
System.out.println(s1 == s4);

// System.out.println("obj : " + obj);
}
}

输出:

创建一个Student对象,使用无参构造方法创建对象。
创建一个Student对象,使用无参构造方法创建对象。
========== 容器创建完毕 ==========
s1 : Student{id=1, name='张三', age=20, books=null, hobbies=null, friends=null, games=null}
s2 : Student{id=1, name='张三', age=20, books=null, hobbies=null, friends=null, games=null}
s3 : Student{id=1, name='张三', age=20, books=null, hobbies=null, friends=null, games=null}
s4 : Student{id=1, name='张三', age=20, books=null, hobbies=null, friends=null, games=null}
false

为什么返回 false ?

容器中创建若干 bean 对象,容器管理这些 bean 对象时,会检查是否同类型,如果同类型,尝试调用 equals。
如果对象等价(equals 返回 true)。那么 getBean 会返回同一个对象。当使用时,如果有冲突,则底层切换具体对象。没有冲突直接使用。

工厂实例化

实例工厂

创建工厂类

在 src/main/java 下创建 factory 软件包,创建 StudentInstanceFactory 类:

package factory;

import pojo.Student;

/*
* 学生实例工厂
* 这个工厂,必须先创建对象,再调用方法,才能创建学生对象。
*/
public class StudentInstanceFactory {
private Student student = new Student();

public StudentInstanceFactory(){
System.out.println("创建了学生实例工厂对象。");
}

/*
* 工厂方法。调用即返回对象student。
* @return
*/
public Student getInstance(){
return student;
}
}
配置 bean

在 applicationContext.xml 中配置:

<!--
实例工厂
先让 spring 容器创建一个工厂的 bean 对象。
再基于这个工厂的 bean 对象,创建产品(学生)bean 对象。
-->
<bean id="stuInstanceFactory" class="factory.StudentInstanceFactory"></bean>
<!--
基于工厂 bean 对象,创建产品对象
factory-bean 使用的工厂 bean 对象的唯一标记,可以是 id,可以是 name
factory-method 工厂 bean 对象中的工厂方法名。
-->
<bean id="instanceFactoryStudent" factory-bean="stuInstanceFactory" factory-method="getInstance"></bean>
在测试类中测试效果

在 TestSpring.java 中添加:

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;

public class TestSpring {
@Test
public void testInstanceFactory(){
// classpath: 代表从 classpath 下开始查找
ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
// 获取工厂创建的对象
Student student = context.getBean("instanceFactoryStudent", Student.class);
System.out.println(student);
}
}

输出:

创建一个Student对象,使用无参构造方法创建对象。
创建了学生实例工厂对象。
Student{id=1, name='张三', age=20, books=null, hobbies=null, friends=null, games=null}

静态工厂

创建工厂类

factory 软件包创建 StudentStaticFactory 类:

package factory;

import pojo.Student;

/*
* 静态工厂。
* 当前类型不需要创建对象。直接使用静态方法,创建产品对象
*/
public class StudentStaticFactory {
private static Student student = new Student();
public StudentStaticFactory(){
System.out.println("学生静态工厂创建对象");
}
public static Student newInstance(){
// 静态工厂方法
return student;
}
}

配置 bean

在 applicationContext.xml 中配置:

<!-- 
静态工厂
不需要创建工厂对象。直接使用工厂的静态方法,创建产品对象
class 定义静态工厂类型
factory-method 静态工厂中的工厂方法名
-->
<bean id="staticFactoryStudent" class="factory.StudentStaticFactory" factory-method="newInstance"></bean>
在测试类中测试效果

在 TestSpring.java 中添加:

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;

public class TestSpring {
@Test
public void testStaticFactory(){
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("staticFactoryStudent", Student.class);
System.out.println(student);
}
}

输出:

创建一个Student对象,使用无参构造方法创建对象。
Student{id=1, name='张三', age=20, books=null, hobbies=null, friends=null, games=null}

属性注入

在上面演示的都是如何实例化 Bean,下面演示的是如果给 Bean 的属性进行赋值。

Spring 中给 Bean 属性赋值有两种方式:

  • 构造注入(Constructor-based Dependency Injection):通过构造方法给 bean 的属性赋值。所以要求 bean 的类中必须提供对应参数的构造方法(有参构造方法)。相当于以前创建对象时 new People(1,"张三");
  • 设值注入,又称 setter 注入(Setter-based Dependency Injection):通过 Bean 的 setter 方法赋值。所以要求 Bean 中属性必须提供 setter 方法。相当于以前的 People peo = new People(); peo.setId(1); peo.setName("张三");

Spring 配置文件中的属性赋值方式

  1. 简单类型, 8 种基本类型,对应包装类型, String 类型。直接赋值,使用 value。
  2. 引用类型,对象。使用其他的 bean 对象赋值。 ref。
  3. 数组类型。 使用标签 <array><value></value><ref></ref></array> 赋值。一个 array 代表一个数组。内部的子标签 value 或 ref 代表一个元素。
  4. List 集合。 使用标签 <list><value></value><ref></ref></list><array> 赋值。一个 list 是一个集合。
  5. Set 集合。 使用标签 <set><value></value><ref></ref></set> 赋值。一个 set 是一个集合。
  6. Map 集合。 使用标签 <map><entry key="" value="" key-ref="" value-ref=""/></map> 赋值。一个 map 是一个集合。一个 entry 是一个键值对。
    key 是键,value 是简单数据值。ref 是引用对象 bean 的 id。
  7. 特殊的 Map 集合 Properties。使用 <props><prop key="">value</prop></props> 或者 <map>。一个 props 是一个 Properties 对象,
    一个 prop 是一个键值对。
  8. 为任意的引用类型属性,赋值 null。 <null/>

构造注入

配置 bean

在配置文件 applicationContext.xml 中可以通过 <bean> 的子标签 <constructor-arg> 设置构造方法中一个参数的值。

constructor-arg 里面有5个属性,这5个属性分为2类。

用来确定给哪个属性进行赋值

​ name:参数名称

​ index:参数索引。从0开始算起。

​ type:参数类型。8大基本数据类型可以直接写关键字。其他类型需要写类型的全限定路径。

​ 这三个属性如果只需要用到一个就能精确的告诉 Spring,要设置的构造方法参数是哪个可以使用一个。

​ 如果无法精确到某个构造方法参数,可以多个一起结合使用。

设置属性的值

​ value:简单数据类型直接设置。Spring 会自动进行类型转换。

​ ref:需要引用另一个 bean 的 id。也就是说这个参数是一个类类型,且这个类的对象也被 Spring 容器管理。

<bean id="student" class="pojo.Student">
<constructor-arg name="id" value="10"></constructor-arg>
<constructor-arg type="java.lang.String" value="李四"></constructor-arg>
<constructor-arg index="2" value="30"></constructor-arg>
</bean>

在测试类中测试效果

在 TestSpring.java 中添加:

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;

public class TestSpring {
@Test
public void testConstructor(){
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student);
}
}

输出:

有三个参数(Integer id, String name, int age)的构造方法运行
Student{id=10, name='李四', age=30, books=null, hobbies=null, friends=null, games=null}

设值注入

配置 bean

spring 容器使用设置注入时,是基于 property 实现的。必须有 getter 方法。且属性命名是 property 名。
property 代表调用一个 setter 方法。
属性:
name 属性名。
value 要赋予的属性值。
ref 要赋予的属性值,引用其他 bean 的 id。

<bean id="student" class="pojo.Student">
<property name="id" value="100"></property>
<property name="name" value="王五"></property>
<property name="age" value="25"></property>
</bean>

在测试类中测试效果

在 TestSpring.java 中添加:

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;

public class TestSpring {
@Test
public void testSetter(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("student", Student.class);
System.out.println(student);
}
}

输出:

创建一个Student对象,使用无参构造方法创建对象。
setId()
setName()
setAge()
Student{id=100, name='王五', age=25, books=null, hobbies=null, friends=null, games=null}

不同属性类型对应的写法

无论是构造注入还是设值注入都提供了 value 和 ref 进行设置值。

这两个属性只能给属性赋予简单数据类型或其他 bean 的引用。如果类的属性是数组、集合等类型需要通过下面方式进行设置。

这些标签都是 <property><constructor-args> 的子标签。

一旦使用了下面子标签方式,就不能对 <property><constructor-args> 设置 value 属性或 ref 属性。且需要在 People 类中提供对应名称,对应类型的属性。

Set 类型

<bean id="stu" class="pojo.Student">
<property name="games">
<set>
<value>game1</value>
<value>game2</value>
</set>
</property>
</bean>

List 类型

<bean id="stu" class="pojo.Student">
<property name="books">
<list>
<value>book1</value>
<value>book2</value>
</list>
</property>
</bean>

Array 类型

<bean id="stu" class="pojo.Student">
<property name="hobbies">
<array>
<value>吃饭</value>
<value>睡觉</value>
</array>
</property>
</bean>

Map 类型

<bean id="stu" class="pojo.Student">
<property name="friends">
<map>
<entry key="基友" value="基友"></entry>
<entry key="闺蜜" value="闺蜜"></entry>
</map>
</property>
</bean>

Null 值类型

<bean id="stu" class="pojo.Student">
<property name="phone">
<null></null>
</property>
</bean>

引用其他 Bean

如果需要引用其他 Bean,直接在 property 标签中使用 ref 引用就可以。使用子标签 ref 也可以,但是没有直接用 ref 属性的方式简单。

<bean id="phone" class="pojo.Phone">
<property name="id" value="1"/>
<property name="phoneNo" value="114514"/>
</bean>

然后

<bean id="stu" class="pojo.Student">
<property name="phone">
<null></null>
</property>
</bean>

Properties 类型

<bean id="stu" class="pojo.Student">
<property name="info">
<props>
<prop key="npy"></prop>
</props>
</property>
</bean>

综合:

<bean id="stu" class="pojo.Student">
<property name="books">
<list>
<value>book1</value>
<value>book2</value>
</list>
</property>
<property name="hobbies">
<array>
<value>吃饭</value>
<value>睡觉</value>
</array>
</property>
<property name="friends">
<map>
<entry key="基友" value="基友"></entry>
<entry key="闺蜜" value="闺蜜"></entry>
</map>
</property>
<property name="games">
<set>
<value>game1</value>
<value>game2</value>
</set>
</property>
<!--<property name="phone" ref="phone"></property>-->
<property name="phone">
<null/>
</property>
<property name="info">
<props>
<prop key="npy"></prop>
</props>
</property>
</bean>
<bean id="phone" class="pojo.Phone">
<property name="id" value="1"/>
<property name="phoneNo" value="114514"/>
</bean>

在测试类中测试效果

在 TestSpring.java 中添加:

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import pojo.Student;

public class TestSpring {
@Test
public void testDI(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = context.getBean("stu", Student.class);
System.out.println(student);
System.out.println(student.getInfo());
System.out.println(student.getPhone());
}
}

输出:

创建一个Student对象,使用无参构造方法创建对象。
setPhone方法运行,参数是:null
Student{id=1, name='张三', age=20, books=[book1, book2], hobbies=[吃饭, 睡觉], friends={基友=基友, 闺蜜=闺蜜}, games=[game1, game2]}
{npy=无}
null