单例模式实战应用

  • Post author:
  • Post category:其他




理论



什么是单例模式

保证

整个系统中一个类只有一个对象的实例

,实现这种功能的方式就叫单例模式

常用的

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中的ServlertContextSerletContextConfig等、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 默认单例


验证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注解的方法,可以当成普通方法,可以使用privatefinalstatic修饰符。
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方法 不能是privatefinal
单例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配合使用的



版权声明:本文为qq_40813329原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。