Spring进阶学习 01、Spring中各个类介绍

举报
长路 发表于 2022/11/28 19:23:49 2022/11/28
【摘要】 文章目录前言一、初识BeanDefinition与BeanFactory1.1、认识BeanDefinition1.2、认识BeanFactory1.2.1、介绍BeanFactory1.2.2、认识实现类DefaultListableBeanFactoryBeanDefinition、BeanFactory与Bean对象的关系二、Bean的生命周期依赖注入注解@Autowired介绍@Autow

@[toc]

前言

本篇博客是在学习 图灵学院-SPRING源码教程 笔记,若文章中出现相关问题,请指出!

所有博客文件目录索引:博客目录索引(持续更新)

一、初识BeanDefinition与BeanFactory

1.1、认识BeanDefinition

image-20210603114056407

BeanDefinitionBeanDefinition表示Bean定义,Spring根据BeanDefinition来创建Bean对象,BeanDefinition有很多的属性用来描述BeanBeanDefinitionSpring中非常核心的概念。

重要属性:beanClass、scope、isLazy、dependsOn、primary、initMethodName

  • beanClass:表示一个bean的类型,比如:UserService.class、orderService.class,Spring在创建Bean的过程中会根据此属性来实例化得到的对象。
  • scope:表示一个bean的作用域,比如:①scope等于singleton,该bean就是一个单例Bean。②scope等于prototype,该bean就是一个原型bean。
  • isLazy:表示一个bean是不是需要懒加载,原型的isLazy属性不起作用,懒加载的单例bean,会在第一个getBean的时候生成该bean,非懒加载的单例bean,则会在Spring启动过程中直接生成好。

哪些会解析成BeanDefinition呢?

  • 如:@Component@Bean<bean/>


1.2、认识BeanFactory

1.2.1、介绍BeanFactory

image-20210603122942574

BeanFactory:是一种"spring容器",其翻译就是Bean工厂,其可以用来创建Bean、获取Bean,BeanFactory是Spring中非常核心的组件。

image-20210603123401100

BeanFactory的核心子接口和实现类:

  • ConfigurationBeanFactory:接口
    • AbstractBeanFactory:抽象类
  • AutowireCapableBeanFactory:接口
    • DefaultListableBeanFactory:抽象类
  • ListableBeanFactory:接口


1.2.2、认识实现类DefaultListableBeanFactory

介绍一个比较重要的实现类DefaultListableBeanFactory

  • 功能:支持单例Bean、支持Bean别名、支持父子BeanFactory、支持Bean类型转换、支持Bean后置处理、支持FactoryBean、支持自动装配等等

为啥特别介绍这个类呢?因为这个实现类实现了上面提及的所有接口,并且其还有子类

image-20210603123735876

image-20210603123818100

可以从继承图上明显看到该类的继承关系!



BeanDefinition、BeanFactory与Bean对象的关系

简明扼要:BeanFactory将利用BeanDefinition来生成Bean对象。

BeanDefinition相当于是BeanFactory的原材料,Bean对象就相当于是BeanFactory所生产出来的产品。



二、Bean的生命周期

Bean的生命周期:其描述的是Spring中一个Bean创建过程和销毁过程中所经历的步骤,其中Bean创建过程是重点。我们可以利用Bean生命周期机制来对Bean进行自定义加工!

包含下面六个步骤

image-20210603133648040

A过程:BeanDefinition表示Bean定义,其定义了某个Bean的类型,Spring就是利用BeanDefinition来创建Bean的,比如需要利用BeanDefinition中的beanClass属性确定Bean的类型,从而实例化出来对象。

B过程:一个Bean中可以有多个构造方法,此时就需要Spring来判断使用哪个构造方法,这个过程很复杂。通过构造方法推断之后确定一个构造方法后,就可以利用构造方法实例化得到一个对象。

C过程:通过构造方法反射得到一个实例化对象

  • 额外说明:在Spring中,可以通过BeanPostProcessor机制来对实例化进行干预。

D过程:实例化得到的对象此时可以说是不完整的,其不完整意思指的是对对象中的某些属性还没有进行属性填充,也就是Spring还没有自动给某些属性赋值,属性填充就是指的自动注入、依赖注入。

E过程:在一个对象属性填充之后,Spring提供了初始化机制,程序员可以利用初始化机制来对Bean进行自定义加工:①可利用InitializingBean接口来对其他属性进行赋值。(init-method方法注册)。②对Bean中的某些属性进行校验。

F过程:初始化后是Bean创建生命周期后最后一个步骤。AOP机制就是在这个步骤中通过BeanPostProcessor机制实现的,初始化后得到的对象才是真正的Bean对象



依赖注入注解

@Autowired

介绍@Autowired注解类

//构造器、方法、参数、属性、注解类
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {

	boolean required() default true;

}

@Autowired:表示某个属性是否需要进行依赖注入,可以写在属性和方法上。注解中的required属性默认为true,表示没有对象可以注入给属性时抛异常。

使用于作用域不同效果说明

  • 某个属性:Spring在进行Bean的生命周期过程中,在属性填充这一步会基于实例化出来的对象,对该对象中加了@Autowired的属性自动赋值。
    • 过程:Spring会先根据属性的类型(可以说是全限定类名)去Spring容器中找出该类型所有的Bean对象;若是找到多个,则再根据属性的名字从多个中再确定一个。如果required属性为true,并且根据属性信息找不到对象则直接抛异常。
  • 某个方法:当@Autowired注解写在某个方法上时,Spring在Bean生命周期的属性填充阶段,会根据方法的参数类型、参数名称从Spring容器找到对象当做方法入参,自动反射调用该方法。(简而言之与注入属性过程一致,多了一个反射方法过程,方法名可随意)
  • 某个构造器:Spring会在推断构造方法阶段,选择该构造方法来进行实例化,在反射调用构造方法之前,会先根据构造方法参数类型、参数名从Spring容器中找到Bean对象,当做构造方法入参。
    • 值的一提:若是有参构造进行属性依赖注入时并没有根据bean类型、name找到bean实例时,设置了@Autowired(required = false),就会取消异常转而去调用无参构造器。


实操1:作用于属性

本次实验目录如下:

image-20210603142525322

bean类:

@Service
public class LoginService {
}

@Controller
public class LoginController {

    @Autowired(required = false)
    private LoginService loginService;

    @Override
    public String toString() {
        return "LoginController{" +
                "loginService=" + loginService +
                '}';
    }
}

注解配置类:

@Configuration  //表示其是配置类
@ComponentScan("xyz.changlu")  //自动扫描xyz.changlu包下的类注解
public class SpringConfig {
}

测试类:

public class Main{
    public static void main(String[] args) throws IOException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        LoginController bean = applicationContext.getBean(LoginController.class);
        System.out.println(bean);
    }
}

①成功注入情况

按照上面执行,能够正常注入

image-20210603143540048

②注入失败抛出异常

额外操作:将LoginService类上面的@Service移除。

之后运行测试类:由于我们压根没有创建Bean实例,所以IOC容器根据type、name去查找bean来进行依赖注入属性时由于都没有找到就会抛出异常!

image-20210603144007603

③注入失败不抛出异常

额外操作:①将LoginService类上面的@Service移除。②给LoginController类的属性loginService上注解加上参数值false

@Autowired(required = false)
private LoginService loginService;

此时运行测试类,依赖注入失败时并不会抛出异常!

image-20210603144310369



实操2:作用于方法

程序部分可以复用实操1中的代码,只需要删除掉LoginController类中属性上注解和添加一个依赖注入方法即可:

@Controller
public class LoginController {

    private LoginService loginService;

    //作用于方法上,根据方法的参数类型、名称来去找到bean作为实际参数,最终进行注入操作
    @Autowired    //若是找不到对应参数的bean实例,不抛出异常可以为:@Autowired(required = false)
    public void setLoginService(LoginService loginService){  //方法名可以随意
        this.loginService = loginService;
    }
}

一般也是三种情况:同作用于属性一致!

总结:本质就是在进行属性填充阶段,若是在方法上写了@Autowired,那么就会根据其参数的类型、name值来作为查找bean的条件,若是查找到就会当做实例传入到方法参数中,并通过反射调用执行该方法。



实操3:作用于构造器

程序部分可以复用实操1中的代码,只需要修改LoginController类中的属性和添加一个有参构造器进行依赖注入即可:

@Controller
public class LoginController {

    private LoginService loginService;

    //可以写无参构造器,若是有参构造器注入失败(又设置了参数required = false),就会调用无参构造器
    public LoginController() {
    }
	
    @Autowired   //同样根据参数类型、名称去找到bean实例进行依赖注入,并执行构造器进行实例化
    public LoginController(LoginService loginService) {
        this.loginService = loginService;
    }
}

一般也是三种情况:同作用于属性一致!

额外说明:之前无论是作用于属性、方法,设置了@Autowired(required = false)没有找到对应的bean实例时不会抛出异常,而在构造器中设置了的话同样也是不抛出异常,并且其会自动去调用无参构造器,若此时没有无参构造器那么就会报无参构造异常!

小贴士:尽量都要创建无参构造器噢,防止构造器实例化注入失败调用无参构造器来进行实例!



@Resource(javax包下)

介绍@Resource注解

@Target({TYPE, FIELD, METHOD})  //类、字段、方法
@Retention(RUNTIME)
public @interface Resource {

    String name() default "";

    ...
}

@Resource:该注解与@Autowired类似,也是用来进行依赖注入的。其是Java层面(即java默认自带的注解)所提供的注解。

  • 相较于@Autowired是Spring所提供的注解,他们依赖注入的底层实现逻辑也不同。

属性介绍:重点看其中的name属性,针对于name属性是否有值,@Resource的依赖注入底层流程是不同的。该name值就是要查找的bean对象的name。

  • name有值情况:Spring会直接根据所指定的name值去Spring容器中找Bean对象,如果找到了则成功;如果没有找到,则报错。
  • name没有值时(单单写个注解):
    1. 先判断属性名字在Spring容器中是否存在Bean对象。如果存在,则成功找到Bean对象进行注入。
    2. 如果不存在,则根据属性类型去Spring容器找Bean对象,找到一个则进行注入。

小总结:与@Autowired不同的是,当name没有值时@Resource是先根据属性名称去找bean对象,找不到再去根据属性类型去找bean对象。并且@Resource还能够根据指定的name名称去找容器中找bean实例(@Autowired需要配合@Qualifier配合才能根据指定name去找)。



实操

实际上与@Autowired的测试类几乎相同,仅仅只是将@Autowired换为@Resource

@Controller
public class LoginController {

    @Resource
    private LoginService loginService;


    @Override
    public String toString() {
        return "LoginController{" +
                "loginService=" + loginService +
                '}';
    }
}

上面理论说当name值没有时,会先根据属性名去找bean实例,若找不到才根据类型去找的,可是我实际测试时报出的异常去@Autowired抛出的异常原因都是一致的!这让我对于name、类型查找的顺序不经有点怀疑?

image-20210603155852206



@Value

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
	String value();
}

@Value:该注解与@Resource、@Autowired类似,也是用来对属性进行依赖注入的。其能够从properties文件中获取值(能够解析SPEL,即spring表达式),注入属性以及对象依赖注入。

介绍三种用途

  • @Value("changlu")(直接注入字符串):直接将字符串"changlu"赋值给String属性,如果属性类型不是String或无法进行类型转换就会报错。
  • @Value("${name}")(读取properties文件进行注入):会把${}中的字符串当做key,从Properties文件中找出对应的value赋值给属性;若是没有找到,则会把"${name}"当成普通字符串注入给属性。
  • @Value("#{bean1}")(根据name找到bean实例注入):会将#{}中的字符串当做Spring表达式进行解析,Spring会将其中的bean1作为name,根据该name从spring容器中找对应bean,若是找到就进行属性注入,没找到则报错。


实操1:字符串注入

目录如下:

image-20210603163021602

这里仅需一个LoginController类、测试类Main、自动注解配置类SpringConfig即可:

@Controller
public class LoginController {

    @Value("changlu")  //将changlu字符串注入到name中
    private String name;
    
    @Override
    public String toString() {
        return "LoginController{" +
                "name='" + name + '\'' +
                '}';
    }
}

配置类:

@Configuration
@ComponentScan("xyz.changlu")
public class SpringConfig {
}

测试类:

public class Main{

	public static void main(String[] args) throws IOException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        LoginController bean = applicationContext.getBean(LoginController.class);
        System.out.println(bean);
    }
}

情况一:注入成功

运行效果:

image-20210603163253860

情况二:非String类型注入,抛出异常

若是@Value("changlu")这种形式放置在一个其他类型(非String类型)上,就会出现类型转换异常,如下:

image-20210603163448755



实操2:读取properties配置文件

准备操作

①在resource目录下添加cl.properties配置文件,并添加键值对name=changlu

image-20210603163549245

②在LoginController类中的name属性上添加@Value("${name}")读取配置文件

@PropertySource({"classpath:cl.properties"})  //配置文件源,spring就会读取该properties配置文件
@Controller
public class LoginController {

    @Value("${name}")   //使用${}表达式就会默认去properties中去找到name对应的value
    private String name;
    
    @Override
    public String toString() {
        return "LoginController{" +
                "name='" + name + '\'' +
                '}';
    }
}

测试效果

情况一:读取成功

其他与实操1的大致相同进行测试:

image-20210603163931238

情况二:读取配置文件失败

上面是成功在配置文件中找到的情况,若是我们将@Value("${name}")改为 @Value("${age}"),由于age在配置文件中没有对应键值对,就会将字符串"${age}"注入到name属性值上:

image-20210603164653773



实操3:依赖注入bean实例

准备过程

测试环境与实操1中大致相同,需要更改的如下:

①添加一个LoginService类,并设置@Service("loginser")

@Service("loginser")  //为了体现出测试的效果,我们为对应的bean实例设置自定义名称:
public class LoginService {
}

②接着修改LoginController中的属性为LoginService实例

@Controller
public class LoginController {

    @Value("#{loginser}")   //使用#{}表达式,将其中loginser作为name去spring容器中查找bean实例
    private LoginService loginService;

    @Override
    public String toString() {
        return "LoginController{" +
                "loginService=" + loginService +
                '}';
    }

}

测试效果

情况一:根据name正确找到bean实例注入成功

直接根据上面修改,运行测试类即可

image-20210603165150136

情况二:没有找到bean实例报异常

修改:将其中的@Value("#{loginser}")修改为@Value("#{login}"),此时保证login作为name的bean实例没有被进行注册,运行测试类就会出现异常如下:

image-20210603165351046




依赖注入注解小总结

共同点:都可以用来在spring框架中进行依赖注入的。

@Autowired

  1. 属于spring(spring-beans)中的注解。
  2. 只有一个属性就是required,默认为true,若是设置为false,依赖注入失败时不会抛出异常!
  3. 默认查找bean实例的顺序为类型、name值。若是想要根据自己指定的name去找bean实例,就需要配合@Qualifer(设置name名称)注解!

@Resource:属于jdk自带的注解,但能够被spring识别进行依赖注入。

  1. 属于jdk自带的注解,在javax包目录下。
  2. 有多个属性,比较重要的是name值,使用该注解时带上name属性,就会根据该name从spring容器中去找bean实例。相较于@Autowired注解需要配合@Qualifer才可以。
  3. 默认查找bean实例顺序为name、类型。(暂时不太确定自己测试与@Autowired一致,仅仅从异常抛出情况来看)

@Value

  1. 属于spring(spring-beans)的注解。
  2. 只有一个value属性,其有三个功能①字符串注入(直接传入,#{}、 ) 。②读取配置文件使用 {})。②读取配置文件使用` {}表达式,需要配合一个@PropertySource来加载配置文件使用(没有则将"${xxx}"作为字符串传入)。③依赖注入bean实例使用#{}达式中的值作为bean的name去查找。
  3. bean依赖注入功能若是找不到bean实例就会抛出异常!


三、认识FactoryBean

3.1、介绍FactoryBean与BeanFactory的区别

Spring中有两种Bean

在Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,也就是FactoryBean。这两种Bean都被容器管理,但是工程Bean跟普通Bean不同,其返回的对象不是特定类的一个实例,其返回的是该FactoryBean的getObject()方法所返回的对象

Spring框架内部有很多地方有FactoryBean的实现类,在很多应用如:SpringAOPORM、事务管理以及其他第三方框架(hibernate、mybatis、jpa集成都有体现)


分辨BeanFactory与FactoryBean区别

BeanFactory:以Factory结尾,表示它是一个工厂类,是用来管理Bean的一个工厂。

  • 是一个Spring容器,是一个大型工厂,可以生产出各种各样的Bean。

FactoryBean:以Bean结尾,表示它是一个Bean,不同于普通Bean的是,其实现了FactoryBean<T>接口的Bean,根据该Bean的id从BeanFactory中获取的实际上是FactoryBean的getObject()返回的对象,而不是FactoryBean本身,若是要获取FactoryBean的对象可以根据&id名来获取。

  • 本身是一个Bean,同时它相当于一个小型工厂,可以生产出另外的Bean(getObject()中获取)。


3.2、认识FactoryBean

image-20210603192125241

  • getObject():返回的是Bean对象。
  • getObjectType():返回的是Bean对象类型。
  • boolean isSingleton():返回是否为单例,默认是true,表示单例。

FactoryBean:其是Spring所提供的一种较灵活的创建Bean的方式,可以通过实现FactoryBean接口中的getObject()方法来返回一个对象,这个对象就是最终的Bean对象。



3.3、自定义一个FactoryBean

image-20210603193311163

自动配置类

@Configuration
@ComponentScan("xyz.changlu")  //自动扫描xyz.changlu包目录下
public class SpringConfig {
}

User类:下面自定义FactoryBean实际创建得到的一个bean

public class User {
}

自定义FactoryBean类:

@Component("clfb")   //这里设置name为clfb。(若是没有自定义参数,那么就会name为changluFactoryBean)
public class ChangluFactoryBean implements FactoryBean {  //实现FactoryBean接口,并重写两个方法

    //返回的实际bean对象,其name为clfb
    @Override
    public Object getObject() throws Exception {
        return new User();
    }

    //返回bean对象的类型
    @Override
    public Class<?> getObjectType() {
        return User.class;
    }

}

测试类:

public class Main{

	 public static void main(String[] args) throws IOException {
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        //FactoryBean实现类会创建两个Bean对象
        //获取到ChangluFactoryBean创建的bean对象
        Object bean = applicationContext.getBean("clfb");
        System.out.println(bean);
        //获取到ChangluFactoryBean这个bean对象
        Object clfb = applicationContext.getBean("&clfb");
        System.out.println(clfb);
    }
}

运行测试类得到以下结果

image-20210603193650275

总结:对于上面的FactoryBean实际上对应了两个Bean对象:

  1. beanName为"clfb",bean对象为getObject方法所返回的User对象。
  2. beanName为"&clfb",bean对象为ChangluFactoryBean类的实例对象。


四、认识ApplicationContext

ApplicationContext:该接口是比BeanFactory更加强大的Spring容器,它既可以创建bean、获取bean,还支持国际化、事件广播、获取资源等BeanFactory不具备的功能。(该接口多继承了多个接口其中就包含了BeanFactory)

介绍ApplicationContext继承的接口

image-20210604135413494

EnvironmentCapable(环境能力):表示拥有获取环境变量的功能,可以通过ApplicationContext获取操作系统环境变量和JVM环境变量。

  • image-20210604135638875

HierarchicalBeanFactory(分层):拥有获取父BeanFactory、判断某个name是否存在bean对象的功能。

  • image-20210604140200254

ListableBeanFactory(清单):拥有获取所有beanNames、判断某个beanName是否存在beanDefinition对象、统计BeanDefinition个数、获取某个类型对应的所有beanNames等功能。(获取bean名称、是否存在bean、个数、类型)

  • image-20210604135820338

ApplicationEventPublisher:拥有事件发布功能,可以发布事件,这是ApplicationContext相对于BeanFactory比较突出、常用的功能。

  • image-20210604140549094

ResourcePatternResolver:拥有加载并获取资源的功能,这里的资源可以是文件,图片等某个URL资源。

  • image-20210604140725773

MessageSource:拥有国际化功能,比如可以直接利用MessageSource对象获取某个国际化资源(比如不同国家语言所对应的字符)

  • image-20210604140351395


五、认识BeanPostProcessor

BeanPostProcessorSpring所提供的一种扩展机制,可以利用该机制对Bean进行定制化加工,在Spring底层源码实现中,也广泛使用了该机制,BeanPostProcessor通常也叫做Bean后置处理器。

BeanPostProcessor:在Spring中是一个接口,我们定义一个后置处理器(实现该接口即可),在Spring中还存在一些接口继承了BeanPostProcessor,这些子接口是在BeanPostProcessor的基础上增加了一些其他功能。

image-20210604142600080

  • postProcessBeforeInitialization():初始化前方法,表示可以利用这个方法来对Bean在初始化前进行自定义加工。
  • postProcessAfterInitialization():初始化后方法,利用这个方法来对Bean在初始化后进行自定义加工。

介绍子接口(扩展功能):InstantiationAwareBeanPostProcessor

首先看一些Bean后置处理器接口有哪些子类接口与实现类:

image-20210604143221897

我们来其中子接口InstantiationAwareBeanPostProcessor中的方法:主要看其中三个方法,有一个已经过时

image-20210604143334113

  • postProcessBeforeInstantiation():实例化前。
  • postProcessAfterInstantiation():实例化后。
  • postProcessProperties():属性注入后。

这个接口相较于BeanPostProcessor更强大具有实例化前后、属性注入后的方法功能。



AOP是如何工作的?

介绍AOP与六个核心概念

AOP:就是面向切面编程,是一种非常适合在无需修改业务代码的前提下,对某个或某些业务增加统一的功能,比如日志记录、权限控制、事务管理等,能很好的使代码解耦,提高开发效率。

其包含六个核心概念

  • Advice:通知、建议,在Spring中通过定义Advice来定义代理逻辑。
  • Pointcut:切点,表示Advice对应的代理逻辑应用到哪个类、哪个方法上。
  • AdvisorAdvice+Pointcut,表示代理逻辑和切入点的一个整体,程序员可通过定义或封装一个Advisor,来定义切点和代理逻辑
  • Weaving织入,将Advice代理逻辑在源代码级别嵌入到切点的过程
  • Target:表示目标对象,也就是被代理对象,在AOP生成的代理对象中会持有目标对象。
  • Join Point:表示连接点,在Spring AOP中,就是方法的执行点。


AOP的工作原理

AOP是发生在Bean的生命周期过程中的:

  1. Spring生成bean对象时,先实例化出来一个对象,也就是target对象。
  2. 接着对target对象进行属性填充。
  3. 初始化后步骤中,会判断target对象有没有对应的切面。
  4. 如果有切面,就表示当前target对象需要进行AOP。
  5. 通过CglibJDK动态代理机制生成一个代理对象,作为最终的bean对象。
  6. 在代理对象中有一个target属性指向target对象。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。