你好呀,我是沉默王二,一枚有趣的程序员,写的文章一直充满灵气,力求清新脱俗。昨天跑去王府井的小米店订购了一台小米 10,说是一周之内能到货,但我还是忍不住今天就想见到她。见我茶不思饭不想的,老婆就劝我说,与其在瞎想,还不如滚去写你的文章。于是就有了今天这篇“Spring Bean 的常用配置”,通过我和三妹对话的形式。

教妹学 Java,没见过这么放肆的标题吧?“语不惊人死不休”,没错,本篇文章的标题就是这么酷炫,不然你怎么会点进来?

我有一个漂亮如花的妹妹(见上图),她叫什么呢?我想聪明的读者能猜得出:沉默王三,没错,年方三六。父母正考虑让她向我学习,做一名正儿八经的 Java 程序员。我期初是反对的,因为程序员这行业容易掉头发。但家命难为啊,与其反对,不如做点更积极的事情,比如说写点有趣的文章教教她。

“二哥,Spring 基础篇学完后,我有一种强烈的感觉,Spring 真的好强大,就如春风佛面一般。”

“哎呀,三妹,你这个比喻虽然有些牵强,但多少有些诗意。”

“好吧,让我们开始今天的学习吧!”

01、Bean 的 Scope 配置

“二哥,据说 Bean 的 Scope 类型有好几种,用于定义了 Bean 的生命周期和使用环境,你能给我具体说说吗?”

“没问题啊。”

1)singleton

也就是单例模式,如果把一个 Bean 的 Scope 定义为 singleton,意味着一个 Bean 在 Spring 容器中只会创建一次实例,对该实例的任何修改都会反映到它的引用上面。这也是 Scope 的默认配置项,可省略。

来新建一个 Writer 类,内容如下:

public class Writer {private String name;public Writer() {}// getter setter
}

再来新建一个 SingletonConfig 类,内容如下:

@Configuration
public class SingletonConfig {@Bean@Scope("singleton")public Writer getWriterSingleton() {return new Writer();}
}

@Configuration 注解表明当前类是一个配置类,相当于 Spring 配置的一个 xml 文件。

@Bean 注解用在 getWriterSingleton() 方法上,表明当前方法返回一个 Bean 对象(Writer),然后将其交给 Spring 管理。

可以使用 Spring 定义的常量来代替字符串 singleton:

@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)

当然也可以完全省略,于是 SingletonConfig 瘦身了。

@Configuration
public class SingletonConfig {@Beanpublic Writer getWriterSingleton() {return new Writer();}
}

新建 SingletonMain 类,代码如下:

public class SingletonMain {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SingletonConfig.class);Writer writer1 = context.getBean(Writer.class);Writer writer2 = context.getBean(Writer.class);System.out.println(writer1);System.out.println(writer2);writer1.setName("沉默王二");System.out.println(writer2.getName());context.close();}
}

程序输出的结果如下所示:

commonuse.singleton.Writer@19dc67c2
commonuse.singleton.Writer@19dc67c2
沉默王二

writer1 和 writer2 两个对象的字符串表示形式完全一样,都是 commonuse.singleton.Writer@19dc67c2;另外,改变了 writer1 对象的 name,writer2 也跟着变了。

从结果中我们可以得出这样的结论:Scope 为 singleton 的时候,尽管使用 getBean() 获取了两次 Writer 实例,但它们是同一个对象。只要更改它们其中任意一个对象的状态,另外一个也会同时改变。

2)prototype

prototype 的英文词义是复数的意思,它表示一个 Bean 会在 Spring 中创建多次实例,适合用于多线程的场景。

新建一个 PrototypeConfig 类,内容如下:

@Configuration
public class PrototypeConfig {@Bean@Scope("prototype")public Writer getWriterPrototype() {return new Writer();}
}

可以使用 Spring 定义的常量来代替字符串 prototype:

@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)

新建 PrototypeMain 类,代码如下:

public class PrototypeMain {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PrototypeConfig.class);Writer writer1 = context.getBean(Writer.class);Writer writer2 = context.getBean(Writer.class);System.out.println(writer1);System.out.println(writer2);writer1.setName("沉默王二");System.out.println(writer2.getName());context.close();}
}

程序输出的结果如下所示:

commonuse.Writer@78a2da20
commonuse.Writer@dd3b207
null

writer1 和 writer2 两个对象的字符串表示形式完全不一样,一个是 commonuse.Writer@78a2da20,另一个是 commonuse.Writer@dd3b207;另外,虽然 writer1 对象的 name 被改变为“沉默王二”,但 writer2 的 name 仍然为 null。

从结果中我们可以得出这样的结论:Scope 为 prototype 的时候,每次调用 getBean() 都会返回一个新的实例,它们不是同一个对象。更改它们其中任意一个对象的状态,另外一个并不会同时改变。

3)request、session、application、websocket

这 4 个作用域仅在 Web 应用程序的上下文中可用,在实践中并不常用。request 用于为 HTTP 请求创建 Bean 实例,session 用于为 HTTP 会话创建 Bean 实例, application 用于为 ServletContext 创建 Bean 实例,而 websocket 用于为特定的 WebSocket 会话创建 Bean 实例。

02、Bean 的字段注入

“二哥,据说 Spring 开发中经常涉及调用各种配置文件,需要用到 @Value 注解,你能给我详细说说吗?”

“没问题啊。”

1)注入普通字符串

来新建一个 ValueConfig 类,内容如下:

@Configuration
public class ValueConfig {@Value("沉默王二")private String name;public void output() {System.out.println(name);}
}

@Value 注解用在成员变量 name 上,表明当前注入 name 的值为“沉默王二”。

来新建一个 ValueMain 类,内容如下:

public class ValueMain {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpELStringConfig.class);SpELStringConfig service = context.getBean(SpELStringConfig.class);service.output();context.close();}
}

程序输出结果如下:

沉默王二

结果符合我们的预期。

2)注入 Spring 表达式

使用 @Value 注入普通字符串的方式最为简单,我们来升级一下,注入 Spring 表达式,先来个加法运算吧。

@Value("#{18 + 12}") // 30
private int add;

双引号中需要用到 #{}。再来个关系运算和逻辑运算吧。

@Value("#{1 == 1}") // true
private boolean equal;@Value("#{400 > 300 || 150 < 100}") // true
private boolean or;

觉得还不够刺激,再来个三元运算吧。

@Value("#{2 > 1 ? '沉默是金' : '不再沉默'}") // "沉默是金"
private String ternary;

3)注入配置文件

假如你觉得以上这些都不够有意思,那来注入配置文件吧。

在 resources 目录下新建 value.properties 文件,内容如下:

name=沉默王二
age=18

新建一个 ValuePropertiesConfig 类,内容如下:

@Configuration
@PropertySource("classpath:value.properties")
public class ValuePropertiesConfig {@Value("${name}")private String name;@Value("${age}")private int age;public void output() {System.out.println("姓名:" + name + " 年纪:" + age);}
}

@PropertySource 注解用于指定载入哪个配置文件(value.properties),classpath: 表明从 src 或者 resources 目录下找。

注意此时 @Value("") 的双引号中为 $ 符号而非 # 符号,{} 中为配置文件中的 key。

新建一个 ValuePropertiesMain 类,内容如下:

public class ValuePropertiesMain {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ValuePropertiesConfig.class);ValuePropertiesConfig service = context.getBean(ValuePropertiesConfig.class);service.output();context.close();}
}

程序运行结果如下:

姓名:³ÁĬÍõÈý 年纪:18

“糟糕,二哥!中文乱码了!”

“不要怕,三妹,问题很容易解决。”

首先,查看 properties 文件的编码方式。

如果不是 UTF-8 就改为 UTF-8。同时,确保修改编码方式后的 properties 文件中没有中文乱码。

然后,在 @PropertySource 注解中加入编码格式。

@PropertySource(value = "classpath:value.properties",  encoding = "UTF-8")

再次运行程序后,乱码就被风吹走了。

姓名:沉默王二 年纪:18

03、Bean 的初始化和销毁

“二哥,据说在实际开发中,经常需要在 Bean 初始化和销毁时加一些额外的操作,你能给我详细说说怎么实现吗?”

“没问题啊。”

1)init-method/destroy-method

新建一个 InitDestroyService 类,内容如下:

public class InitDestroyService {public InitDestroyService() {System.out.println("构造方法");}public void init() {System.out.println("初始化");}public void destroy {System.out.println("销毁");}
}

InitDestroyService() 为构造方法,init() 为初始化方法,destroy() 为销毁方法。

新建 InitDestroyConfig 类,内容如下:

@Configuration
public class InitDestroyConfig {@Bean(initMethod = "init",destroyMethod = "destroy")public InitDestroyService initDestroyService() {return new InitDestroyService();}
}

@Bean 注解的 initMethod 用于指定 Bean 初始化的方法,destroyMethod 用于指定 Bean 销毁时的方法。

新建 InitDestroyMain 类,内容如下:

public class InitDestroyMain {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(InitDestroyConfig.class);InitDestroyService service = context.getBean(InitDestroyService.class);System.out.println("准备关闭容器");context.close();}
}

程序运行结果如下:

构造方法
初始化
准备关闭容器
销毁

也就是说,初始化方法在构造方法后执行,销毁方法在容器关闭后执行。

2)@PostConstruct/@PreDestroy

新建一个 InitDestroyService 类,内容如下:

public class InitDestroyService {public InitDestroyService() {System.out.println("构造方法");}@PostConstructpublic void init() {System.out.println("初始化");}@PreDestroypublic void destroy() {System.out.println("销毁");}
}

@PostConstruct 注解的作用和 @Bean 注解中 init-method 作用相同,用于指定 Bean 初始化后执行的方法。

@PreDestroy 注解的作用和 @Bean 注解中 destroyMethod 作用相同,用于指定 Bean 被容器销毁后执行的方法。

新建 InitDestroyConfig 类,内容如下:

@Configuration
public class InitDestroyConfig {@Beanpublic InitDestroyService initDestroyService() {return new InitDestroyService();}
}

@Bean 注解中不需要再指定 init-method 和 destroyMethod 参数了。

新建 InitDestroyMain 类,内容如下:

public class InitDestroyMain {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(InitDestroyConfig.class);InitDestroyService service = context.getBean(InitDestroyService.class);System.out.println("准备关闭容器");context.close();}
}

程序运行结果如下:

构造方法
初始化
准备关闭容器
销毁

结果符合我们的预期。

04、为 Bean 配置不同的环境

“二哥,据说 Spring 开发中经常需要将 Bean 切换到不同的环境,比如说开发环境、测试环境、正式生产环境,你能给我具体说说怎么实现的吗?”

“没问题啊。”

来考虑这样一个常见的场景,我们需要为开发环境和正式生产环境配置不同的数据源。

新建 Datasource 类,内容如下:

public class Datasource {private String dburl;public Datasource(String dburl) {this.dburl = dburl;}// getter/setter
}

dbname 用于指定不同环境下数据库的连接地址。

新建 Config 类,内容如下:

@Configuration
public class Config {@Bean@Profile("dev")public Datasource devDatasource() {return new Datasource("开发环境");}@Bean@Profile("prod")public Datasource prodDatasource() {return new Datasource("正式生产环境");}
}

@Profile 注解用于标识不同环境下要实例化的 Bean。

新建 Main 类,内容如下:

public class Main {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();ConfigurableEnvironment environment = context.getEnvironment();environment.setActiveProfiles("prod");context.register(Config.class);context.refresh();Datasource datasource = context.getBean(Datasource.class);System.out.println(datasource.getDburl());context.close();}
}

新建 AnnotationConfigApplicationContext 对象的时候不要指定配置类,等到调用 setActiveProfiles("prod") 方法将环境设置为正式生产环境后再通过 register(Config.class) 方法将配置类注册到容器当中,同时记得刷新容器。

运行程序,输出以下内容:

正式生产环境

然后将 “prod” 更改为 “dev”,再次运行程序,输出以下内容:

开发环境

“二哥,这篇文章中的示例代码你上传到码云了吗?最近 GitHub 访问起来有点卡。”

“你到挺贴心啊,三妹。码云传送门~”

“二哥,你教得真不错,我完全学会了,一点也不枯燥。”

“那必须得啊,期待下一篇吧?”

“那是当然啊,期待,非常期待,望穿秋水的感觉。”

请允许我热情地吐槽一下,这篇文章我不希望再被喷了,看在我这么辛苦搞原创(创意+干货+有趣)的份上,多鼓励鼓励好不好?别瞅了,点赞呗,你最美你最帅

如果觉得文章对你有点帮助,请微信搜索「 沉默王二 」第一时间阅读,回复【666】【1024】更有我为你精心准备的 500G 高清教学视频(已分门别类),以及大厂技术牛人整理的面经一份。

最最重要

实践出真知,实践出真知,实践出真知。重要的话说三遍。

千万不要遇到了一片好文章,就放到了收藏夹,我要等到某某天再去看…

千万不要这样,当你遇到了,就马上就去实践,去学习文章中的知识,你要等的某某天它是不会轻而易举来的,你可能就会错过一次最佳的学习机会。

不要觉得学习的知识很多,认准一个作者,跟着他的节奏,不要三心二意,认真跟一段时间,你就会发现真的学到了很多。

总结一下吧

所以我给大家的建议就是,学习不要盲从,也不要留给明天,留给下一次,该学习的内容一定不要放过。世界上有两种痛苦,其一就是后悔的痛苦。

不要在评论区秀,说什么“先收藏,然后不去学”,有意义吗?你学到了就是自己的,就能领先别人一小步,积少成多,未来你就是引领别人学习的大牛。

看在熬夜写作的份上,送我个赞呗,谢谢。

1、老铁们,关注我的原创微信公众号「沉默王二」,专注于有趣的 Java 技术和有益的程序人生。

2、给我点个赞呗,你最美你最帅,除此之外,还可以让更多的人看到这篇文章,顺便激励下我,再次感谢。

学妹问的Spring Bean常用配置,我用最通俗易懂的讲解让她学会了相关推荐

  1. 学妹问我Java枚举类与注解,我直接用这个搞定她!

    很多人问我学妹长什么样,不多说 上图吧! 学妹问我Java枚举类与注解,我直接一篇文章搞定! 一.枚举类 ① 自定义枚举类 ② enum关键字定义枚举类 ③ enum 枚举类的方法 ④ enum 枚举 ...

  2. 【熬夜猛肝万字博文】学妹问我怎么入门 Javascript,百般盘问下我终于决定贡献出自己的 JavaScript入门笔记(三)

    你好,我是阿ken?? 版权声明:本文为CSDN博主「」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明. 另外,博文中某些图片或内容可能出自网络,如有侵权或问题,请及 ...

  3. Spring Bean的配置及常用属性

    作为 Spring 核心机制的依赖注入,改变了传统的编程习惯,对组件的实例化不再由应用程序完成,转而交由 Spring 容器完成,在需要时注入应用程序中,从而对组件之间依赖关系进行了解耦.这一切都离不 ...

  4. python记忆口诀-学妹问我: 如何提高编程能力

    聊天截图 聊天截图 前言 开局两张图,剩下全靠吹了. 上面这两张图便是写这篇文章的原由. 对话框的另一边,是一位大二计算机科班在读的小姐姐,看似平静的文字背后透露着迷茫与困惑,还对未来的焦虑. 透过屏 ...

  5. python记忆口诀-学妹问我:如何提高编程能力

    聊天截图 聊天截图前言 开局两张图,剩下全靠吹了. 上面这两张图便是写这篇文章的原由. 对话框的另一边,是一位大二计算机科班在读的小姐姐,看似平静的文字背后透露着迷茫与困惑,还对未来的焦虑. 透过屏幕 ...

  6. Spring - Bean注解配置光速入门

    Bean注解配置光速入门 步骤一: 创建 web 项目,引入 Spring 的开发包 在 Spring 的注解的 AOP 中需要引入 spring-aop 的 jar 包 步骤二: 引入相关配置文件 ...

  7. 学妹问单例模式,我用最通俗易懂的讲解让她学会了

    前记 昨天学妹抱怨专业课中的java 23个设计模式.她问:23个设计模式越看越扎心,尤其是单例模式,有没有什么破解之法.我告诉她 java 23个设计模式看上去多,要挑知识重点并结合程序实例来记忆, ...

  8. 学妹问H哥:你是如何平衡工作和生活的?

    作者 l Hollis 来源 l Hollis(ID:hollischuang) 这几天被B站的<后浪>刷屏了,不管别人怎么看,我个人觉得还挺不错的.很多人说<后浪>传达出一种 ...

  9. Spring Bean默认配置为单实例 Spring Bean生命周期

    2019独角兽企业重金招聘Python工程师标准>>> Spring 的Bean默认的是单例的. 如果不想单例需要如下配置: <bean id="user" ...

最新文章

  1. apache服务Forbidden 403问题精彩总结
  2. 浙江大学PAT考试1009~1012(1010上帝是冠军。。)
  3. Essential Studio for mobile MVC如何创建一个Razor应用程序平台
  4. 【PAT (Advanced Level) Practice】1001 A+B Format (20 分)
  5. CNN人脸关键点检测
  6. 好的产品经理是怎样炼成的?
  7. php access allow,PHP标头不适用于Access-Control-Allow-Origin
  8. 厦门大学c语言第七八章作业答案,厦门大学 运筹学 第七、八章作业
  9. 家里电脑是win10,但开机都要3分钟,请问怎么提快电脑速度?
  10. 21个实用便利的PHP代码
  11. ISOIEC27000标准族的介绍与进展
  12. SQL查询时间段方法
  13. redis-trib.rb和redis-cli部署redis主从集群的异同
  14. WEB 视频开发-视频播放器
  15. android 公式编辑,能编辑公式的安卓手机股票软件
  16. Anaconda配置强化学习环境
  17. java教程分享-我赢职场2018年 曹雪松老师java零基础最佳入门视频教程
  18. @keyup.enter.native不生效问题解决
  19. 如何用ChatGPT做会议总结?
  20. 响应式织梦模板玩具动漫类网站

热门文章

  1. 无话可说,北邮211本科不符合华为OD要求,清华硕士符合!
  2. 微信测试公众号如何测试菜单
  3. python numba_Numba初步使用经验
  4. springboot拦截器 跳过_springboot创建拦截器过程图解
  5. wangEditor实现导入word并将内容显示到编辑器中
  6. 第2章 Python语言基础
  7. 2021年本溪高考成绩查询,2021本溪市地区高考成绩排名查询,本溪市高考各高中成绩喜报榜单...
  8. Linux重启网卡失败
  9. 虚拟机上nacos集群配置服务注册不上
  10. 鸿蒙系统4月份升级,华为旗舰手机将在4月起升级鸿蒙系统