SpringBoot:详解Bean装配

在这里插入图片描述

🏡浩泽学编程:个人主页

 🔥 推荐专栏:《SpringBoot从官方文档学习》《java项目分享》
              《RabbitMQ》《Spring》《SpringMVC》

🛸学无止境,不骄不躁,知行合一

文章目录

  • 前言
  • 一、IoC容器的简介
    • BeanFactory接口源码
  • 二、Bean装配
    • 扫描装配
      • 探索启动类
    • 条件装配
    • 自定义Bean
  • 总结


前言

IoC((Inversion of Control,控制反转)容器是 Spring 的核心,可以说 Spring 是一种基于 IoC容器编程的框架。因为Spring Boot 是基于注解的开发 Spring IoC, 所以我们就从全注解的方式来讲诉Bean装配。


一、IoC容器的简介

Spring IoC 容器是一个管理 Bean 的容器,在 Spring 的定义中,它要求所有的 IoC 容器都需要实现接口 BeanFactory,它是一个顶级容器接口。 我们从源码讲诉。

BeanFactory接口源码

package org.springframework.beans.factory;import org.springframework.beans.BeansException;
import org.springframework.core.ResolvableType;
import org.springframework.lang.Nullable;public interface BeanFactory {// 前缀String FACTORY_BEAN_PREFIX = "&";// 多个getBean方法Object getBean(String name) throws BeansException;<T> T getBean(String name, Class<T> requiredType) throws BeansException;Object getBean(String name, Object... args) throws BeansException;<T> T getBean(Class<T> requiredType) throws BeansException;<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;<T> ObjectProvider<T> getBeanProvider(Class<T> requiredType);<T> ObjectProvider<T> getBeanProvider(ResolvableType requiredType);// 是否包含Beanboolean containsBean(String name);//是否单例boolean isSingleton(String name) throws NoSuchBeanDefinitionException;// 是否原型boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// 是否类型匹配boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;boolean isTypeMatch(String name, Class<?> typeToMatch) throws NoSuchBeanDefinitionException;// 获取Bean的类型@NullableClass<?> getType(String name) throws NoSuchBeanDefinitionException;// 获取Bean的别名@NullableClass<?> getType(String name, boolean allowFactoryBeanInit) throws NoSuchBeanDefinitionException;String[] getAliases(String name);
}

分析:

  • 上诉源码中加入了中文注释,通过它们就可以理解这些方法的含义。
  • 这里值得注意的是接口中的几个方法:
    • 首先我们看到了多个getBean 方法,这也是IoC 容器最重要的方法之一, 它的意义是从IoC 容器中获取Bean而从多个getBean方法中可以看到有按类型(bytype)获取Bean 的,也有按名称(by name)获取 Bean 的,这就意味着在 Spring IoC 容器中,允许我们按类型或者名称获取 Bean。这对理解后面将讲到的Spring 的依赖注入(Dependency Injection, DI) 是十分重要的。
    • isSingleton 方法则判断 Bean 是否在 Spring IoC 中为单例。这里需要记住的是在 Spring IoC 容器中,默认的情况下, Bean 都是以单例存在的,也就是使用 getBean 方法返回的都是同一个对象。与isSingleton 方法相反的是 isPrototype 方法,如果它返回的是 true,那么当我们使用 getBean 方法获取Bean 的时候, Spring IoC 容器就会创建一个新的 Bean 返回给调用者。

由于BeanFactory 的功能还不够强大,因此 Spring 在 BeanFactory 的基础上, 还设计了一个更为高级的接口 ApplicationContext。 它是 BeanFactory 的子接口之一, 在 Spring 的体系中 BeanFactoryApplicationContext 是最为重要的接口设计,在现实中我们使用的大部分 Spring IoC 容器是ApplicationContext 接口的实现类。

在这里插入图片描述

  • 在图中可以看到, ApplicationContext 接口通过继承上级接口,进而继承 BeanFactory 接口, 但是在BeanFactory 的基础上,扩展了消息国际化接口(MessageSource)、环境可配置接口 (EnvironmentCapable)、应用事件发布接口(ApplicationEventPublish巳r) 和资源模式解析接口(ResourcePatternResolver),所以它的功能会更为强大
  • 在Spring Boot 当中我们主要是通过注解来装配Bean到 Spring IoC 容器中,为了贴近 SpringBoot 的需要, 这里不再介绍与 XML 相关的 IoC 容器,而主要介绍一个基于注解的 IoC 容器,它就是AnnotationConfigApplicationContext,从名称就可以看出它是一个基于注解的 IoC 容器。 之所以研究它, 是因为Spring Boot 装配和获取 Bean 的方法与它如出一辙。

例:创建一个User类,然后使用AnnotationConfigApplicationContext构建IoC容器

public class User {private Long id; private String userName; 
/**setter and getter **/ 
}
import org.springframework.context.annotation.Bean; 
import org.springframework.context.annotation.Con f工guration ;
import com.springboot.chapter3.po] o.User;
@Configuration 
public class AppConfig { @Bean(name =”user” } public User ini tUser () { User user= new User (); user. set Id (1L) ; user.setUserName (”aa”); return user;}
}

@Configuration 代表这是一个 Java 配置文件, Spring 的容器会根据它来生成IoC 容器去装配Bean;@Bean 代表将 initUser 方法返回的 POJO 装配到 IoC 容器中,而其属性name 定义这个 Bean 的名称如果没有配置它,则将方法名称“initUser”作为 Bean 的名称保存到Spring IoC 容器中。

import org.apache. log4j .Logger; 
import org. springframework.context.ApplicationContext; 
import org. springframework.context annotation.AnnotationConfigApplicat工onContext;
import com.springboot.chapter3.po] o .User;
public class IoCTest { private static Logger log= Logger.getLogger(IoCTest.class); publ工c static 飞roid main (String [] args) { ApplicationContext ctx = new AnnotationConfigApplcationContext(AppConfig. class);User user= ctx.getBean(User.class); }
}

代码中将Java 配置文件 AppConfig 传递给 AnnotationConfigApplicationContext 的构造方法,这样它就能够读取配置了。然后将配置里面的Bean装配到IoC容器中,于是可以使用 getBean方法获取对应的POJO。

二、Bean装配

扫描装配

上诉讲诉的User对象装配就是使用@Bean装配。但是如果一个个的 Bean 使用注解@Bean 注入 Spring loC 容器中,那将是一件很麻烦的事情。好在Spring 还允许我们进行扫描装配 Bean 到 loC 容器中,对于扫描装配而言使用的注解是@Component和@ComponentScan@Component 是标明l哪个类被扫描进入 Spring IoC 容器,而ComponentScan则是标明采用何种策略去扫描装配Bean

@Component(”user") 
public class User { @Value("1") private Long id; @Value("aa"} private String userName; 
/**setter and getter **/ 
}

这里的注解@Component表明这个类将被SpringIoC 容器扫描装配,其中配置的“user"则是作为Bean 的名称,当然你也可以不配置这个字符串,那么IoC容器就会把类名第一个字母作为小写其他不变作为Bean 名称放入到IoC 容器中注解@Value则是指定具体的值,使得Spring IoC给予对应的属性注入对应的值。为了让SpringIoC 容器装配这个类, 需要改造类AppConfig:

import org.springframework.context.annotaton.ComponentScan;
import org.springframework.context.annotation Configuration; 
@Configuration 
@ComponentScan 
public class AppConfig {
}

这里加入了@ComponentScan,意味着它会进行扫描,但是它只会扫描类AppConfig所在的当前包和其子包。也就是@ComponentScan默认扫描当前类所在包及其子包。 所以User类的位置要注意。

测试:

Applicat工onContext ctx = new AnnotationConfigApplicationContext{AppConfig.class) ;
User user= ctx.getBean(User.class); 
log. info(user.getid());

为了更加合理,@ComponentScan还允许我们自定义扫描的包,我们看一下源码:

package org.springframework.context.annotation;import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.core.annotation.AliasFor;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
//在一个类中可重复定义
@Repeatable(ComponentScans.class)
public @interface ComponentScan {// 定义扫描的包@AliasFor("basePackages")String[] value() default {};//定义扫描的包@AliasFor("value")String[] basePackages() default {};//定义扫描的类Class<?>[] basePackageClasses() default {};//Bean name生成器Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;//作用域解析器Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;//作用域代理模式ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;//资源匹配模式String resourcePattern() default "**/*.class";//是否启用默认的过滤器boolean useDefaultFilters() default true;//当满足过滤器的条件时扫描Filter[] includeFilters() default {};//当不满足过滤器的条件时扫描Filter[] excludeFilters() default {};//是否延迟初始化boolean lazyInit() default false;//定义过滤器@Retention(RetentionPolicy.RUNTIME)@Target({})public @interface Filter {//过滤器类型,可以按注解类型或者正则式等过滤FilterType type() default FilterType.ANNOTATION;//定义过滤的类@AliasFor("classes")Class<?>[] value() default {};@AliasFor("value")Class<?>[] classes() default {};//匹配方式String[] pattern() default {};}
}

分析:

  • 首先可以通过配置项basePackages定义扫描的包名,在没有定义的情况下,它只会扫描当前包和其子包下的路径:还可以通过basePackageClasses 定义扫描的类;
  • 其中还有 includeFilters 和 excludeFilters, includeFilters 是定义满足过滤器(Filter)条件的 Bean 才去扫描excludeFilters 则是排除过滤器条件的 Bean,它们都需要通过一个注解@Filter 去定义,它有一个type 类型,这里可以定义为注解或者正则式等类型。 classes定义注解类, pattern 定义正则式类

所以得出三个扫描路径表示:

@ComponentScan ("com.springboot.example.* ")
@ComponentScan(basePackages = {"com.springboot.example.pojo"})
@ComponentScan(basePackageClasses = {User.class} ) 

以及排除扫描包或类,让其不被装配:

//扫描example下所有包除了@Service装配的类
//这样,由于加入了 excludeFilters 的配置,使标注了@Service 的类将不被 IoC 容器扫描注入,这样就可以把它类排除到 Spring IoC容器中了。
@ComponentScan(basePackages = {"com.dragon.restart"},excludeFilters = {@ComponentScan.Filter(classes = Service.class)})

探索启动类

事实上,之前在 Spring Boot 的注解@SpringBootApplication 也注入了@ComponentScan,这里不妨探索其源码:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
//自定义排除的扫描类
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}
), @Filter(type = FilterType.CUSTOM,classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {//通过类型排除自动配置@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};//通过名称排除自动配置类@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};//定义扫描包@AliasFor(annotation = ComponentScan.class,attribute = "basePackages")String[] scanBasePackages() default {};//定义被扫描的类@AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};

显然,通过它就能够定义扫描哪些包。但是这里需要特别注意的是,它提供的exclude和excludeName两个方法是对于其内部的自动配置类才会生效的。为了能够排除其他类,还可以再加入@ComponentScan以达到我们的目的。

条件装配

  • 例如在数据库连接池的配置中漏掉一些配置会造成数据源不能连接上。 在这样的情况下, IoC容器如果还进行数据源的装配, 则系统将会抛出异常,导致应用无法继续。这时倒是希望IoC容器不去装配数据源。
  • 为了处理这样的场景, Spring 提供了@Conditional注解帮助我们,而它需要配合另外一个接口Condition(org.springframework.context.annotation.Condition )来完成对应的功能。

装配的Bean:

@Bean(name = "dataSource", destroyMethod = "close" ) 
@Conditional(DatabaseConditional.class)
public DataSource getDataSource ( 
@Value("${database.driverName}") String driver, 
@Value("${database.url}") String url, 
@Value("${database.username}") String username, 
@Value("{database.password}") String password 
){ Properties props= new Properties(); props.setProperty("driver", driver); props setProperty("url", url); props.setProperty("username", username); props setProperty("password", password); DataSource dataSource = null; try { dataSource = BasicDataSourceFactory.createDataSource(props) ; ) catch (Exception e) { e.printStackTrace();}return dataSource;
}

自定义DatabaseConditional类:

public class DatabaseConditional implements Condition { 
/**
* 数据库装配条件
* 
* @param context 条件上下文
* @param metadata 注释类型的元数据
* @return true 装配 Bean,否则不装配
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { //取出环境配置Environment env = context.getEnvironment(); //判断属性文件是否存在对应的数据库配置return env.containsProperty(”database.driverName” ) && env.containsProperty(”database.url”) && env.containsProperty(” database.username”) && env.containsProperty (”database.password");

matches 方法首先读取其上下文环境, 然后判定是否已经配置了对应的数据库信息。这样,当这些都己经配置好后则返回true。这个时候Spring会装配数据库连接池的Bean,否则是不装配的。

自定义Bean

  • 现实的Java 的应用往往需要引入许多来自第三方的包, 并且很有可能希望把第三方包的类对象也放入到Spring IoC 容器中,这时@Bean注解就可以发挥作用了。
  • 例如,要引入一个DBCP数据源,我们先在pom.xml上加入项目所需要DBCP包和数据库MySQL驱动程序的依赖。
<dependency> <groupid>org.apache.commons</groupid> <artifactid>commons-dbcp2</artifactid> 
</dependency> 
<dependency> <groupid>mysql</groupid> <artifactid>mysql-connector-ava</artifactid>
</dependency>

这样 DBCP 和数据库驱动就被加入到了项目中,接着将使用它提供的机制来生成数据源:

@Bean(name = "dataSource") 
@Conditional(DatabaseConditional.class)
public DataSource getDataSource (){ Properties props= new Properties(); props.setProperty("driver", driver); props setProperty("url", url); props.setProperty("username", username); props setProperty("password", password); DataSource dataSource = null; try { dataSource = BasicDataSourceFactory.createDataSource(props) ; ) catch (Exception e) { e.printStackTrace();}return dataSource;
}

这里通过@Bean 定义了其配置项 name 为“dataSource“,那么 Spring 就会把它返回的对象用名称“dataSource” 保存在 loC 容器中。当然, 你也可以不填写这个名称,那么它就会用你的方法名称作为Bean 名称保存到 IoC 容器中。通过这样,就可以将第三方包的类装配到SpringIoC容器中了。


总结

以上就是SpringBoot的Bean装配的详细讲解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/610704.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++:多态|虚函数、虚表底层原理|多态原理

C&#xff1a;多态|虚函数、虚表底层原理|多态原理 前言一、多态的概念二、多态的定义及实现2.1 多态的构成条件2. 2 虚函数2.3 虚函数的重写2.3.1 虚函数重写的例外1&#xff1a;协变(基类与派生类虚函数返回值类型不同)2.3.2 虚函数重写的例外2&#xff1a;析构函数的重写(基…

【hcie-cloud】【18】华为云Stack灾备服务介绍【容灾解决方案介绍、灾备方案架构介绍、管理组件灾备方案介绍、高阶云服务容灾简介、缩略词】【下】

文章目录 灾备方案概述、备份解决方案介绍容灾解决方案介绍华为云容灾解决方案概览云容灾服务云硬盘高可用服务 (VHA)VHA组网结构VHA逻辑组网架构VHA管理组件介绍VHA服务实现原理云服务器高可用服务&#xff08;CSHA&#xff09;CSHA物理组网架构CSHA逻辑组网架构CSHA服务组件间…

面试宝典进阶之redis缓存面试题

R1、【初级】Redis常用的数据类型有哪些&#xff1f; &#xff08;1&#xff09;String&#xff08;字符串&#xff09; &#xff08;2&#xff09;Hash&#xff08;哈希&#xff09; &#xff08;3&#xff09;List&#xff08;列表&#xff09; &#xff08;4&#xff09;Se…

# 大模型实战作业02

大模型实战作业02 知识库助手搭建 注 有些问题在回答的时候可能出现乱码的情况 可能的原因 模型内部提示词知识库 这部分可以做适当的优化&#xff0c;对于无法回答或回答质量不佳的回答返回特定话术提升用户体验

.gitignore作用和使用以及文件无法变黄或忽略文件无效还是被提交问题

目录 .gitignore作用 .gitignore使用 1.语法规范&#xff08;熟悉正则很容易理解&#xff09; 2.示例 文件无法变黄或忽略文件无效还是被提交问题 解决方案 .gitignore作用 .gitignore 文件在 Git 版本控制系统中扮演着非常重要的角色。在任何 Git 仓库中&#xff0c;.giti…

Spring Boot 和 Spring 有什么区别

Spring Boot 和 Spring 是两个不同的概念&#xff0c;它们服务于不同的目的&#xff0c;但它们之间有着紧密的联系。下面是它们之间的主要区别&#xff1a; 目的和定位&#xff1a; Spring&#xff1a;Spring 是一个开源的 Java 平台&#xff0c;它最初由 Rod Johnson 创建&am…

保姆级AI绘画入门教程 从入门到实操!!!!小白必收藏!!!

手把手教你入门绘图超强的AI绘画程序&#xff0c;用户只需要输入一段图片的文字描述&#xff0c;即可生成精美的绘画。给大家带来了全新保姆级教程资料包&#xff08;文末可获取&#xff09; 很多朋友想尝尝AI绘画的鲜&#xff0c;但是使用市场上的AI程序也好、软件也好&#…

HCIA-Datacom题库(自己整理分类的)_17_简单的命令判断【11道题】

1.华为AR路由器的命令行界面下&#xff0c;save命令的作用是保存当前的系统时间。 解析&#xff1a;Save保存配置 2.VRP界面下&#xff0c;使用命令delete vrpcfg.zp删除文件&#xff0c;必须在回收站中清空&#xff0c;才能彻底删除文件。√ 解析&#xff1a;delete删除到回…

常用连读技巧2

1. how to do it – 咋做 连读的时候的音标是/haʊdə du ɪt/&#xff1a;听起来就像是【薅的毒it】 “how to do” 中的 “w” 和 “to” 之间会发生连读。在口语中&#xff0c;“to” 常常被弱读为 /tə/ 或 /tu/&#xff0c;所以 “how to” 发音时可以近似为 /haʊtə/ 或…

uniapp自定义顶部导航并解决打包成apk后getMenuButtonBoundingClientRect方法失效问题

需求&#xff1a;要在app上的顶部导航提示哪里添加一些东西进去&#xff0c;用uniapp自带的肯定不行啊&#xff0c;所以自定义了所有的页面的顶部导航&#xff0c;之后自定义后用手机调试发现 uni.getMenuButtonBoundingClientRect()这个方法的top获取不到....网上找了很多种方…

Vue 缓存Hook:提高接口性能,减少重复请求

前言 在开发 Web 应用时&#xff0c;我们经常会遇到需要重复调用接口的场景。例如&#xff0c;当用户频繁刷新页面或进行某个操作时&#xff0c;我们可能需要多次请求相同的数据。这不仅会增加服务器负担&#xff0c;还会导致用户体验下降。为此&#xff0c;我们可以使用缓存机…

树莓派Debian系统中如何用mDNS广播自己的ip地址

在 Debian 系统的树莓派上使用 mDNS&#xff08;Multicast DNS&#xff09;广播其 IP 地址通常涉及到 Avahi 服务的使用。Avahi 是 Linux 系统中的一个零配置网络&#xff08;Zeroconf&#xff09;实现&#xff0c;它允许设备在没有配置DNS服务器的情况下通过网络发现服务和主机…

中国电子学会2022年09月份青少年软件编程Scratch图形化等级考试试卷二级真题(含答案)

一、单选题(共25题&#xff0c;共50分) 角色初始位置如图所示&#xff0c;下面哪个选项能让角色移到舞台的左下角?( ) A. B. C. D. 2点击绿旗&#xff0c;执行下面程序&#xff0c;关于小鱼的运动描述正确 A.小鱼不会动 B.小鱼一会儿向上游&#xff0c;一会儿向下游。 C按下…

【代码随想录】刷题笔记Day49

前言 跑了个步吃了个饭洗了个澡以及和母上打了个电话&#xff0c;继续来刷题咯o(*&#xffe3;▽&#xffe3;*)ブ 122. 买卖股票的最佳时机 II - 力扣&#xff08;LeetCode&#xff09; 贪心法 之前写过的&#xff0c;代码直接看【代码随想录】刷题笔记Day35-CSDN博客 动规法…

自动化的运维管理:探究Kubernetes工作机制的奥秘

1 云计算时代的操作系统 Kubernetes 是一个生产级别的 容器编排平台 和 集群管理系统 &#xff0c;能够 创建、调度容器&#xff0c;监控、管理服务器。 容器是什么&#xff1f;容器是软件&#xff0c;是应用&#xff0c;是进程。服务器是什么&#xff1f;服务器是硬件&#…

华硕飞行堡垒FX53VD键盘全部失灵【除电源键】

华硕飞行堡垒FX53VD键盘全部失灵【除电源键】 前言一、故障排查二、发现问题三、使用方法总结 前言 版本型号&#xff1a; 型号 ASUS FX53VD&#xff08;华硕-飞行堡垒&#xff09; 板号&#xff1a;GL553VD 故障情况描述&#xff1a; 键盘无法使用&#xff0c;键盘除开机键外…

详解 ThreadPoolExecutor 的参数含义及源码执行流程?

Java 学习面试指南&#xff1a;https://javaxiaobear.cn 线程池是为了避免线程频繁的创建和销毁带来的性能消耗&#xff0c;而建立的一种池化技术&#xff0c;它是把已创建的线程放入“池”中&#xff0c;当有任务来临时就可以重用已有的线程&#xff0c;无需等待创建的过程&a…

开源C语言库Melon:多线程治理

问题描述 不知你是否有过类似如下的需求&#xff1a; 有一些功能&#xff0c;它们足够单一&#xff0c;但又需要后台持续运行&#xff0c;以容器实现感觉太重了&#xff0c;以进程实现又太琐碎了&#xff0c;以线程实现可以接受但是又不好管理。 这类程序诸如&#xff1a;数据…

详解c++移动构造函数和移动赋值运算符在代码性能中起的作用

对象移动 对象移动&#xff0c;就是把一个不想用了的对象A中的一些有用的数据提取出来&#xff0c;在构建新对象B的时候就不需要重新构建对象中的所有数据——从不想用了的对象A中提取出来的有用数据在构建对象B时都可以拿来使用。 我们知道&#xff0c;拷贝构造函数、拷贝赋…

14 .docker部署若依

文章目录 0.参考资料1. 创建网络2.制作java容器3.容器启动 0.参考资料 https://blog.csdn.net/Pan_peter/article/details/128807946 1. 创建网络 因为java容器的运行需要访问mysql和redis容器&#xff0c;nginx容器需要访问java容器&#xff0c;所以需要创建一个他们能互相…