目录
什么是Spring?
DI
Spring 存与取
spring 的存操作
spring的取操作
更快速的进行 Spring 存 与 读
三大注入方式
@Autowired
set 注入
构造方法注入
Spring 容器中有多个相同的类时
Bean 作用域
设置作用域
Spring 执行流程
Bean 生命周期
什么是Spring?
Spring : 它是一个开源框架 , 是包含了众多工具方法的 IoC 容器 , (说的就是对象的创建和销毁的权利都交给了 Spring 来管理了,它本身又具备了存储对象和获取对象的能力,通俗点来讲Spring就是容器, 帮我们管理对象的生命周期) 。
Spring 的核心功能就是 :如何将对象存入到 Spring 中,再从 Spring 中获取对象的过程。
IoC :(Inversion of Control) 控制反转 ,它是一种思想。
从文字上去了解肯定有点难懂,让我们拨开层层迷雾,一一道来。
先来解释一下什么是 控制反转 ,举个栗子 :
设计这么一个关系链,最开始的思想:汽车调用车身,车身调用底盘,底盘调用轮胎,需要的参数都通过构造方法去传递。
代码如下 :
// 汽车
public class Car {
Framework framework = new Framework(12);
public void init() {
framework.init();
}
}
// 车身
public class Framework {
private Bottom bottom;
public Framework(int size) {
bottom = new Bottom(size);
}
public void init() {
bottom.init();
}
}
// 底盘
public class Bottom {
// 大小
private Tire tire;
public Bottom(int size) {
tire = new Tire(size);
}
public void init() {
tire.init();
}
}
// 轮胎
public class Tire {
// 大小
private int size;
public Tire(int size) {
this.size = size;
}
public void init() {
System.out.println("轮胎大小:" + size);
}
}
这种思想弊病在于:假设轮胎这一层需要加一个参数,这时底盘调用轮胎就得变,因为参数都是由汽车这个类传进来,因此车身调用底盘的参数就要变,汽车调用车身的参数也要变,牵一发而动全身。
将思想再改变一下,我们将需要的参数进行封装,每次传递的时候,传递一个对象,这么即使需要修改参数,直接修改对象就好了。
代码如下 :
// 测试类
public class Main {
public static void main(String[] args) {
Tire tire = new Tire(12);
Bottom bottom = new Bottom(tire);
Framework framework = new Framework(bottom);
Car car = new Car(framework);
car.init();
}
}
// 汽车
public class Car {
private Framework framework;
public Car(Framework framework) {
this.framework = framework;
}
public void init() {
framework.init();
}
}
// 车身
public class Framework {
private Bottom bottom;
public Framework(Bottom bottom) {
this.bottom = bottom;
}
public void init() {
bottom.init();
}
}
// 底盘
public class Bottom {
// 大小
private Tire tire;
public Bottom(Tire tire) {
this.tire = tire;
}
public void init() {
tire.init();
}
}
// 轮胎
public class Tire {
// 大小
private int size;
public Tire(int size) {
this.size = size;
}
public void init() {
System.out.println("轮胎大小:" + size);
}
}
这时我们的创建类的顺序就变成了 : Tire -> Bottom -> Framework -> Car.
最开始的创建类的顺序 : Car -> Framework -> Bottom -> Tire
第一次实现 是将 Car 创建 Framework , Framework 创建了 Bottom,依次往下,这样做,都是由上级对象创建并控制下级对象,而第二次实现则是将下级对象注入到当前对象中,此时下级对象的生命周期不在当前对象中,因此下级对象的控制权就不再由上级对象控制,这样即使下级类发生变化,当前类都是不受影响的。这就是IoC思想 , 这就是控制反转.
对比上面两种思想,IoC 更为灵活,所有对象的生命创建和销毁的权利都交给了Main,Spring扮演的就是Main的角色。
DI
DI :(Dependency Injection)“ 依赖注入 ”。
DI :在程序运行期间,动态的将某个对象引入到当前类的机制。实现了对象之间的解耦。
DI 与 IoC 的关系 :
IoC 它就是一种思想,而 DI 就是实现了IoC这种思想的技术。
就像是 乐观锁 是一种思想,而 CAS 就是一种具体实现的技术。
Spring 存与取
创建一个 maven 项目 , 引入Spring的依赖 .这样得到一个Spring的项目.
spring 的存操作
第一步 , 创建一个Bean对象 . (Bean对象就是java代码中的类)
public class T {
public String First() {
return "First";
}
}
第二步 , 将 Bean 对象存储到 Spring 当中.
其中 , 第一小步, 在resources 目录下创建一个spring的配置文件.
<?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:content="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- <content:component-scan base-package="com.demo"></content:component-scan>-->
</beans>
将Bean对象放在这个配置文件中 , 这样在Spring在启动项目时 , 这些Bean对象会被初始化出来.
第二小步 , 将 Bean 对象配置到 Spring 配置文件中 .
spring的取操作
取操作 : 首先得到 Spring (上下文) 对象 , 然后从Spring中取出对象 .
public class Main {
public static void main(String[] args) {
// 1. 先得到 Spring (上下文) 对象
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
// 2. 从 Spring 中取出 Bean对象
T t = (T)context.getBean("first"); // 根据 id 来得到 Bean 对象,如果参数为null那么就会强转错误
// T t = context.getBean(T.class); // 根据 Bean对象的类型来获取 Bean对象 , 但如果Spring中存在相同的对象时,这时使用类型来获取Bean方式就会报错.
// T t = context.getBean("first",T.class) 根据 id 和 Bean 类型获取 Bean 对象
}
}
除了ApplicationContext 可以获得Spring对象之外 , BeanFactoy 也可以获取 Spring 对象。
BeanFactory context = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));
关于 ApplicationContext 与 BeanFactory 的区别 :
相同点 : 都是可以得到 Spring 上下文对象。都是来自 Spring 的顶级接口。
不同点 : 在继承关系和功能上 : ApplicationContext 属于 BeanFactory 的子类。BeanFactory 只有最基础的访问 Bean 的能力,而 Application 除了拥有 父类 BeanFactory 功能外,还包含了更多的功能,例如:国际化支持、资源访问、时间传播 等等
从性能上来看 : ApplicationContext 加载方式是将 Bean 对象一次性加载,所以在后面访问 Bean 对象时会比 BeanFactory 快,(牺牲了空间换取了时间)。BeanFactory 需要某个Bean对象 时,才去加载Bean 对象,所在它在执行 Bean 获取时,速度会比较慢。
更快速的进行 Spring 存 与 读
最开始的存就是往 xml 的配置文件中放 Bean对象,但如果后期的项目足够大,对象越来越多,那么这个放操作就会很繁琐并且难以维护。因此使用注解可以更快速的进行存与取。
要做的工作首先,配置要扫描的路径,(也就是那些包下的类需要被加载到 Spring 中),效率会更高。
重点注意:如果想要加载的对象不在扫描路径下,那么就不能被加载到Spring的IoC容器中
将对象存储到 Spring 中,有两种注解类型可以实现:
1 . 首先是类注解 : @Controller 、@Service 、@Repository 、@Component 、@Configuration
五大类注解 :在项目中扮演的角色是不一样的。
@Controller (控制器): 归属于业务逻辑层,用来控制用户的行为,它主要是用来检查用户参数的有效性。
@Service (服务) : 归属于服务层,调用持久层的类实现相应的功能。(它不会直接和数据库直接交互,像是控制中心一样的存在)
@Repository (仓库) :归属于持久层,是直接和数据库进行交互的。
@Configuration (配置) : 归属与配置层,是用来配置当前项目的一些信息。
@Component (组件) : 归属于公共工具类 , 提供公共方法。
对于上述的5大注解 ,举个栗子:登录博客,这时要输入账号和密码,回车键一摁,前端将请求发给后端,后端拿到信息就会先核实一下登录信息是否正确 这个操作就是 控制器来管理的,而如果你想要发布一个博客,Controller就会调用服务层,服务层会根据请求会涉及到那些功能去进行调用持久层,持久层就会去调用数据库中的表。
@Controller // 注解
public class UController {
public String inp() {
return "采用注解方式来注入";
}
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UController uController = context.getBean("UController",UController.class);
System.out.println(uController.inp());
}
获取Bean对象 :
如果使用最开始的获取Bean对象的方式,默认情况下 id 就是将类名首字母小写。
特殊情况下:如果类名的首字母和第二个字母都是大写的情况下此时 id 就是原类名。
可以看Spring中的源码 :
2 . 采用方法注解 : @Bean
@Bean 注解,如果没有给@bean设置name属性的话,默认情况下 获取对象的 id 就是方法名,并且如果设置了name属性之后,那么就不能再使用 方法名来获取对象了。
注意:在采用方法注解时,是将方法返回的对象放入到 Spring 当中。
使用@Bean注解时,要配合5大类注解来使用。否则会是无效注解。
@Controller
public class UController {
@Bean
public UController inp() {
return new UController();
}
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UController uController = context.getBean("inp",UController.class);
}
@Controller
public class UController {
@Bean(name = {"n1","n2"}) // 设置name属性
public UController inp() {
return new UController();
}
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
UController uController = context.getBean("n1",UController.class);
}
三大注入方式
@Autowired
接下来 , 我们使用注解的方式来获取对象。
public class User {
// 使用属性注入的方式获取 Bean对象
@Autowired
private UController uController;
}
加上 @Autowired ,Spring就会自动把对象赋值给变量。
@Autowired
优点 : 实现简单
缺点 :1. 功能性问题 :不能注入不可变(final)对象。(final对象要么直接赋值,要么在构造方法中赋值)
2 . 通用性问题 : 只适用于 IoC 容器。
3 . 设计原则问题 : 更容易违背单一设计原则。(因为属性注入的方式更简便 从而就会导致滥用,所以它更容易违背单一设计原则)。
set 注入
public class User {
// 2. set 注入
private UController uController;
@Autowired
public void setuController(UController uController) {
this.uController = uController;
}
public void hi() {
System.out.println("hi");
}
}
优点 : 更加符合单一设计原则。(相比起属性注入,set 注入更繁琐,因此更加符合单一设计原则)
缺点 : 1. 不能注入不可变对象。
2. 注入对象可被修改。(set方法是普通 set 方法, 可以被重复调用,在被调用时就存在修改的风险)
构造方法注入
public class User {
private UController uController;
@Autowired
public User(UController uController) {
this.uController = uController;
}
}
优点 : 构造方法注入相比与前两种注入方法,它可以注入不可变对象,并且它只会执行一次,也不存在像Setter注入那样,被注入的对象随时被修改的情况。
1 . 可注入不可变对象。
2 . 注入对象不会被修改
3 . 注入对象会被完全初始化
4 . 通用性更好。
附加 :
@Resource VS @Autowried
相同点 : 都是用来实现依赖注入的。
不同点 :1. @ Autowired 来自于 Spring,而 @Resource 来自于 JDK 。
2. 相比起 @Autowired ,@Resource 支持更多的参数设置,例如 name 设置,根据名称获取 Bean。
3. @Autowired 可用与 Setter 注入,构造方法注入和属性注入。而@Resource 只能用于Setter注入和属性注入,不能使用构造方法注入。
Spring 容器中有多个相同的类时
@Component
public class PeopleBeans {
@Bean
public Student student1() {
return new Student();
}
@Bean
public Student student2() {
return new Student();
}
}
@Controller
public class PeoController {
@Resource
private Student stu;
public void fun() {
System.out.println(stu.toString());
}
}
public class Student {
private int age = 10;
public Student() {
System.out.println("实现了构造方法");
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
'}';
}
}
// 测试
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
}
}
报错的原因是因为容器中有两个相同的类 , 程序无法确定将那个依赖注入,因此弹出错误让程序猿处理.
解决方案 :
如果使用 @Resource : 可采用 name 属性来指定依赖的名称
@Resource(name = "student1")
如果使用 @Autowried : 可以配合 @Qualifier 来设置依赖的名称
@Autowired @Qualifier("student1")
Bean 作用域
Bean 作用域 : Bean 在整个 Spring 框架(项目)中的行为模式 .
六大作用域 :
1 . singleton : 单例作用域 (默认情况下的作用域)
2 . prototype : 原型作用域 (多例作用域)
以下四种都是在SpringMVC 中存在 :
3 . request : 请求作用域
4 . session : 回话作用域
5 . application : 全局作用域
6 . websocket : HTTP WebSocket 作用域
其中 singleton 是默认选择的作用域 , 它在 IoC容器中只存在一份 , 无论是通过 ApplicationContext 或者 注解的方式获取到的都是同一个对象.
prototype : 每次从IoC容器中取出都会是一个新的对象 .
其中 request : 每次 http 请求会创建新的Bean实例 , 类似于 prototype .
session : 在一个 http session 中, 定义一个 Bean 实例, 一般用来记录用户的登录信息.
singleton 与 application 的区别
singleton 是 Spring Core 的作用域 , application 是 Spring Web 的作用域.
singleton 作用 IoC 的容器, 而 application 作用于 Servlet 容器 .
设置作用域
使用 @Scope 注解 , 可以修饰类或者方法.
以下两种方式都可以 :
@Scope("prototype")
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
Spring 执行流程
首先启动Spring项目 , 结合 xml 文件对Bean对象进行初始化 , 根据配置好的目录扫描带有五大注解的类或者带有@Bean注解的方法 , 将这些Bean对象注册到容器中. 最后给带有@Autowried 和 @Resource 注解的变量进行依赖注入 .
Bean 生命周期
生命周期指的是一个对象从诞生到销毁的整个生命过程 , 我们将这个过程叫做一个对象的生命周期 .
Bean的生命周期主要分为 5 个部分 :
1 . 实例化 Bean (为对象分配内存 , 将字节码转换成内存中的对象 , 实现了从无到有,这是Bean里面什么也没有)
2 . 设置属性 (Bean 注入和装配)
3 . Bean 初始化 (各种通知 , 进行初始化的前置工作,进行初始化工作 [使用注解 @PostConstruct 初始化, 使用 (xml) init - method 初始化],初始化的后置工作)
4 . 使用Bean
5 . 销毁Bean
End........