作者简介:zhz小白
公众号:小白的Java进阶之路
专业技能:
1、Java基础,并精通多线程的开发,熟悉JVM原理
2、熟悉Java基础,并精通多线程的开发,熟悉JVM原理,具备⼀定的线上调优经验
3、熟悉MySQL数据库调优,索引原理等,⽇志原理等,并且有出过⼀篇专栏
4、了解计算机⽹络,对TCP协议,滑动窗⼝原理等有⼀定了解
5、熟悉Spring,Spring MVC,Mybatis,阅读过部分Spring源码
6、熟悉SpringCloud Alibaba体系,阅读过Nacos,Sentinel,Seata,Dubbo,Feign,Gateway核⼼源码与设计,⼆次开发能⼒
7、熟悉消息队列(Kafka,RocketMQ)的原理与设计
8、熟悉分库分表ShardingSphere,具有真实⽣产的数据迁移经验
9、熟悉分布式缓存中间件Redis,对其的核⼼数据结构,部署架构,⾼并发问题解决⽅案有⼀定的积累
10、熟悉常⽤设计模式,并运⽤于实践⼯作中
11、了解ElasticSearch,对其核⼼的原理有⼀定的了解
12、了解K8s,Jekins,GitLab
13、了解VUE,GO
14、⽬前有正在利⽤闲暇时间做互游游戏,开发、运维、运营、推销等
本人著作git项目:https://gitee.com/zhouzhz/star-jersey-platform,有兴趣的可以私聊博主一起编写,或者给颗star
领域:对支付(FMS,FUND,PAY),订单(OMS),出行行业等有相关的开发领域
如果此文还不错的话,还请关注、点赞、收藏三连支持一下博主~
@Scope注解能够设置组件的作用域,我们先来看看@Scope注解类的源码,如下所示。
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.core.annotation.AliasFor;
/**
* When used as a type-level annotation in conjunction with
* {@link org.springframework.stereotype.Component @Component},
* {@code @Scope} indicates the name of a scope to use for instances of
* the annotated type.
*
* When used as a method-level annotation in conjunction with
* {@link Bean @Bean}, {@code @Scope} indicates the name of a scope to use
* for the instance returned from the method.
*
*
NOTE: {@code @Scope} annotations are only introspected on the
* concrete bean class (for annotated components) or the factory method
* (for {@code @Bean} methods). In contrast to XML bean definitions,
* there is no notion of bean definition inheritance, and inheritance
* hierarchies at the class level are irrelevant for metadata purposes.
*
*
In this context, scope means the lifecycle of an instance,
* such as {@code singleton}, {@code prototype}, and so forth. Scopes
* provided out of the box in Spring may be referred to using the
* {@code SCOPE_*} constants available in the {@link ConfigurableBeanFactory}
* and {@code WebApplicationContext} interfaces.
*
*
To register additional custom scopes, see
* {@link org.springframework.beans.factory.config.CustomScopeConfigurer
* CustomScopeConfigurer}.
*
* @author Mark Fisher
* @author Chris Beams
* @author Sam Brannen
* @since 2.5
* @see org.springframework.stereotype.Component
* @see org.springframework.context.annotation.Bean
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
/**
* Alias for {@link #scopeName}.
* @see #scopeName
*/
@AliasFor("scopeName")
String value() default "";
/**
* Specifies the name of the scope to use for the annotated component/bean.
* Defaults to an empty string ({@code ""}) which implies
* {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
* @since 4.2
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
* @see #value
*/
@AliasFor("value")
String scopeName() default "";
/**
* Specifies whether a component should be configured as a scoped proxy
* and if so, whether the proxy should be interface-based or subclass-based.
* Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
* that no scoped proxy should be created unless a different default
* has been configured at the component-scan instruction level.
*
Analogous to {@code } support in Spring XML.
* @see ScopedProxyMode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
从@Scope注解类的源码中可以看出,在@Scope注解中可以设置如下值:
我们可以看到@Scope注解的注释中有一句话,表明它是有两个实现类的,所以我们直接上实现类中看。
我们可以发现SCOPE_SINGLETON就是singleton,而SCOPE_PROTOTYPE就是prototype。
我们可以发现SCOPE_REQUEST的值就是request,SCOPE_SESSION的值就是session。
request和session作用域是需要Web环境来支持的,这两个值基本上使用不到。当我们使用Web容器来运行Spring应用时,如果需要将组件的实例对象的作用域设置为request和session,那么我们通常会使用
request.setAttribute(“key”, object);
session.setAttribute(“key”, object);
这两种形式来将对象实例设置到request和session中,而不会使用@Scope注解来进行设置。
我们在MainConfig中添加一个Bean对象,放入spring容器中,如下:
package com.zhz.config;
import com.zhz.bean.Person;
import com.zhz.filter.MyTypeFilter;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
/**
* @author zhouhengzhe
* @description: todo
* @date 2022/11/4 10:27
* @since v1
*/
@Configuration
public class MainConfig {
/**
* @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
*/
@Bean(name = "person")
public Person person1() {
return new Person("zhz", 20);
}
}
然后我们在IOCTest中,执行如下代码:
package com.zhz.test;
import com.zhz.bean.Person;
import com.zhz.config.MainConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author zhouhengzhe
* @description: todo
* @date 2022/11/4 10:58
* @since v1
*/
public class IOCTest {
@SuppressWarnings("resource")
@Test
public void test() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = applicationContext.getBean(Person.class);
Person person1 = applicationContext.getBean(Person.class);
System.out.println(person == person1);
}
}
我们可以发现他们是同一个对象,因此可知Spring容器默认是单例Bean,所以只要启动之后,Spring就会把实例对象加载到Bean当中,然后接下来的每一次去获取实例对象,都会是同一个引用地址返回。
结论:对象在Spring容器中默认是单实例的,Spring容器在启动时就会将实例对象加载到Spring容器中,之后,每次从Spring容器中获取实例对象,都是直接将对象返回,而不必再创建新的实例对象了。
我们在MainConfig中添加一个Bean对象,放入spring容器中,并且添加**@Scope(“prototype”)到Bean对象中,**代码如下:
package com.zhz.config;
import com.zhz.bean.Person;
import com.zhz.filter.MyTypeFilter;
import org.springframework.context.annotation.*;
/**
* @author zhouhengzhe
* @description: todo
* @date 2022/11/4 10:27
* @since v1
*/
@Configuration
public class MainConfig {
/**
* @Bean注解是给IOC容器中注册一个bean,类型自然就是返回值的类型,id默认是用方法名作为id
*/
@Scope("prototype")
@Bean(name = "person")
public Person person1() {
return new Person("zhz", 20);
}
}
然后我们运行测试类IOCTest,代码如下:
package com.zhz.test;
import com.zhz.bean.Person;
import com.zhz.config.MainConfig;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author zhouhengzhe
* @description: todo
* @date 2022/11/4 10:58
* @since v1
*/
public class IOCTest {
@SuppressWarnings("resource")
@Test
public void test() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = applicationContext.getBean(Person.class);
Person person1 = applicationContext.getBean(Person.class);
System.out.println(person == person1);
}
}
总结:从以上输出结果中也可以看出,此时,输出的person对象和person2对象已经不是同一个对象了。说明他是一个原型实例
我们思考一下单例bean是什么时候创建的呢?
我们来做个实现,首先我们再MainConfig中创建一个对象,如下:
@Bean(name = "person")
public Person person1() {
System.out.println("单例bean创建");
return new Person("zhz", 20);
}
然后我们写一个测试用例,如下:
@Test
public void test() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
}
运行结果:
因此我们可以知道Spring容器在创建的时候,就将@Scope注解标注为singleton的组件进行了实例化,并加载到了Spring容器中,以后每次从容器中获取组件实例对象时,都是直接返回相应的对象,而不必再创建新的对象了。
我们也根据上面的测试案例去测一遍多例Bean是怎么创建对象的,测试代码如下:
@Scope("prototype")
@Bean(name = "person")
public Person person1() {
System.out.println("单例bean创建");
return new Person("zhz", 20);
}
测试用例1:
@Test
public void test() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
}
测试用例2:
@Test
public void test() {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = applicationContext.getBean(Person.class);
Person person1 = applicationContext.getBean(Person.class);
System.out.println(person == person1);
}
由测试用例1和测试用例2我们可以发现当对象是多实例时,每次从Spring容器中获取对象时,都会创建新的实例对象,并且每个实例对象都不相等。
单实例bean是整个应用所共享的,所以需要考虑到线程安全问题,之前在玩SpringMVC的时候,SpringMVC中的Controller默认是单例的,有些开发者在Controller中创建了一些变量,那么这些变量实际上就变成共享的了,Controller又可能会被很多线程同时访问,这些线程并发去修改Controller中的共享变量,此时很有可能会出现数据错乱的问题,所以使用的时候需要特别注意。
多实例bean每次获取的时候都会重新创建,如果这个bean比较复杂,创建时间比较长,那么就会影响系统的性能,因此这个地方需要注意点。
如果Spring内置的几种scope都无法满足我们的需求时,我们可以自定义bean的作用域。
public interface Scope {
/**
* 返回当前作用域中name对应的bean对象
* @param name 需要检索的bean对象的名称
* @param objectFactory 如果name对应的bean对象在当前作用域中没有找到,那么可以调用这个objectFactory来创建这个对象
*/
Object get(String name, ObjectFactory<?> objectFactory);
/**
* 将name对应的bean对象从当前作用域中移除
*/
@Nullable
Object remove(String name);
/**
* 用于注册销毁回调,若想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
*/
void registerDestructionCallback(String name, Runnable callback);
/**
* 用于解析相应的上下文数据,比如request作用域将返回request中的属性
*/
@Nullable
Object resolveContextualObject(String key);
/**
* 作用域的会话标识,比如session作用域的会话标识是sessionId
*/
@Nullable
String getConversationId();
}
需要调用org.springframework.beans.factory.config.ConfigurableBeanFactory#registerScope这个方法,咱们看一下这个方法的声明。
在定义bean的时候,指定bean的scope属性为自定义的作用域名称。
我们在com.zhz.scope包下新建一个ThreadScope类。
package com.zhz.scope;
import com.alibaba.ttl.TransmittableThreadLocal;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author zhouhengzhe
* @description: 自定义本地线程级别的bean作用域,不同的线程中的bean是不同的实例,同一个线程中同名的bean是同一个实例
* @date 2022/11/5 23:46
* @since v1
*/
public class ThreadScope implements Scope {
public static final String THREAD_SCOPE = "thread";
private ThreadLocal<Map<String, Object>> beanMap = new TransmittableThreadLocal<>();
/**
* 返回当前作用域中name对应的bean对象
*
* @param name:需要检索的bean对象的名称
* @param objectFactory:如果name对应的bean对象在当前作用域中没有找到,那么可以调用这个objectFactory来创建这个bean对象
*/
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> map = beanMap.get();
Object bean=null;
if (Objects.isNull(map)) {
map = new HashMap<>(16);
bean = objectFactory.getObject();
map.put(name, bean);
beanMap.set(map);
}
bean = map.get(name);
return bean;
}
/**
* 将name对应的bean对象从当前作用域中移除
*/
@Override
public Object remove(String name) {
return this.beanMap.get().remove(name);
}
/**
* 用于注册销毁回调,若想要销毁相应的对象,则由Spring容器注册相应的销毁回调,而由自定义作用域选择是不是要销毁相应的对象
* bean作用域范围结束的时候调用的方法,用于bean的清理
*/
@Override
public void registerDestructionCallback(String name, Runnable callback) {
System.out.println("name");
}
/**
* 用于解析相应的上下文数据,比如request作用域将返回request中的属性
*/
@Override
public Object resolveContextualObject(String key) {
return null;
}
/**
* 作用域的会话标识,比如session作用域的会话标识是sessionId
*/
@Override
public String getConversationId() {
return Thread.currentThread().getName();
}
}
我们在com.zhz.config包下创建一个配置类,例如MainConfig,并使用@Scope(“thread”)注解标注Person对象的作用域为Thread范围:
@Scope("thread")
@Bean(name = "person")
public Person person1() {
System.out.println("单例bean创建");
return new Person("zhz", 20);
}
测试类:
@Test
public void test1() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class);
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
// 向容器中注册自定义的Scope
beanFactory.registerScope(ThreadScope.THREAD_SCOPE, new ThreadScope());
for (int i = 0; i < 2; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + ","+applicationContext.getBean("person"));
System.out.println(Thread.currentThread().getName() + ","+applicationContext.getBean("person"));
}).start();
}
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}
}
由上可知:bean在同样的线程中获取到的是同一个bean的实例,不同的线程中bean的实例是不同的。