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

简介: Spring进阶学习 01、Spring中各个类介绍(一)

一、初识BeanDefinition与BeanFactory


1.1、认识BeanDefinition



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


重要属性: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



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



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


ConfigurationBeanFactory:接口

AbstractBeanFactory:抽象类

AutowireCapableBeanFactory:接口

DefaultListableBeanFactory:抽象类

ListableBeanFactory:接口


1.2.2、认识实现类DefaultListableBeanFactory


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


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

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





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



BeanDefinition、BeanFactory与Bean对象的关系

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


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



二、Bean的生命周期


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


包含下面六个步骤:



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:作用于属性

本次实验目录如下:



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);
    }
}


①成功注入情况


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



②注入失败抛出异常


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


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



③注入失败不抛出异常


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


@Autowired(required = false)
private LoginService loginService;


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




实操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没有值时(单单写个注解):

先判断属性名字在Spring容器中是否存在Bean对象。如果存在,则成功找到Bean对象进行注入。

如果不存在,则根据属性类型去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、类型查找的顺序不经有点怀疑?




@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:字符串注入

目录如下:



这里仅需一个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);
    }
}


情况一:注入成功


运行效果:



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


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




实操2:读取properties配置文件

准备操作


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



②在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的大致相同进行测试:



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


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




实操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实例注入成功


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



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


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





依赖注入注解小总结

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


@Autowired:


属于spring(spring-beans)中的注解。

只有一个属性就是required,默认为true,若是设置为false,依赖注入失败时不会抛出异常!

默认查找bean实例的顺序为类型、name值。若是想要根据自己指定的name去找bean实例,就需要配合@Qualifer(设置name名称)注解!

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


属于jdk自带的注解,在javax包目录下。

有多个属性,比较重要的是name值,使用该注解时带上name属性,就会根据该name从spring容器中去找bean实例。相较于@Autowired注解需要配合@Qualifer才可以。

默认查找bean实例顺序为name、类型。(暂时不太确定自己测试与@Autowired一致,仅仅从异常抛出情况来看)

@Value:


属于spring(spring-beans)的注解。

只有一个value属性,其有三个功能①字符串注入(直接传入,#{}、) 。 ② 读 取 配 置 文 件 使 用 ‘ {})。②读取配置文件使用`)。②读取配置文件使用‘{}表达式,需要配合一个@PropertySource来加载配置文件使用(没有则将"${xxx}"作为字符串传入)。③依赖注入bean实例使用#{}表达式中的值作为bean的name去查找。

bean依赖注入功能若是找不到bean实例就会抛出异常!


相关文章
|
3天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
23 9
|
23天前
|
前端开发 Java 数据库
SpringBoot学习
【10月更文挑战第7天】Spring学习
33 9
|
25天前
|
XML Java 数据格式
Spring学习
【10月更文挑战第6天】Spring学习
19 1
|
29天前
|
Java 测试技术 开发者
springboot学习四:Spring Boot profile多环境配置、devtools热部署
这篇文章主要介绍了如何在Spring Boot中进行多环境配置以及如何整合DevTools实现热部署,以提高开发效率。
52 2
|
29天前
|
前端开发 Java 程序员
springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
这篇文章是关于如何在Spring Boot项目中集成Swagger2和Knife4j来生成和美化API接口文档的详细教程。
49 1
|
29天前
|
Java API Spring
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中拦截器的入门教程和实战项目场景实现的详细指南。
20 0
springboot学习七:Spring Boot2.x 拦截器基础入门&实战项目场景实现
|
29天前
|
Java API Spring
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
这篇文章是关于Spring Boot 2.x中过滤器的基础知识和实战项目应用的教程。
22 0
springboot学习六:Spring Boot2.x 过滤器基础入门&实战项目场景实现
|
29天前
|
Java 关系型数据库 MySQL
springboot学习五:springboot整合Mybatis 连接 mysql数据库
这篇文章是关于如何使用Spring Boot整合MyBatis来连接MySQL数据库,并进行基本的增删改查操作的教程。
49 0
springboot学习五:springboot整合Mybatis 连接 mysql数据库
|
29天前
|
Java 关系型数据库 MySQL
springboot学习四:springboot链接mysql数据库,使用JdbcTemplate 操作mysql
这篇文章是关于如何使用Spring Boot框架通过JdbcTemplate操作MySQL数据库的教程。
22 0
springboot学习四:springboot链接mysql数据库,使用JdbcTemplate 操作mysql
|
29天前
|
Java Spring
springboot 学习十一:Spring Boot 优雅的集成 Lombok
这篇文章是关于如何在Spring Boot项目中集成Lombok,以简化JavaBean的编写,避免冗余代码,并提供了相关的配置步骤和常用注解的介绍。
78 0
下一篇
无影云桌面