文章目录
一、Spring 简介
1.简介
发展历程:
- 2002年,首次出现spring框架雏形,interface21框架
- 2004年,正式发布spring 1.0 版本
- Rod Johnson 是创始人,音乐学博士
轮子理论:
- “轮子理论”,也即“不要重复发明轮子”,这是西方国家的一句谚语,原话是:Don’t Reinvent the Wheel。
- 拿到软件领域中就是指有的项目或功能,别人已经做过,我们需要用的时候,直接拿来用即可,而不要重新制造。
- 当我们进行项目开发的时候,若已有的技术能满足我们的开发需求,我们不需要在去创造新的技术,只需要把现有的技术拿过来用就可以了。若已有的技术不能满足我们的开发需求,这时,我们就要去创造新的“轮子”。
Spring 理念:
- 简化服务器开发,使现有技术更容易使用
- 本身是个大杂烩,整合了现有的技术框架
- 源码质量高,有助于开发者阅读学习
官方文档:
官方下载:
GitHub:
Maven:
- 导入 spring web mvc 会自动把所需的spring依赖全部导入
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.4</version>
</dependency>
2.特点
- 开源、免费的框架(容器)
- 轻量级,非入侵的框架,不会对原有项目产生影响
- 最大特性:控制反转(IOC)、面向切面编程(AOP)
- 支持事务处理
- 支持对各类框架的整合(号称大杂烩)
缺点:
- 随着不断发展,配置越来越多
3.组成
七大模块:
官网介绍:现代化的Java开发,即基于Spring的开发
学好spring,为下一步springboot打下良好基础
二、Spring IOC
项目准备
新建maven项目,导入spring依赖,新建子模块
我们发现导入spring-webmvc之后,会自动导入其他相关依赖
1.IOC本质
分析
早期的项目:
- 调用流程,serviceImpl实现service,为了调用Dao层方法,serviceImpl还要实现Dao层接口,这样用户操作service层对象实现业务,
- 当用户需要调用很多Dao层实现业务时,service就必须实现很多Dao层,修改很多代码或者创造很多对象,非常不方便
- 用户的需求可能会影响原来的代码,我们需要根据用户的需求来修改源代码
- 用户需要什么Dao层的业务,我们就要在实现类中new一个对应的实现类对象
我们可在serviceImpl添加方法解决这个问题,如,
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
- 之前是程序主动创建对象,控制权在程序员手上,每换业务,程序员都需要手动创建新对象
- 使用set注入后,程序不再主动,而是被动接收对象
这种思想就是控制反转,程序员不用再去管理对象创建,系统耦合性大大降低,可以更加专注业务实现
这是IOC的原型,原理推导可以参考
其实就是,之前那是业务层去根据用户需求,调用各个Dao接口实现类,程序员需要主动创建对象
现在是业务层只提供set方法,用户自己决定调用什么业务就注入什么对象,主动权在用户身上
IOC本质
控制反转IOC,是一种设计思想,DI(y依赖注入)是实现IOC的一种方法
在没有IOC的程序中,我们使用面向对象编程,对象的创建于对象间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方
也就是获得依赖对象的方式反转了
IOC是Spring的核心内容,可以使用xml配置,也可以使用注解,新版本spring也可以零配置实现IOC
spring容器在初始化时,先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,程序使用时再从IOC容器中取出需要的对象
业务代码本身不再主动去创建对象,而是准备个配置文件,需要什么对象就去配置,spring读取配置帮助用户创建对象,在业务层中,需要的时候直接注入即可;
由spring来创建对象,在spring中,这些对象都称之为bean
在配置文件加载的时候,容器中管理的对象就已经初始化了
小结:
-
传统的创建对象,我们使用new的方式,在业务代码中,根据业务需要提前new对象,缺点是代码耦合度高,修改业务也会导致代码修改
-
IOC控制反转的核心在于,程序员不用主动在程序去new对象,而是通过spring加载配置文件来创建;作为业务的调用方,用户完全可以根据需要将
bean配置在配置文件中,使用时直接在容器中取出即可;用户只需修改配置文件便可以决定创建哪些对象bean,而创建对象的过程则由spring处理。
spring IOC容器创建对象的过程就是利用了反射原理 -
加载上下文配置创建上下文对象,是spring官方规定的标准写法,只有这样spring才能根据配置创建bean;默认情况下配置文件加载bean使用单例模式,
所以多次调用同一个bean得到的都是同一个对象;bean对象在上下文对象加载配置文件后就创建了,用户getBean只是从spring IOC容器中取出对象, -
从上下文对象到上下文接口,经历了很多层接口实现,这也间接说明spring帮助我们创建对象,在底层做了很多工作,
-
resources中创建xml,写入上下文配置模板,IDEA会提示需要添加为spring的上下文配置文件,根据提示操作,可以选择单独添加为配置文件,
或者合并入现有的spring配置文件中 -
上下文配置文件除了加载注册类,也提供其他功能,给bean别名,给属性赋值,选择无参构造还是有参构造创建对象,bean作用域,多个配置文件的合并关系
-
spring加载bean,可以使用上下文配置文件xml,也可以使用java程序和注解的方式
2. IOC 容器
官方解读
https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html
该org.springframework.context.ApplicationContext
接口代表Spring IoC容器,并负责实例化,配置和组装Bean。容器通过读取配置元数据获取有关要实例化,配置和组装哪些对象的指令。
配置元数据以XML,Java批注或Java代码表示。
ApplicationContextSpring
提供了该接口的几种实现。通常创建实例ClassPathXmlApplicationContext
或 FileSystemXmlApplicationContext
。
下图显示了Spring的工作原理的高级视图。您的应用程序类与配置元数据结合在一起,因此,在ApplicationContext创建和初始化后,您将拥有一个完全配置且可执行的系统或应用程序
Spring IoC容器使用配置文件告诉Spring容器如何实例化,配置和组装应用程序中的对象。
传统上,配置元数据以简单直观的XML格式提供
基于注释的配置:Spring 2.5引入了对基于注释的配置元数据的支持。
基于Java的配置:从Spring 3.0开始,Spring JavaConfig项目提供的许多功能已成为核心Spring Framework的一部分。因此,您可以使用Java而不是XML文件来定义应用程序类外部的bean。要使用这些新功能,请参阅 @Configuration, @Bean, @Import,和@DependsOn注释。
Spring配置由容器必须管理的至少一个(通常是一个以上)bean定义组成。
- XML配置元使用bean标签。
- Java配置使用注释@Bean在@Configuration类中使用带注释的方法。
- 这些bean定义对应于组成应用程序的实际对象。
3.Spring IOC 创建对象过程
项目准备
创建模块
实体类
public class User {
private String name;
public User() {
System.out.println("User无参构造执行了");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show() {
System.out.println("name="+name);
}
}
测试类
public class MyTest {
public static void main(String[] args) {
User user = new User();
}
}
执行代码,无参构造执行了,说明对象创建,这是之前我们创建对象的方式
spring 创建对象
添加xml配置文件
IDEA提示我们没有配置该文件到项目中,需要配置文件上下文,根据提示点击右侧确认即可
编写配置文件,将User类注册为spring的一个bean,使用spring来创建对象,在spring中这些都成为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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.swy.pojo.User">
<property name="name" value="Jack"/>
</bean>
</beans>
- bean标签将类注册为bean,相当于创建对象,id就是对象名,class就是对象的全限定类名
- property描述类中的属性,相当于给属性赋值,name对应属性名,value对应属性值
- 这个过程就是控制反转,将创建对象的过程交给spring来做
- beans就相当于容器,里面可以存放多个bean
- 注入属性的过程需要使用set方法,因此被注册的类中需要有set方法
- 一旦注册这个类之后,表示该类交给spring管理,所以被注册类也会出现叶子的标志
注册之后,我们发现User类左侧出现变化,表示当前User类已经注册为Spring的一个bean
开始测试,使用spring创建对象
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
}
}
无参构造执行,说明spring上下文对象读取了xml配置,帮助我们创建了User对象
含参构造创建对象
这方式默认使用无参构造创建对象,如果想使用含参构造创建对象,除了类中添加含参构造方法,还要进行xml配置
首先User类中添加含参构造
public User(String name) {
this.name = name;
System.out.println("User含参构造执行了");
}
xml配置可以使用三种方法,先将原有的无参构造property标签去掉
通过属性的索引,
<!-- index="0"表示第一个属性,也即是name -->
<bean id="user" class="com.swy.pojo.User">
<constructor-arg index="0" value="Mike"/>
</bean>
通过属性的类型,基本类型直接写,引用类型使用全限定类名,当有多个同类型属性时,存在问题,不推荐使用
<bean id="user" class="com.swy.pojo.User">
<constructor-arg type="java.lang.String" value="Mike"/>
</bean>
直接给属性名赋值,简单,推荐使用
<bean id="user" class="com.swy.pojo.User">
<constructor-arg name="name" value="Mike"/>
</bean>
控制台输出,说明含参构造创建对象
在添加一个类User1,并且在xml中进行配置,然后执行代码
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
}
}
控制台输出
我们发现,虽然我们只是从上下文对象中获取user对象,但user1对象也被创建了,说明spring加载配置文件后就将bean实例化了,我们只是从容器取出而已
如果执行代码,
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
User user = (User) context.getBean("user");
User user0 = (User) context.getBean("user");
System.out.println(user == user0);
}
}
控制台输出
我们发现,多次从容器中取同一个类的bean,每次取的都是同一个对象
分析
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
是固定写法,如果使用xml配置bean就必须使用这种写法,参数填写自己准备的xml配置文件,可以填写多个,这就为什么我们添加xml时会有提示,否则spring不识别这个xml- 这段代码表示,获取spring的上下文对象。加载配置文件之后,我们配置的对象交给spring管理,使用时直接取出来就可以,返回的是Object对象,所以需要强转,
- 通过IDEA点进去查看
ClassPathXmlApplicationContext
,我们发现进入了很多层,才找到ApplicationContext
接口,说明spring框架帮助我们在底层实现了很多功能,所以我们使用起来才如此方便
小结:
到这一步,我们可以不再到程序中去改动了(new 对象),要实现不同的操作,只需在xml中配置修改即可,所以IOC就是,将对象交给Spring来创建、管理、装配
4.Spring 配置说明
详情见:https://docs.spring.io/spring-framework/docs/5.2.0.RELEASE/spring-framework-reference/core.html
xml配置文件除了上述配置外,还有一些其他的配置,这里进行一下补充
别名
<alias name="user" alias="userNew"/>
表示给user起了个别名叫userNew,有了别名,上下文对象可以通过别名从容器中取对象,如,
User user = (User) context.getBean("userNew");
Bean配置
id:bean的唯一标识,相当于我们学的对象名
class:bean对象所对应的全限定类名,包名+类名
name:别名,并且name可同时取多个别名(功能比alias更强大,有别名需要用这个就够了),空格分号逗号都可以用来分隔
scope:设置单例还是多例,后面介绍
<bean id="user1" class="全限定类名" name="user2,user3,user4...">
import
一般用于团队开始用,可将多个配置文件导入合并为一个,
当我们再创建一个配置文件beans1.xml时,根据IDEA提示,可以将其并入已有的xml配置文件中,
然后,在负责合并的配置文件中,导入其他的配置文件即可
<import resource="beans1.xml"/>
在创建上下文对象时,我们只需导入总的配置文件,相应的子配置文件也会被加载
如果导入的多个配置中,有重复的bean,spring也会自动的去重
配置文件名字自定,通常正规叫法为applicationContext.xml
5.DI 依赖注入
构造器注入、Set方式注入
构造器注入
上面已经介绍
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private MovieFinder movieFinder;
// a constructor so that the Spring container can inject a MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
请注意,该类没有什么特别的。它是一个POJO,不依赖于特定于容器的接口,基类或注释。
Set 方式注入(重点)
- 依赖:bean对象的创建依赖于容器
- 注入:bean对象中的所有时属性,由容器来注入(重点只Set注入)
Spring通过Set方式注入是最核心的注入方式
创建项目,准备一个复杂类型
首先创建一个类,用来作为引用类型
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Job {
private String job1;
private String job2;
}
接着,创建我们用来实例的bean,为了方便测试,这个类中我们引入复杂参数,包括上面的引用类,为了节省文章空间,我们使用lombok注解代表该类中已包含get/set方法,构造方法,toSting方法
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
private Job job;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
}
创建配置文件,根据提示添加为当前spring项目的上下文配置文件
开始注册bean对象,属性,官网使用很多例子来演示不同的属性注入,我们全部集中到一个例子中演示
实际上,在添加属性注入时,我们也可以根据IDEA的提示,选择不同属性应该对应哪个标签
<?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="job" class="com.swy.pojo.Job">
<property name="job1" value="老师"/>
<property name="job2" value="农民"/>
</bean>
<bean id="person" class="com.swy.pojo.Person">
<!-- 普通属性注入 -->
<property name="name" value="Rose"/>
<!-- 引用属性注入 -->
<property name="job">
<ref bean="job"/>
</property>
<!-- 数组属性注入 -->
<property name="books">
<array>
<value>数学</value>
<value>语文</value>
<value>英语</value>
</array>
</property>
<!-- list集合注入 -->
<property name="hobbys">
<list>
<value>运动</value>
<value>音乐</value>
</list>
</property>
<!-- map集合注入 -->
<property name="card">
<map>
<entry key="身份证" value="666"/>
<entry key="银行卡" value="999"/>
</map>
</property>
<!-- set集合注入 -->
<property name="games">
<set>
<value>英雄联盟</value>
<value>魔兽世界</value>
</set>
</property>
<!-- Null注入 -->
<property name="wife">
<null/>
</property>
<!-- properties配置注入 -->
<property name="info">
<props>
<prop key="username">admin</prop>
<prop key="password">123456</prop>
</props>
</property>
</bean>
</beans>
运行测试
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Person person = context.getBean("person",Person.class);
System.out.println(person);
}
}
- getBean第二个参数可以添加对应的类对象,返回值可以不用强转
出现的问题
问题描述:
- 在这里我首先给Student和Job使用普通get/set方法,但是由于bean配置出现错误,导致异常;(j异常为属性不可写,没有set之类的)
- 将配置修改为正确(也就是现在的配置),运行代码依然异常;
- 个人怀疑可能是构造方法问题,于是分别添加无参构造、有参构造,依然异常
- 将手写get/set,构造去掉,改用lombok注解
@Data @NoArgsConstructor @AllArgsConstructor
,依然异常 - 将注解去掉,重写改为手动生成,终于正常运行
- 将手写去掉,重新使用lombok注解,正常运,之后,手写与lombok都可以正常运行
分析:
目前还不清楚问题的确切原因,个人感觉和缓存有关系,或者是某一次运行的错误代码编译后的字节码文件没有即使更新,导致的,还需要进一步分析
如果之前出现过异常,修改正确之后依然异常,可以考虑删除target目录清除缓存,或者新建项目、换台机器试一试,排除缓存可能带来的干扰
使用p/c-namespace 命名空间 方式注入(扩展方式)
Spring支持名称空间的方式实现注入,这些名称空间基于XML Schema定义,也即是在beans标签中定义
p命名空间在XSD文件(约束文件)中定义,仅存在于Spring的核心中,举例,
新增一个beans1.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"
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="student" class="com.swy.pojo.Student" p:name="Mike" p:age="18"/>
</beans>
注意beans标签新增一条约束 xmlns:p="http://www.springframework.org/schema/p"
同样 p 命名空间也可以使用 ref 引用
新建类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
private String name;
private int age;
}
<