水滴石穿-深入理解Spring(一)

本文最后更新于:May 13, 2023 pm

积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里,不积小流无以成江海。齐骥一跃,不能十步,驽马十驾,功不在舍。面对悬崖峭壁,一百年也看不出一条裂缝来,但用斧凿,能进一寸进一寸,能进一尺进一尺,不断积累,飞跃必来,突破随之。

目录

什么是Spring

Spring 框架一般指的都是 Spring Framework,它是很多模块的集合,使用这些模块可以很方便地协助我们进行开发。Spring 是一款开源的轻量级 Java 开发框架,旨在提高开发人员的开发效率以及系统的可维护性。核心功能主要是 IoC 和 AOP。

Spring的优缺点

优点

方便解耦,简化开发。

通过spring提供的IOC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免原编码所造成过度程序耦合;有了spring,用户不再为了单列模式类、属性文件解析等这些很底层的需求编写代码,可以专注于上层的应用。

AOP编程的支持

通过spring提供的AOP功能方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。spring的AOP支持允许将一些通用任务如安全,事务,日志等进行集中式管理,从而提供了更好的复用。

声明事务的支持

在spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明方式灵活地进行事务的管理,提高开发效率和质量。

方便程序的测试

可以用非容器依赖编程方式进行几乎所有的测试工作,在spring里,测试不在是昂贵的操作,而是随手课做的事情没列入spring对junit4支持,可以通过注解方便的测试spring程序。

方便集成各种框架

spring不排斥各种优秀的开源框架,相反,spring可以降低各种框架的使用难度,spring提供了对各种优秀框架(如Strust,Hibernate,Hessian,Quartz)等的直接支持。

降低javaEE API的使用难度

spring对很多难用的java EE API(如JDBC,javaMail,远程调用等)提供了一个薄薄的封装层,通过spring的简易封装,这些java EE API的使用难度大大降低。

缺点

  • 从应用层面来说暂时没有缺点

  • 因为简化开发,如果需要深入给底层去了解就非常困难(上层使用的越简单,底层的封装就越复杂)

  • 源码的缺点:由于spring大并且全(要集成很多框架,提供非常多的扩展点)代码非常庞大,对于学习源码具有一定困难

Spring的Ioc容器

IoC(Inverse of Control:控制反转) 是一种设计思想,而不是一个具体的技术实现。IoC 的思想就是将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。不过, IoC 并非 Spring 特有,在其他语言中也有应用。

  • 控制 :指的是对象创建(实例化、管理)的权力
  • 反转 :控制权交给外部环境(Spring 框架、IoC 容器)

Spring Ioc的实现机制

机制并非原理!

实现机制: 简单工厂 + 反射

  • 简单工厂就是调用这个方法 BeanFactory.getBean(name);
  • 反射就是在工厂模式getBean()方法中通过反射的方式来创建Bean。

反射会根据getBean(name), 传入的name来通过以下两种配置方式找到类全路径然后进行创建Bean对象。

  • 通过spring.xml配置:<bean id="userDao" class="com.test.***.userDao"/>

  • 通过注解配置:@Component("userDao")、@Service等

Ioc和DI的区别是什么

简单理解:Ioc是思想,DI是Ioc的具体实现。

IOC 是英文inversion of control的缩写,意思是控制反转DI 是英文Dependency Injection的缩写,意思是依赖注入

依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦。

  • Ioc 控制反转,指将对象的创建权,反转到Spring容器。
  • DI 依赖注入,指Spring创建对象的过程中,将对象依赖属性通过配置进行注入。
  • 依赖注入是beanFactory生产bean时为了解决bean之间的依赖的一种技术。

BeanFactory的作用

简单理解:就是用来生产Bean的。

  • BeanFactory是Spring中核心的一个顶层接口。
  • BeanFactory 一个最简单的Spring容器,给依赖注入(DI)提供了基础的支持。

BeanDefinition的作用

主要负责存储Bean的定义信息,决定Bean的生产方式。

1
2
3
<bean id="" class="" scope="" lazy-init="" abstract="" autowire="">
<property name="" value=""></property>
</bean>

其中,id、class、scope等等就是定义信息。

AbstractBeanDefinition部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
public static final String SCOPE_DEFAULT = "";
public static final int AUTOWIRE_NO = 0;
public static final int AUTOWIRE_BY_NAME = 1;
public static final int AUTOWIRE_BY_TYPE = 2;
public static final int AUTOWIRE_CONSTRUCTOR = 3;
/** @deprecated */
@Deprecated
public static final int AUTOWIRE_AUTODETECT = 4;
public static final int DEPENDENCY_CHECK_NONE = 0;
public static final int DEPENDENCY_CHECK_OBJECTS = 1;
public static final int DEPENDENCY_CHECK_SIMPLE = 2;
public static final int DEPENDENCY_CHECK_ALL = 3;
public static final String INFER_METHOD = "(inferred)";
@Nullable
private volatile Object beanClass;
@Nullable
private String scope;
private boolean abstractFlag;
@Nullable
private Boolean lazyInit;
private int autowireMode;
}

自定义配置的Bean的定义信息会被加载到BeanDefinition,一个Bean就会创建一个BeanDefinition,多个则会创建多个BeanDefinition。

所有的Bean都会存在于BeanDefinitionMap中,key为Bean的名字,Value就是BeanDefinition。

1
private final Map<String, BeanDefinition> beanDefinitionMap;

最后就会交给BeanFactory来进行生产。

BeanFactory和ApplicationContext的区别

ApplicationContext是BeanFactory的子接口。都可以作为容器。

  • ApplicationContext中

  • BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

  • ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

  • 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

  • BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用(BeanPostProcessor、BeanFactoryPostProcessor为后置处理器),但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

  • BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

BeanFactory和FactoryBean的区别

  • BeanFactory是一个工厂,也是一个容器,是来管理和生产bean的。
  • FactoryBean是一个bean,但是一个特殊的bean,也是由BeanFactory来管理的。
  • FactoryBean里面的getObject()是用来获取FactoryBean产生的对象,相当于一个工厂方法,通过此方法可以产生任何对象。在BeanFactory中使用&来得到FactoryBean本身,用来区分通过容器获取FactoryBean产生的对象还是获取FactoryBean本身。如:context.getBean(“&userMapper”);
  • FactoryBean是一个接口,必须被一个bean实现,实现了此接口的bean将会发生”变异”。实现FactoryBean的bean通过重写getObject()方法来重新返回一个对象。如:一个bean为User,原本应该返回User对象,但是现在重写getObject()方法并返回一个其他对象,那么这个bean就不再是原本user的bean了。

配置Bean的方式

通过配置文件:<bean id="aop" class="com.tothefor.springAOP.aspectJAOP"></bean>

使用注解:@Component、@Service、@Controller、@Repository。(需要配置扫描包<context:component-scan base-package="com.tothefor.dao"></context:component-scan>

JavaConfig配置类:@Bean(标注在方法上,方法返回的对象成为bean,通常和@Configuration一起使用)

@Import(3中方式)

Bean的作用域

  • singleton : 唯一 bean 实例,Spring 中的 bean 默认都是单例的,对单例设计模式的应用。
  • prototype : 每次请求都会创建一个新的 bean 实例。
  • request : 每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
  • session : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
  • global-session : 全局 session 作用域,仅仅在基于 portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

配置作用域

xml文件:

1
<bean id="..." class="..." scope="singleton"></bean>

注解:

1
2
3
4
5
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
return new Person();
}

Spring中Bean是否是线程安全的

Spring 不保证 bean 的线程安全。默认 spring 容器中的 bean 是单例的。当单例中存在竞态条件,即有线程安全问题。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.tothefor.sprbean;

/**
* @Author DragonOne
* @Date 2022/6/6 16:41
* @墨水记忆 www.tothefor.com
*/
public class UserService {
private String name;
public String show(String name){
this.name = name + "hello";
try {
Thread.sleep(100);
}catch (Exception e){
e.printStackTrace();
}
return name;
}
}

解决安全问题:将成员变量的声明放到方法中即可;或者修改为多例(prototype)、声明为ThreadLocal。

实例化Bean的方式

  • 构造器方式(反射)。
  • 静态工厂方式。
1
<bean id="book" class="com.tothefor.springAOP.BookImpl" factory-method="creasTest"></bean>

creasTest即为静态方法。

  • 实例工厂方式(@Bean)。
1
2
3
4
<bean id="StuFactory" class="com.tothefor.dao"></bean>
<bean id="Stu" class="com.tothefor.daoFactory"
factory-bean="StuFactory"
factory-method="creasTest"></bean>
  • FactoryBean方式。