Spring

Introduction to Spring Framework

Spring

IOC

Inversion Of Control,一种编程模式,将依赖关系的获取和管理转移到了容器或框架中,从而实现了组件的松耦合。

ApplicationContext

就是管理bean的容器,ioc容器。

构建context&bean的方式

  • ClassPathXmlApplicationContext

    1
    2
    3
    <beans +一堆命名空间>
    <bean id="" class=""></bean>
    </beans>
    1
    2
    3
    4
    new ClassPathXmlApplicationContex("这里面放resources目录下的配置文件路径").var
    Object o = context.get("beanId"); //<T> t = context.getBean("beanId",T.class);
    //这里的实际类型就是bean的实际类型,只是用Object来接收它,所以直接打印调用的是重写的tostring
    //默认tostring返回 getClass().getName() + "@" + Integer.toHexString(hashCode()),而且默认的hashCode是本地方法,根据对象的实际内存地址得到哈希码(与内容无关),如果没有哈希冲突,这个哈希码可以唯一标识对象。重写的hashCode大多都是根据内容得到哈希码,内容一样的两个对象得到一样的哈希码,比如两个值相同的字符串。
  • AnnotationConfigApplicationContext

    @ComponentScan("")

    @Configuration @Bean

    1
    new AnnotationConfigApplicationContext(T.class).var
  • 两种可以混合使用

    • 在xml中<context:component-scan base-package="">或者@ComponentScan("");然后注解创建
    • 在配置类上@ImportResource("/.../XXX.xml")导入配置文件;
  • @component

    修饰在类上,context在初始化的时候就会实例化一个该类的对象放入context

  • 集合的创建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <util:list id="list" value-type="java.lang.String">
    <value>El1</value>
    <value>El2</value>
    <value>El3</value>
    </util:list>
    <util:set id="set" value-type="com.wdte.bean.User">
    <value>wdte</value>
    <value>liys</value>
    <value>publishBy</value>
    </util:set>
    <util:map id="map" key-type="java.lang.String" value-type="com.wdte.bean.Note">
    <entry key="note-1" value="note1"/>
    </util:map>

    这里还需要注册类型转换器把id转成实际的bean

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import org.springframework.core.convert.converter.Converter;

    public class StringToUserConverter implements Converter<String, User> {
    @Override
    public User convert(String source) {
    // 在这里执行将字符串转换为User对象的逻辑
    // 例如,根据字符串查询数据库获取User对象
    // 然后返回User对象
    }
    }
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
    <set>
    <bean class="com.example.StringToUserConverter"/>
    </set>
    </property>
    </bean>

DI

Dependency Injection,一种特殊的赋值,通过外部的方式将一个对象的依赖关系注入到该对象中,而不是由该对象自己去创建或获取依赖对象。(解耦)

  • 构造方法注入 <constructor-arg name="" value=""/>
  • setter方法注入(通过外部文件如yaml就是这种方式) <property name="" value=""/>
  • 注解注入 @Component,需要开启包扫描@ComponentScan() || <context:component-scan base-package="">
    • 构造方法和setter方法可以混用,因为默认空参构造
    • 前两种方法的赋值如果传入的不是基本类型,value变ref,引用context中已有的bean
    • 使用工厂模式的工厂方法创建的bean首先要创建工厂实例,然后用factory-bean=“”传入工厂实例和factory-mothod指定工厂方法,然后嵌套<constructor-arg/>传构造方法参数

bean标签是创建bean,里面嵌套的赋值是DI;自动装配是为了更灵活地DI

特殊的有集合bean的创建和DI,还需要引入命名空间

AutoWiring

无需显式指定依赖关系

在Spring框架中,自动装配(Autowiring)是一种通过Spring容器自动将组件之间的依赖关系建立起来的机制。通过自动装配,您可以告诉Spring框架,哪些组件需要依赖哪些其他组件,而Spring会在运行时自动将这些依赖关系满足。

自动装配可以通过注解或XML配置来实现,最常用的方式是使用注解。

要是没有自动装配,需要用户对每个bean的字段一个一个手动赋值,复杂类型的用ref属性引用已经存在的bean

有了自动装配,就不用手动赋值了。配置文件的所有自动装配都不能装配简单类型(包括String、Integer…)

自动装配的类型

  • byName:在context里面找到beanId和字段名一样的bean并赋值

    ​ 需要保证beanId唯一性,并且如果有多个类型不一样的bean使用同一个id会类型转换错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
        <bean id="publishBy" class="com.wdte.bean.User">
    <property name="name" value="publish User"/>
    <property name="age" value="18"/>
    </bean>
    <bean id="content" class="java.lang.String">
    <constructor-arg value="Hello World"/>
    </bean>
    <bean id="note1" class="com.wdte.bean.Note" autowire="byName">
    <property name="id" value="1"/>
    </bean>

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Note {
    private Integer id;
    private String content;
    private User publishBy;
    }
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
    private String name;
    private int age;
    }

    这里content不会被自动装配
  • byType:在context里面找到该类型的唯一一个实例并赋值

    要么有一个该类型实例要么没有,没有或者有多个会报错

  • 构造方法(constructor)

    1. 首先byType再byName

    2. 使用了autowire=”constructor”属性,实例化这个对象的时候就会 根据需要的 DI 在提供的构造方法中选择合适的构造方法实例化

    3. 如果 被选中的构造方法中包含一个找不到的形参(此时context中没有该类型的bean||有多个该类型的bean)就会报错

    4. 没有被构造方法参数列表包含的字段会被设为字段类型的默认值

  • 自定义自动装配(custom):通过实现BeanFactoryPostProcessor接口,可以自定义自动装配逻辑。

  • 无自动装配

通过注解声明自动装配方式

@Autowired byType

@Resource byName

@Autowired + **@Qualifier **byType产生歧义时,@Qualifier(“”) 传入name

可以这样使用:@Service将service层的实现类放入context,自动装配用接口类型接收,接口有多个实现时,用@Qualifier(“”) 指定beanId,或者在其中一个实现类上标记@Primary,首选实现。

构造方法:如果一个类只有一个构造方法,并且该构造方法的参数在Spring容器中有对应的组件,那么Spring会自动将这些参数注入到构造方法中,即使构造方法上没有显式使用@Autowired注解。

对象在实例化的时候就会传参引入依赖,这种做法更保险,但是还是要根据实际需求选择自动装配的方式。

自动装配简化了Spring应用程序的配置,提高了代码的可维护性和可读性。然而,在使用自动装配时,需要谨慎处理好依赖关系,避免出现不清晰的情况,以确保应用程序的正确性。

@Value() 注入简单类型 “”、外部配置文件${}、别的实例的属性值和spEL #{}

bean在 DI 和 自动装配时的先后顺序

场景 如果A类中需要 DI 一个B类类型的字段 结果 A在进行DI时 B还没有注入到IOC容器

解决方案 Spring 在设计之初考虑到这个问题 会尽可能的分析依赖关系 先从最下层的依赖bean创建并放入IOC容器 然后逐级向上创建对象并进行DI 然而 这并不稳妥 Spring只是尽可能做到这个事情

Spring团队也考虑到这个问题 在bean标签上提供了 depends-on 属性 方便我们声明依赖关系 Spring 得到这个依赖关系的树状图后 会先从最底层的bean开始创建并进行DI

DI&Autowring方式对比

配置类 @Configuration @Component
将一个对象注入到IOC容器 将一个对象注入到IOC容器
@Bean注解注在方法上 @Component注解在类上 当前类的一个实例会被放入IOC
自己new 或者 new完set @Value + @Autowired + @Resource
调用被@Bean注解修饰的方法 @Autowired + @Resource 自动装配
往往在注入其他人或者开源团队写好的代码中的对象到IOC容器中 往往是注入自己的一个类的对象到IOC容器中

@Resource是JavaEE规范提供的,不是spring的注解,在别的框架也可以用。

@Resource主要是byName的装配,更适合字段装配;@Autowred主要是byType的装配,不太适合但是可以用在字段上。

@Value

@Value 是 Spring 框架中的一个注解,用于将值注入到 Spring 管理的 bean 中的属性中。这允许你在配置文件中指定值,然后在代码中使用这些值,从而实现了配置和代码的分离。@Value 注解可以用于字段、方法、构造函数参数和方法参数上。

在注入值时,@Value 可以用于以下不同的场景:

  1. 注入简单值: 你可以使用 @Value 注解将一个简单的值(如字符串、数字等)注入到属性中。

    1
    2
    @Value("Hello, Spring!")
    private String greeting;
  2. 注入属性值: 你可以使用 @Value 注解来注入其他 bean 的属性值。

    1
    2
    @Value("#{otherBean.property}")
    private String someProperty;
  3. 注入外部配置: @Value 可以用来注入外部配置文件(如 properties 文件)中的属性值。

    1
    2
    @Value("${app.url}")
    private String appUrl;
  4. SpEL 表达式: 你还可以使用 Spring 表达式语言(SpEL)来计算属性值。

    1
    ‘xxxxxxxxxx2 1@Value("#{ T(java.lang.Math).PI * 2 * radius }")2private double circumference;java

作用域

  • Singleton

    默认的作用域

    跟随context被实例化而被实例化,销毁而销毁。生命周期由context管理

    1
    2
    3
    <bean id="mySingletonBean" class="com.example.MySingletonBean" scope="singleton">
    <!-- Bean properties and configuration -->
    </bean>
  • Prototype

    每次获取时创建一个新的bean,需要调用者手动销毁。context不负责管理它的生命周期

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import javax.annotation.PreDestroy;

    @Component
    @Scope("prototype")
    public class User {
    private String name;
    private int age;

    public User(String name, int age) {
    this.name = name;
    this.age = age;
    }

    // Bean properties and methods

    @PreDestroy
    public void destroy() {
    // Perform cleanup or resource release here
    System.out.println("User Bean is being destroyed");
    }
    }
  • Request

    每次http请求创建一个新的实例,请求结束销毁。

  • Session

    每个用户会话创建一个新的实例,会话结束销毁。

Singleton和Prototype对比

Singleton Prototype
创建时机 在应用程序上下文启动时创建 在每次请求或获取时创建
实例重用 是,每次请求或获取时返回同一个实例 否,每次请求或获取时返回新的实例
生命周期 与应用程序上下文相同 由调用者负责管理和销毁
线程安全性 需要保证线程安全 不用保证线程安全,每个实例在单独的线程中使用
依赖注入 可以被其他Bean注入依赖 可以被其他Bean注入依赖,但每次注入的都是新实例
适用场景 适用于状态无关、共享数据的Bean 适用于每次请求需要新的实例或状态相关的Bean
Spring容器管理 是,由Spring容器负责创建和销毁 否,由调用者负责管理和销毁

Singleton的懒加载

@Lazy 或者 lazy-init="true" 会让singleton作用域的bean懒加载,即:需要使用时再实例化。

与prototype不同的是,实例化后一直在context中,生命周期由context管理。

生命周期

由实例化、赋值、初始化、使用、销毁几个阶段组成

  • 实例化:contructor/factory方法实例化创建bean

  • 赋值:setter方法(标签)注入依赖 赋值

  • 初始化

    • 后置处理器前置初始化方法(需要BeanPostProcessor类型的实例,有一个实例所有的bean都会有这个周期)
    • 初始化前回调方法 实现InitializingBean接口,实现afterPropertiesSet方法
    • 自定义初始方法 init-mothod指定自定义初始方法
    • 后置处理器后置初始化方法(BeanPostProcessor)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    @Bean
    BeanPostProcessor beanPostProcessor(){
    return new BeanPostProcessor() {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("这是后置处理器前置初始化方法from "+beanName);
    return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    System.out.println("这是后置处理器后置初始化方法from "+beanName);
    return bean;
    }
    };
    }
    这是后置处理器前置初始化方法from org.springframework.context.event.internalEventListenerProcessor
    这是后置处理器后置初始化方法from org.springframework.context.event.internalEventListenerProcessor
    这是后置处理器前置初始化方法from org.springframework.context.event.internalEventListenerFactory
    这是后置处理器后置初始化方法from org.springframework.context.event.internalEventListenerFactory
    这是后置处理器前置初始化方法from abean
    Properties seted!
    这是后置处理器后置初始化方法from abean
    这是后置处理器前置初始化方法from bbean
    这是后置处理器后置初始化方法from bbean
    abeanAbean(field1=1, field2=2, field3=3)
    bbeanBbean(field1=4, field2=5)
  • 使用:getBean方法获取使用bean

  • 销毁

    • 销毁前回调方法 @Destroy 方法标记方法
    • 自定义销毁方法 DisposableBean接口desdroy方法
    • context销毁bean

AOP

Aspected Oriented Programming,一种编程思想;

通过预编译和运行期间动态代理实现程序功能统一维护的一种技术;

AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。AOP是OOP的延续。

可以做到在不修改原来的代码的情况下增加业务

  • 切面Aspect

    创建切面类@Aspect标记

  • 切点Cutpoint

    用表达式声明拦截哪些方法

  • 连接点JoinPoint

    向增强方法传的参数

  • 通知Advice

    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing

Dynamic Proxying

Aop 实现原理

Transaction (TX)

Aop的应用

循环依赖

循环依赖(Circular Dependency)是指两个或多个类之间存在相互依赖关系的情况,从而形成了一个闭环。这种情况下,每个类都依赖于另一个类,导致无法在不出现问题的情况下正确地创建这些类的实例。循环依赖可能会导致程序运行时的错误、死锁、性能问题以及代码难以理解和维护。

在软件开发中,循环依赖是应该避免的,因为它们可能会引发一系列问题。下面是一些关于循环依赖的问题和解决方法:

问题:

  1. 无法正确实例化: 当两个或多个类互相依赖时,创建它们的实例可能会导致死循环或栈溢出,从而无法成功初始化这些类。

  2. 不稳定性: 循环依赖可能导致对象的状态不稳定。因为循环依赖可能会导致对象的构造和初始化过程交织在一起,使得对象的状态在不同步骤中发生变化。

  3. 难以理解和维护: 循环依赖会增加代码的复杂性,使代码难以理解和维护。代码中出现循环依赖会使得类之间的关系变得混乱,增加了调试和修改的困难。

解决方法:

  1. 设计调整: 重新考虑类之间的依赖关系,看是否可以进行逻辑上的调整,以减少循环依赖。

  2. 引入中间层: 可以考虑引入一个中间层或接口来隔离循环依赖,从而将直接依赖转变为间接依赖。

  3. 使用延迟初始化: 在 Spring 等容器中,可以使用延迟初始化(Lazy Initialization)来解决循环依赖问题。容器会在需要时才创建对象,从而避免了循环依赖导致的初始化问题。

  4. 使用构造函数注入: 在 Spring 中,使用构造函数注入可以降低循环依赖的风险。构造函数注入在对象创建时就完成了依赖的注入,避免了后续初始化过程中的问题。

总的来说,循环依赖是一种应该尽量避免的情况,因为它会引发一系列问题。在设计和编写代码时,需要注意依赖关系,以确保类之间的依赖关系清晰,避免出现循环依赖。

需要包扫描的注解

  1. @Component 及其衍生注解: 包括 @Component@Service@Repository@Controller 注解,用于定义组件类。这些注解通常被用来标记需要被Spring容器管理的类。
  2. @Configuration: 用于定义配置类,其中包含了Spring Bean的定义和配置信息。
  3. @RestController 和 @Controller: 用于定义Web控制器类。
  4. @Service: 用于定义业务逻辑的服务类。
  5. @Repository: 用于定义数据访问对象(DAO)类。
  6. @Autowired 和 @Inject: 用于自动装配Bean依赖。
  7. @Value: 用于属性注入,允许将外部属性值注入到Bean的字段或方法参数中。
  8. @Aspect: 用于定义切面,通常与AOP一起使用。
  9. 自定义注解: 如果您定义了自己的注解,并希望通过在类上使用这些注解来标记特定的类,那么您需要进行包扫描以使Spring能够发现这些标记的类

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!