单例模式实战应用

36 篇文章 1 订阅

理论

什么是单例模式

保证整个系统中一个类只有一个对象的实例,实现这种功能的方式就叫单例模式

常用的 servicedao 层的对象通常都是单例的,而多例则指每个请求用一个新的对象来处理,比如 action

spring 中的 beanspring 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配合使用的

  • 12
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值