理论
什么是单例模式
保证
整个系统中一个类只有一个对象的实例
,实现这种功能的方式就叫单例模式
常用的
service
和
dao
层的对象
通常都是单例的
,而
多例
则指
每个请求用一个新的对象来处理
,比如 action
spring
中的
bean
和
spring mvc
中的
controller、service、dao
层中
通过@autowire
的
依赖注入对象
默认都是单例的
特点
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
为什么要用单例模式
1
单例模式
节省公共资源
比如:
大家都要喝水
,但是
没必要每人家里都打一口井
是吧,通常的做法是
整个村里打一个井
就够了,大家都从这个井里面打水喝。
对应到我们计算机里面,像
日志管理、打印机、数据库连接池、应用配置
。
2
单例模式
方便控制
就像
日志管理
,如果
多个人同时来写日志,你一笔我一笔那整个日志文件都乱七八糟
如果想要
控制日志的正确性
,那么
必须要对关键的代码进行上锁
,
只能一个一个按照顺序来写
而单例模式
只有一个人来向日志里写入信息
方便控制,避免了这种多人干扰的问题出现。
几种实现方式
饿汉模式 (线程安全,调用效率高,但是不能延时加载)
饿汉模式的意思是,我
先把对象(面包)创建好
,等我
要用(吃)的时候
,
直接来拿
就行了
public class Singleton {
//先把对象创建好
private static final Singleton singleton = new Singleton();
private Singleton() {}
//其他人来拿的时候直接返回已创建好的对象
public static Singleton getInstance() {
return singleton;
}
}
这种模式是
最简单最省心
的,不足的地方是
容易造成资源上的浪费
(比如:
我事先把面包都做好了,但是你并不一定吃,这样容易造成资源的浪费
)
优点:没有任何锁,执行效率高,用户体验比懒汉式单例模式更好
缺点:类加载的时候就初始化,不管用不用都占内存空间
建议:
适用于单例模式较少的场景
如果我们在程序启动后,一定会加载到类,那么用饿汉模式实现的单例简单又实用;
如果我们是写一些工具类,则优先考虑使用懒汉模式,可以避免提前被加载到内存中,占用系统资源。
懒汉模式 (线程安全,调用效率不高,可以延时加载)
饿汉模式
可能会
造成资源浪费
的问题,所以就有了懒汉模式,懒汉模式的意思是,我
先不创建类的对象实例
,等你
需要的时候
我
再创建
/**
* 单例模式案例
*/
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
//获取对象的时候再进行实例化
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
}
懒汉模式在并发情况下可能引起的问题
懒汉模式
解决了
饿汉模式
可能
引起的资源浪费
问题,因为这种模式
只有在用户要使用的时候
才会
实例化对象
但是这种模式
在并发情况下
会出现
创建多个对象
的情况。
因为
可能出现外界多人同时
访问
SingleCase.getInstance()
方法,这里可能会出现
因为并发问题导致类被实例化多次
,所以懒汉模式
需要加上锁synchronized (Singleton.class)
来
控制类只允许被实例化一次
如果
不加锁并发的情况下
会出现这种情况
加锁后
就
不会出现多个线程同时执行相同代码
的情况,因为
线程是按队列的形式执行的
,只有
当前一个线程执行完之后才能进入代码块
懒汉模式加锁引起的性能问题
在上面的案例中,我们
通过锁的方式保证了单例模式的安全性
,因为
获取对象的方法加锁
,
多人同时访问只能排队等上一个人执行完才能继续执行
,但加锁的方式
会严重影响性能
解决方案一:双重检查加锁(DCL)
/**
* 描述:懒汉式单例模式---双重检查锁
* 相比单锁而言,双重检查锁性能上虽然有提升,但是依旧用到了synchronized关键字总归要上锁,对程序性能还是存在一定的性能影响
* 不算最优--存在优化空间
*
* 建议:如果我们在程序启动后,一定会加载到类,那么用饿汉模式实现的单例简单又实用;
* 如果我们是写一些工具类,则优先考虑使用懒汉模式,可以避免提前被加载到内存中,占用系统资源
**/
public class Singleton {
//Java提供了volatile关键字来保证多线程可见性,当其中一个线程对某一个值修改后,其他线程能看到
//保证线程间变量的可见性,还有一个作用就是阻止局部重排序的发生
private volatile static Singleton singleton = null;
private Singleton() { }
public static Singleton getInstance() {
if (singleton == null) {//先验证对象是否创建
//使用了synchronized 同步代码块
synchronized (Singleton.class) {//只有当对象未创建的时候才上锁
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
双检测锁定
的方式 是只有
当对象未创建的时候才对请求加锁
,
对象创建以后都不会上锁
,这样
有效的提升了程序的效率
,
也可以保证只会创建一个对象的实例
DCL是
完美的解决了单例模式中性能和资源浪费的问题
,但是DCL在并发情下也会存在一个问题,因为
Jvm指令是乱序的
情况如下:
线程1调用getInstance 获取对象实例,因为对象还是空未进行初始化
此时线程1会执行new Singleton()进行对象实例化
而当线程1的进行new Singleton()的时候JVM会生成三个指令。
指令1: 分配对象内存。
指令2: 调用构造器,初始化对象属性。
指令3: 构建对象引用指向内存。
因为
编译器会自作聪明的对指令进行优化
,指令优化后顺序会变成这样:
1、执行指令1:分配对象内存
2、执行指令3:构建对象引用指向内存。
3、然后正好这个时候CPU 切到了线程2工作
而线程2此时也调用getInstance获取对象,那么线程2将执行下面这个代码 if (singleton == null)
此时线程2发现对象不为空(因为线程1已经创建对象引用并分配对象内存了)
那么线程2会得到一个没有初始化属性的对象(因为线程1还没有执行指令2)。
所以在这种情况下,双检测锁定的方式会出现
DCL失效
的问题
解决方案二:用内部类实现懒汉模式
如下
静态内部类式(线程安全,调用效率高,但是可以延时加载)
当
外部类
被访问时,并
不会加载内部类
,所以
只要不访问 SingletonHoler 这个内部类
,
private static Singleton singleton = new Singleton() 不会实例化
,这就
相当于实现懒加载
的效果
只有当
SingletonHoler.singleton
被调用时
访问内部类的属性
,此时
才会将对象进行实例化
,这样
既解决了饿汉模式
下可能造成
资源浪费
的问题,也
避免了了懒汉模式下的并发问题
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
/*在返回结果前,一定会先加载内部类*/
return SingletonHoler.singleton;
}
//定义静态内部类
private static class SingletonHoler {
//当内部类第一次访问时,创建对象实例
private static final Singleton singleton = new Singleton();
}
}
注册式单例模式
最好!!!!枚举单例(线程安全,调用效率高,不能延时加载)
单元素的枚举类型
已经成为
实现Singleton的最佳实践
/*
通过enum关键字来实现枚举,在枚举中需要注意的有:
1. 枚举中的属性必须放在最前面,一般使用大写字母表示
2. 枚举中可以和java类一样定义方法
3. 枚举中的构造方法必须是私有的
4. 通过一个java类来模拟枚举的功能
*/
package com.bjpowernode.test;
public class Person {
private Person() {}
/*这里有几个原因关于为什么在Java中宁愿使用一个枚举量来实现单例模式:
1、 自由序列化;
2、 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);
3、 线程安全;
懒汉式枚举实现 Singleton*/
public enum Singleton { // 相当于Singleton类
INSTANCE; // 枚举里的属性相当于Singleton的实例
private Person instance;
Singleton() {// 私有构造函数 默认是 private
instance = new Person(); // 在构造函数中完成实例化操作
}
public static Person getInstance() {// 提供公有方法对其访问
return instance;
}
}
// 使用Singleton.INSTANCE.getInstance();来调用
}
容器式单例模式
适用于实例非常多的情况,便于管理,但是是非线程安全的
/**
* 描述:注册式单例模式/登记式单例模式,将每个实例都登记到一个地方,使用唯一的标识获取单例。
* 注册单例模式有两种:枚举式单例模式+容器式单例模式
* 建议:容器式单例模式适用于实例非常多的情况,便于管理,但是是非线程安全的。
*/
public class ContainerSingleton {
private ContainerSingleton() {}
private static Map<String, Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(String className) {
synchronized (ioc) {
if (ioc.containsKey(className)) {
return ioc.get(className);
}
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
}
}
实战
适用场景
单例模式只允许创建一个对象,因此
节省内存,加快对象访问速度
,因此对象需要
被公用的场合适合使用
,如
多个模块使用同一个数据源连接对象
等等。
需要频繁实例化然后销毁的对象。
创建对象时耗时过多或者耗资源过多,但又经常用到的对象
需要频繁访问一个对象,可以用单例,避免创建过多的垃圾
有状态的工具类对象。
-频繁访问数据库或文件的对象。
应用场景
其实那些所谓的常用场景,只要写一次,后期就基本上不会去改动了。所以
用得非常少
window 的控制面板、任务管理器、回收站
网站的计数器
应用程序的日志应用:log4j、slf4j、logkback
项目中配置文件的读取
线程池(管理多个线程):java 自带线程池
数据库连接池(管理多个数据库连接):c3po 等
文件系统
1.外部资源,每台计算机有若干个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。
内部资源:大多数软件都有一个(或多个)属性文件存放系统配置,这样的系统应该有一个对象管理这些属性文件
2.Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗?不信你自己试试看哦~
3.windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
4.网站的计数器,一般也是采用单例模式实现,否则难以同步。
5.应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
6.Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
7.数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
8.多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
9.操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
控制资源的情况下,方便资源之间的互相通信。如线程池等。
在Java中,运行时类也就是Runtime类,被设计成是单例的饿汉式
spring 中的bean 和 spring mvc 中的controller、service、dao层中通过@autowire的依赖注入对象默认都是单例的,使用单例的目的当然是节约内存节省资源
J2EE中的ServlertContext、SerletContextConfig等、Spring框架应用中的ApplicationContext、数据库连接池
比如在项目中有些
相关的配置
(是
整个系统通用的配置
),如果
把这所有的配置都放在一个类里面存储
,这些配置数据由
一个单例对象统一读取
,然后服务进程中的
其他对象
再
通过这个单例对象获取这些配置信息
,这个时候,
在程序的任何地方用到的配置都是一样的
用到的时候,
创建一个类,给里面添加一些配置
。
要修改的时候,再创建一个类,然后添加一些同样的配置,这个时候就是浪费资源
注意
单例模式
在
多线程的
,应用场合下必须小心使用。如果当
唯一实例尚未创建
时,有
两个线程同时调用创建方法
,那么它们
同时没有检测到唯一实例的存在
,
从而同时各自创建了一个实例
,这样
就有两个实例被构造出来
,从而
违反了单例模式中实例唯一的原则
解决这个问题的办法是为
指示类是否已经实例化的变量
提供
一个互斥锁(虽然这样会降低效率)
优点:
-
在
单例模式中
,
活动的单例只有一个实例
,对
单例类的所有实例化
得到的
都是相同的一个实例
。这样就
防止其它对象对自己的实例化
,
确保所有的对象都访问一个实例
-
单例模式具有
一定的伸缩性
,
类自己来控制实例化进程
,类就在改变实例化进程上有相应的伸缩性。 -
提供了对唯一实例的
受控访问
。 -
由于
在系统内存中只存在一个对象,因此可以 节约系统资源
,当 需要
频繁创建和销毁的对象时单例模式无疑可以提高系统的性能
。 -
允许可变数目的实例。
-
避免
对共享资源的多重占用
。
缺点:
-
不适用于变化的对象
,如果
同一类型的对象
总是要
在不同的用例场景发生变化
,单例就会引起数据的错误,不能保存彼此的状态。 -
由于
单例模式中没有抽象层
,因此单例类的扩展有很大的困难。 -
单例类的职责过重
,在一定程度上违背了“单一职责原则”。 -
滥用单例将带来一些负面问题,如
为了节省资源将数据库连接池对象设计为的单例类
,
可能会导致共享连接池对象的程序过多而出现连接池溢出
;如果
实例化的对象长时间不被利用,系统会认为是垃圾而被回收
,这将导致对象状态的丢失。
使用时不能用反射模式创建单例,否则会实例化一个新的对象
使用懒单例模式时注意线程安全问题
饿单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(如登记式模式)
优雅的使用单例模式
Java使用构造方法去创建对象
可以有三种方式:
使用new关键字
使用Class.getInstance(通过反射调用无参构造方法)
使用Constructor.newInstance(实则也是通过反射的方式调用任何构造方法)
单例模式
私有化了构造方法
,所以
其他类无法使用通过new的方式去创建对象
,在
其他类使用该类的实例时,只能通过getInstance去获取
。但是
可以通过Constructor反射的方式获取私有化的构造器然后通过构造方法去创建对象
最成功的单例并不是双重检验锁
,而是
枚举
,
枚举本身就是一种单例
,并且
无法使用反射攻击
,再一个最优雅的是Spring本身实现的单例:
常用Spring中
@Repository、@Component、@Configuration @Service
注解作用下的类
默认都是单例模式
的,所以,我目前认为在Spring下使用单例
最优的方式是将类@Component注册为组件
使用场景主要有:
数据库配置、Redis配置、权限配置、Filter过滤、webMvcConfig、swagger及自定义的时间转换器、类型转换器、对接第三方硬件时,调用硬件的dll、so文件等
Spring 中注解默认模式
Spring
实现单例的原因
把
类注册为组件Bean后
,从
运行开始到结束
,
类只加载到内存一次
,
类进行初始化,该组件的生命周期就交由Spring容器管理
,声明为单例的组件
在Spring容器只会实例化一个Bean
,
多次请求中复用同一个Bean
,Spring会
先从缓存的Map中查询是否存在该Bean,
如果
不存在才会创建对象
@Component 默认实例化的对象是单例
@Repository 默认单例
@Service 默认单例
@Controller 默认单例
@Scope 改变作用域,改变单例多例
1. singleton: 单例模式,当spring创建applicationContext容器的时候,spring会欲初始化所有的该作用域实例,加上lazy-init就可以避免预处理;
2. prototype:原型模式,每次通过getBean获取该bean就会新产生一个实例,创建后spring将不再对其管理;
====下面是在web项目下才用到的===
3. request:搞web的大家都应该明白request的域了吧,就是每次请求都新产生一个实例,和prototype不同就是创建后,接下来的管理,spring依然在监听
4. session: 每次会话,同上
5. global session:全局的web域,类似于servlet中的application
好了,上面都说了spring的controller默认是单例,那很自然就是singleton了。
Spring实现管理的方式
@Component + @xxxApplication 类上加 @ComponentScan
@Configuration + @Bean 当需要引入第三方库组件到项目中时,无法在第三方的组件上添加 @Component 和 @Autowired 注解的,因为第三方库的代码都是 only-read 的,所以需要使用显式配置的方式
full 模式和 Lite 模式
Full模式、Lite模式
是
针对于Spring
的
配置“类”
而言的,xml配置不能与之相提并论
@Configuration+@Bean
注解
可以扫描到方法级别
的配置,在
使用@Component的类
中
@Bean注解声明的方法上
,或者
只使用@Bean注解声明的方法
都被
称为是配置的Lite模式
,而使用
@Configuration声明的类+@Bean声明的方法
被称为
Full模式
。具有以下特点的配置都
被称为Lite模式
:
类上标注有@Component注解
类上标注有@ComponentScan注解
类上标注有@Import注解
类上标注有@ImportResource注解
若类上没有任何注解,但类内存在@Bean方法
标注有@Configuration(proxyBeanMethods = false)
总结
只对类进行了标注, 就是 lite 模式
对类 和类中的方法进行标注,就是 full 模式
@Configuration注解中,proxyBeanMethods 默认(不写)是true,自Spring 5.2开始,几乎所有内置的@Configuration配置类都使用了proxyBeanMethods = false
1. proxyBeanMethods 为true时,需要使用到CGlib动态代理,CGLib将继承用到了极致,CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法,反之,如果不使用CGlib动态代理,就不用生成CGLib的子类,从而提高运行速度。
2. 既然proxyBeanMethods 为true的时候,该类被声明为配置类,反之,proxyBeanMethods 为false的时候,就可以将Lite模式的配置类视为普通类,所以使用@Bean注解的方法,可以当成普通方法,可以使用private、final、static修饰符。
Lite模式的缺点:各个Bean之间不能通过方法互相调用
此时就体现了Full模式的优点:Full模式的配置类在Spring容器中是其本身,保证在运行时单例,在多次使用时,都是一个实例
@Bean 注解在 @Configuration 类中声明,称之为“full”模式
@Component 注解组合使用时,称之为“lite”模式
两种模式的差异:
如果只是把 @Bean 注解用在 方法上 ,并且 各个@Bean注解的 方法之间没有调用
上述两种模式达到的效果基本相同。都可以把@Bean注解方法返回的对象作为bean注册到容器中
如果各个 @Bean 注解的 方法之间有相互调用 ,那么两种模式就会有很大的区别-与full模式下的@Configuration不同
lite模式下 @Bean方法 互相调用 无法声明各个Bean之间的相互依赖关系
通常,@Bean方法被声明在@Configuration类里面。这种情况下,同一个类的里@Bean方法可能直接调用另一个@Bean方法,这确保了beans之间的引用强类型和导向的
为何要区分full和lite模式
因为被Spring管理的类功能比较多,有一些是业务类
这些类解析起来比较简单和有规律,可归纳为lite模式一类
有一些类需要额外一些逻辑,需要生成CGLIB代理,所以这一类就被划分到full模式一类
建议使用Full模式, @Configuration + @Bean,以避免奇奇怪怪的问题。
full 模式
被@Configuration修饰,且属性proxyBeanMethods = true(proxyBeanMethods 默认为true)
full模式
使用特性:
full模式下的配置类会 被CGLIB代理生成代理类 取代原始类型(在容器中)
full模式下的 @Bean方法 不能是private和final
单例scope下 不同@Bean方法 可以互相引用,达到单实例的语义
Teacher.java
@Data
public class Teacher {
private String tName;
private int tAge;
public Teacher() {
System.out.println("teacher create current INSTANCE is : " + this.hashCode());
}
}
TeaWebConfig.java
@Configuration
public class TeaWebConfig {
@Bean
public Teacher teacher() {
return new Teacher();
}
@Bean
public String equalsTeacher(Teacher teacher) {
System.out.println("invoke others Bean's INSTANCE is "+teacher.hashCode());
//使用teacher()模拟不同Bean之间的调用
System.out.println("invoke Constructor's INSTANCE is "+teacher().hashCode());
return "万物基于MIUI";
}
}
Test.java
public static void main(String[] args) {
ApplicationContext teaContext = new AnnotationConfigApplicationContext(TeaWebConfig.class);
Teacher teacher = teaContext.getBean(Teacher.class);
System.out.println("Test.invoke + : " + teacher.hashCode());
}
输出
teacher create current INSTANCE is : 6018
invoke others Bean's INSTANCE is 6018
invoke Constructor's INSTANCE is 6018
Test.invoke + : 6018
可以看到,Full模式下,配置类对象在Spring中是单例
Lite模式
没有被@Configuration修饰,被@Component修饰
没有被@Configuration修饰,被@ComponentScan修饰
没有被@Configuration修饰,被@Import修饰
没有被@Configuration修饰,被@ImportResource修饰
没有任何Spring相关注解,类里面有@Bean修饰的方法
被@Configuration修饰,但是属性proxyBeanMethods = false
lite模式
使用特性:
lite模式下的 配置类不生成代理 ,原始类型进入容器
lite模式下的 @Bean方法 可以是 private 和 final
单例scope下 不同@Bean方法 引用时 无法做到单例
@Component
public class LiteConfig {
@Bean
User u1() {
return new User("小明", 13);
}
@Bean
User u2() {
User localUser = u1();
System.out.println(localUser.hashCode());
User localUser2 = u1();
System.out.println(localUser2.hashCode());
return new User("小明2", 14);
}
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(LiteConfig.class);
Object liteConfig = ctx.getBean("liteConfig");
System.out.println(liteConfig);
}
}
368342628
1192923170
com.peace.test.configuration.test2.LiteConfig@59e2d8e3
Process finished with exit code 0
使用JavaConfig的方式(显式)
当需要
引入第三方库组件到项目中
时,
无法在第三方的组件上添加 @Component 和 @Autowired 注解
的,因为
第三方库的代码都是 only-read 的
,所以
需要使用显式配置的方式
显式配置
分为
JavaConfig
和
使用xml配置文件
两种形式,在
Spring MVC
框架中会使用到
xml配置
,这种方式
配置比较繁琐
,后逐步被Spring Boot取代,在
Spring Boot中会采用JavaConfig的形式
,
JavaConfig并非业务逻辑代码,所以它与业务代码并没有耦合度
如果系统中
需要引入权限控制模块
,假如我们选用SpringSecurity的情况下,会使用
@Configuration
和
@Bean
`@Bean 需要在配置类中使用`,`类上`需要`加上@Configuration`注解
@Configuration + @Bean
常用于
kafkaConfig,PoolConfig,PropertiesConfigs,RedisConfig,ZkConfig
@Configuration(proxyBeanMethods = false)
public class KafkaConfig {
@Value("${spring.kafka.consumer.concurrency:21}")
private int concurrency;
@Bean
public KafkaListenerContainerFactory<ConcurrentMessageListenerContainer<String, EngineImageProcessService.ObjectInfo>> kafkaListenerContainerFactory(ConsumerFactory<String, EngineImageProcessService.ObjectInfo> consumerFactory) {
ConcurrentKafkaListenerContainerFactory<String, EngineImageProcessService.ObjectInfo> factory = new ConcurrentKafkaListenerContainerFactory<>();
...
return factory;
}
}
@Configuration(proxyBeanMethods = false)
public class ZkConfig {
@Value("${zookeeper.endpoints}")
private String zkEndpoints;
@Bean
@ConfigurationProperties("xxx")
public CuratorFramework zkClient() {
ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3, 5000);
CuratorFramework zkClient = CuratorFrameworkFactory.builder()
...
zkClient.start();
return zkClient;
}
}
@ConfigurationProperties("xxx")
SpringBoot 基础学习 配置绑定
自动配置(隐式)
自动配置
: 让
Spring自动满足bean依赖的一种方法
,在满足依赖的过程中,会
在Spring上下文中寻找匹配的某个bean
需求的其他Bean
隐式装配较显示装配更为便利
,但是
显式装配更加强大
。组件扫描和自动装配组合使用可使显式配置降低到最少
从两个角度来实现自动配置
组件扫描(component):Spring自动发现应用上下文中所创建的bean
自动装配(autowired):Spring自动满足bean之间的依赖
@Component
默认
单例
,如果想
声明成多例
用
@Component
@Scope("prototype")
@Component
注解 +
xxxApplication
类上加
@ComponentScan
告诉Spring,
我是一个bean
,你要来管理我,然后
使用@AutoWired注解去装配Bean
(所谓
装配,就是管理对象直接的协作关系
)
其优点是:
使用范围比较广,所有类都可以进行注解
@Component
的作用就是
把普通的pojo实例化到Spring容器中
,相当于
配置文件中的<bean id="" class=""/>
,所以
@Component
这种方式
适用于程序员自身开发的组件
(有源码)
/**
* 日期转换工具
*/
@Component
public class DateConverter implements Converter<String, Date> {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@Override
public Date convert(String source) {
try {
return sdf.parse(source);
} catch (ParseException e) {
e.printStackTrace();
}
return null;
}
}
@ComponentScan(value = "com.xxx",
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {xxx.class}))
@Component
public class xxxListener extends AbstractListener {
...
}
注意
单例不用每次都new,性能快
在单例的bean中切记声明成员属性(如Map、List集合来缓存数据),是线程不安全的
万一必须要定义一个非静态成员变量时候,则通过注解@Scope("prototype"),将其设置为多例模式
避免同时 @Bean 与 @Component 用在同一个类上
参考
@Bean 与 @Component 用在同一个类上,会怎么样?
Spring 5.0.7.RELEASE ( Spring Boot 2.0.3.RELEASE )
支持
@Configuration + @Bean
与
@Component
同时作用于同一个类
启动时会给 info 级别的日志提示
,同时会
将@Configuration + @Bean
修饰的
BeanDefinition
覆盖掉
@Component 修饰的 BeanDefinition
Spring 1.2 引进
DefaultListableBeanFactory
的时候就有了
private boolean allowBeanDefinitionOverriding = true
; 默认是
允许 BeanDefinition 覆盖
@Compent和@Bean到底区别在哪
在
应用开发的过程中
,如果
想要将第三方库中的组件装配到你的应用中
,在这种情况下,是
没有办法在它的类上添加@Component和@Autowired注解
的,因此就要使用
xml
或者
在@Configuration配置类中通过@Bean进行配置
@Component
来表示
一个通用注释
,用于
说明
一个
类是一个spring容器管理的类
(再通俗易懂一点就是
将要实例化的类丢到Spring容器中去
)
@Component
的
范围比较广
,
所有类都可以进行注解
而
@Configuration
注解一般
注解在类
里面有
@Value注解的成员变量
或
@Bean注解的方法
,
@Bean主要和@Configuration配合使用的