概述
其实在开发的过程中大家基本上很少使用这个这个注解,我看了下我公司的项目中,完全没用到。但是没用到不代表,没有用,我们今天就来学习这个注解,了解它的基本作用和使用场景。
注解介绍
@Scope, 英文名是范围的意思,用来表示Spring中Bean的作用域范围, 该注解只能写在类上或者方法上。
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scope { @AliasFor("scopeName") String value() default ""; @AliasFor("value") String scopeName() default ""; ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; }
注解有3个属性,value和scopeName一样,用来表示注解的作用于范围。proxyMode用来为spring bean设置代理。
作用域(value或者scopeName)属性范围
- singleton: 默认值,单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例。
- prototype: 原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。
- request: 对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效。
- session: 对于每次HTTP Session,使用session定义的Bean都将产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效。
默认的作用域范围为singleton。
proxyMethod属性:
- DEFAULT:proxyMode的默认值,一般情况下等同于NO,即不需要动态代理。
- NO:不需要动态代理,即返回的是Bean的实例对象。
- INTERFACES:代理的对象是一个接口,即@Scope的作用对象是接口,这种情况是基于jdk实现的动态代理。
- TARGET_CLASS:代理的对象是一个类,即@Scope的作用对象是一个类,上面例子中的ClassB就可以用这种代理,是以生成目标类扩展的方式创建代理,基于CGLib实现动态代理。
后面通过实例来讲解下我们为什么要有这个属性。
使用注解
前面讲了该注解作用在类上或者方法上,但是其实它前提必须是一个Bean,所以存在下面两种情况:
作用在类上
搭配@Component、@Service注解
@Component @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) public class Student { private String name; private Integer age; public Student() { System.out.println("实例化学生对象~~~"); } }
作用在方法上
搭配@Bean注解使用
@Configuration public class ScopeConfig { @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public Student getStudent() { return new Student(); } }
通常我们不配置Scope情况下的Bean的作用域都是单例模式singleton,不进行任何代理。
实例演示
我们前面讲解了通过Bean如何控制我们Bean的作用域范围,那我们通过例子演示验证下。
原型模式prototype
原型模式prototype,也叫多例模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例。
public class PrototypeBean { PrototypeBean() { System.out.println("实例化 PrototypeBean"); } public void init() { System.out.println("初始化 PrototypeBean"); } public void destroy() { System.out.println("销毁 PrototypeBean"); } }
@Bean(initMethod = "init", destroyMethod = "destroy") @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public PrototypeBean prototypeBean() { return new PrototypeBean(); }
验证代码:
PrototypeBean prototypeBean1 = context.getBean(PrototypeBean.class); System.out.println(prototypeBean1); PrototypeBean prototypeBean2 = context.getBean(PrototypeBean.class); System.out.println(prototypeBean2); System.out.println(prototypeBean1 == prototypeBean2); context.close();
执行结果:
实例化 PrototypeBean 初始化 PrototypeBean com.alvinlkk.scope.PrototypeBean@26a94fa5 实例化 PrototypeBean 初始化 PrototypeBean com.alvinlkk.scope.PrototypeBean@464a4442 false
小结:
- prototype多例模式,每次在调用getBean() 获取实例时,都会重新实例化,初始化。
- prototype多例模式,它的Bean实例对象则不受IOC容器的管理,最终由GC来销毁。
单例模式singleton
默认情况下,Spring Bean都是单例模式,在容器启动的时候,Bean就会创建。
public class SingletonBean { SingletonBean() { System.out.println("实例化 SingletonBean"); } public void init() { System.out.println("初始化 SingletonBean"); } public void destroy() { System.out.println("销毁 SingletonBean"); } }
@Bean(initMethod = "init", destroyMethod = "destroy") @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) public SingletonBean singletonBean() { return new SingletonBean(); }
验证代码:
SingletonBean singletonBean1 = context.getBean(SingletonBean.class); System.out.println(singletonBean1); SingletonBean singletonBean2 = context.getBean(SingletonBean.class); System.out.println(singletonBean1 == singletonBean1);
执行结果:
实例化 SingletonBean 初始化 SingletonBean 执行 SingletonBean 测试: com.alvinlkk.scope.SingletonBean@e8fadb0 com.alvinlkk.scope.SingletonBean@e8fadb0 true 销毁 SingletonBean
小结:
- singleton单实例模式下,多次getBean()取到的对象是一样的。
- 针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法。
单实例Bean注入多实例Bean
那么如果单实例中注入了多实例的bean,会是什么样的情况呢?
定义多实例Bean
@Component @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PrototypeBean1 { /** * 打印自己这个对象 */ public void printCurrentObj() { System.out.println(this); } }
定义单实例Bean:
@Component @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON) public class SingletonBean1 { @Autowired private PrototypeBean1 prototypeBean; public void callProtypeBeanPrint() { prototypeBean.printCurrentObj(); } }
验证:
SingletonBean1 singletonBean1 = context.getBean(SingletonBean1.class); for (int i = 0; i < 5; i++) { singletonBean1.callProtypeBeanPrint(); }
执行结果:
在单实例对象Bean中注入多实例对象,最终都是同一个对象,因为在Bean创建的那个时刻被注入了,那有什么棒法变成真正的多实例吗?这时候代理proxyMethod属性派上用场了。
重新运行测试,查看结果如下:
发现每个对象都不一样了,本质上是通过代理对象调用方法printCurrentObj时,会重新从容器getBean,获取真实的Bean,这时候会重新创建对象,具体可以查看。blog.csdn.net/geng2568/ar…
总结
几乎90%以上的业务使用 singleton单例就可以,所以 Spring 默认的类型也是singleton,singleton虽然保证了全局是一个实例,对性能有所提高,但是如果实例中有非静态变量时,会导致线程安全问题,共享资源的竞争。
当设置为prototype多例时:每次连接请求,都会生成一个bean实例,也会导致一个问题,当请求数越多,性能会降低,因为创建的实例,导致GC频繁,GC时长增加。