@Scope注解的用法及源码分析
Scope域@Scope("prototype")//多实例,IOC容器启动创建的时候,并不会创建对象放在容器在容器当中,当你需要的时候,需要从容器当中取该对象的时候,就会创建。@Scope("singleton")//单实例 IOC容器启动的时候就会调用方法创建对象,以后每次获取都是从容器当中拿同一个对象(map当中)。@Scope("request")//同一个请求创建一个实例@Scope("
文章目录
示例
Scope域
@Scope("prototype")//多实例,IOC容器启动创建的时候,并不会创建对象放在容器在容器当中,当你需要的时候,每次去从容器当中getBean获取该对象的时候,就会创建。
@Scope("singleton")//单实例(默认) IOC容器启动的时候就会调用方法创建对象,以后每次获取都是从容器当中拿同一个对象。
@Scope("request") //同一个请求创建一个实例
@Scope("session") //同一个session创建一个实例
示例1
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.zzhua</groupId>
<artifactId>demo-scope</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo-scope</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
RequestScope
@Scope("request")
// @Scope(value = "request",proxyMode = ScopedProxyMode.TARGET_CLASS) // 如果指定proxyMode为TARGET_CLASS的话,注入的时候就不用写@Lazy了
@Component("myReqScope")
public class MyReqScope { // 每次请求都会创建这个bean哦,并且也会走bean的生命周期
private int count;
public void incr() {
count++;
}
public int getCount() {
return count;
}
}
SessionScope
@Scope("session")
// @Scope(value = "request",proxyMode = ScopedProxyMode.TARGET_CLASS) // 如果指定proxyMode为TARGET_CLASS的话,注入的时候就不用写@Lazy了
@Component("mySessionScope")
public class MySessionScope { // 每打开一个新的会话都会创建这个bean哦,并且也会走bean的生命周期
private int count;
public void incr() {
count++;
}
public int getCount() {
return count;
}
}
ScopeController
@RestController
public class ScopeController implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Lazy // 不加@Lazy会报错
@Autowired
private MyReqScope myReqScope;
@Lazy // 不加@Lazy会报错
@Autowired
private MySessionScope mySessionScope;
@RequestMapping("testMyReqScope")
public String testMyReqScope() {
// MyReqScope myReqScope = (MyReqScope) applicationContext.getBean("myReqScope");
myReqScope.incr();
System.out.println(myReqScope.getCount());
return myReqScope.toString();
}
@RequestMapping("testMySessionScope")
public String testMySessionScope() {
// MySessionScope mySessionScope = (MySessionScope) applicationContext.getBean("mySessionScope");
mySessionScope.incr();
System.out.println(mySessionScope.getCount());
return mySessionScope.toString();
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
测试
示例2
IPerson
public interface IPerson {
void say();
}
DefaultPerson
public class DefaultPerson implements IPerson {
public DefaultPerson() {
System.out.println("DefaultPerson构造方法执行: " + this);
}
@Override
public void say() {
System.out.println("DefaultPerson#say执行: " + this);
System.out.println("-----------------------------");
}
}
LoginController
@RestController
public class PersonController {
public static final ObjectMapper mapper = new ObjectMapper();
/* 1. 这里注入的person实际上是beanName为person,
beanDefinition为ScopedProxyFactoryBean,
bean为ScopedProxyFactoryBean所创建的代理对象;
2. 而调用这个代理对象的方法时, 最终会调用到SimpleBeanTargetSource的getBean(targetBeanName),其中targetBeanName就是Scoped.person,
并且它的scope是request, 因此每个请求都会由requestScope来根据名字获取,
3. 由于原始定义的bean是由@Bean定义的, 因此会触发这个@Bean方法的执行;
4. 如果它的scope是session, 那么每个会话中的请求才会去触发@Bean方法的执行;
5. 在执行@Bean方法时, 会将方法上的参数当作依赖来解析, 其中的el表达式中所使用的request是在scope为request时, 从RequestScope的resolveContextualObject('request')解析出来的
6. 这其实就给了我们一种方式: 注入同一个bean, 但是在调用时去触发1次getBean(beanName), 并且是带有scope的, 这种方式得益于代理的功劳 */
@Autowired
private IPerson IPerson;
@RequestMapping("/login")
public String login() {
IPerson.say(); // 每次调用,都会让@Bean方法中执行
return "login";
}
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.INTERFACES)
// @Scope(value = "session", proxyMode = ScopedProxyMode.INTERFACES)
public IPerson person(@Value("#{request.parameterMap}") Map<String, String[]> parameters,
@Value("#{request.getAttribute('currentUri')}") String currentUri) throws JsonProcessingException {
System.out.println("开始: @Bean方法调用");
System.out.println("parameters: " + mapper.writeValueAsString(parameters));
System.out.println("currentUri: " + mapper.writeValueAsString(currentUri));
System.out.println("开始: 创建defaultPerson");
DefaultPerson defaultPerson = new DefaultPerson();
System.out.println("结束: 创建defaultPerson: " + defaultPerson);
System.out.println("结束: @Bean方法调用");
return defaultPerson;
}
}
源码分析
前面部分完全复制:
@Scope注解创建代理对象 https://www.cnblogs.com/yangxiaohui227/p/13403082.html
代理创建过程分析
一.源码环境的搭建:
@Component
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_SINGLETON,proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyMath implements Calc{
public Integer add(int num1,int num2){
return num1+num2;
}
}
@Configuration
@ComponentScan("com.yang.xiao.hui.aop")
public class App {
public static void main( String[] args ) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(App.class);
MyMath myMath = (MyMath)ctx.getBean("myMath");
System.out.println(myMath.getClass());
System.out.println(ctx.getBean("scopedTarget.myMath").getClass());
}
}
启动main方法:
二.源码分析,先看Scope注解:
scope注解的proxyMode的属性决定了被该注解标注的类是否会被代理,这是一个枚举,有如下几个值:
本次测试代码使用的是cglib代理,被Scope标注的对象,如果代理模式是jdk或者cglib代理的话,会在spring容器中产生2个bean,一个是代理的bean,一个是原始的bean,原始的bean的beanName被命名为:scopedTarget.xx;如果被Scope标注的对象,代理模式不是jdk或者cglib代理,那:
debug调试:
省略n步:
我们在这个方法里面可以看到spring是如何解析主启动类,扫描到其他的bean的:
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
if (configClass.getMetadata().isAnnotated(Component.class.getName())) { //解析Component注解
// Recursively process any member (nested) classes first
processMemberClasses(configClass, sourceClass);
}
// Process any @PropertySource annotations
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable( //解析Component注解ComponentScan.class
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); //componentScan解析器,对该注解进行解析
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
//..............................省略部分代码
}
这里是在走扫包的逻辑了,我们要关注的加了@Scope注解标注的bean,就是在这里被处理检测到。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);//通过包名,扫描该包名下的所有bean的定义信息
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate); //scope注解解析器,获取Scope注解的属性信息,封装成ScopeMetadata
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);//beanName生成器,这里是myMath
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
// 要处理的加了@Scope注解标注的bean
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); //这里处理scope注解,下面跟进这个
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
从 图示代码可以看到,如果我们加了@Scope注解标注的bean的scopeProxyMode标记的是ScopedProxyMode.NO,那么就不会走下面ScopedProxyCreator.createScopedProxy(…)处理逻辑了,那这个bean该是什么scope,就用什么scope,当需要去get这个bean的时候,就从scope中去拿即可,拿不到的话,该咋样咋样,该报错报错。如果不是ScopedProxyMode.NO,那就看看spring又给我们整个啥新花样。
小知识:(还有一点注意:那我要是写的ScopedProxyMode.DEFAULT呢?这个可以看看AnnotationScopeMetadataResolve这个类了)
class:AnnotationScopeMetadataResolver
public AnnotationScopeMetadataResolver() {
this.defaultProxyMode = ScopedProxyMode.NO; // 默认就是ScopedProxyMode.NO
}
@Override
public ScopeMetadata resolveScopeMetadata(BeanDefinition definition) {
ScopeMetadata metadata = new ScopeMetadata();
if (definition instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annDef = (AnnotatedBeanDefinition) definition;
AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(
annDef.getMetadata(), this.scopeAnnotationType);
if (attributes != null) {
metadata.setScopeName(attributes.getString("value"));
ScopedProxyMode proxyMode = attributes.getEnum("proxyMode");
if (proxyMode == ScopedProxyMode.DEFAULT) {
proxyMode = this.defaultProxyMode;// 如果写的是默认,那就使用默认的NO了
}
metadata.setScopedProxyMode(proxyMode);
}
}
return metadata;
}
好的,我们继续来看看当我们设置的不是ScopedProxyMode.NO的情况的处理
public static BeanDefinitionHolder createScopedProxy(BeanDefinitionHolder definition,
BeanDefinitionRegistry registry, boolean proxyTargetClass) {
String originalBeanName = definition.getBeanName();//原始的beanName: myMath
BeanDefinition targetDefinition = definition.getBeanDefinition(); //原始的bean定义信息
String targetBeanName = getTargetBeanName(originalBeanName); //scopedTarget.myMath
// Create a scoped proxy definition for the original bean name,
// "hiding" the target bean in an internal target definition.
RootBeanDefinition proxyDefinition = new RootBeanDefinition(ScopedProxyFactoryBean.class); //创建一个代理对象
proxyDefinition.setDecoratedDefinition(new BeanDefinitionHolder(targetDefinition, targetBeanName)); //将原始的bean定义信息作为被装饰的bean定义信息
proxyDefinition.setOriginatingBeanDefinition(targetDefinition);//设置原始的bean定义信息
proxyDefinition.setSource(definition.getSource());
proxyDefinition.setRole(targetDefinition.getRole());
proxyDefinition.getPropertyValues().add("targetBeanName", targetBeanName);
if (proxyTargetClass) {
targetDefinition.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// ScopedProxyFactoryBean's "proxyTargetClass" default is TRUE, so we don't need to set it explicitly here.
}
else {
proxyDefinition.getPropertyValues().add("proxyTargetClass", Boolean.FALSE);
}
// Copy autowire settings from original bean definition.
proxyDefinition.setAutowireCandidate(targetDefinition.isAutowireCandidate());
proxyDefinition.setPrimary(targetDefinition.isPrimary());
if (targetDefinition instanceof AbstractBeanDefinition) {
proxyDefinition.copyQualifiersFrom((AbstractBeanDefinition) targetDefinition);
}
// The target bean should be ignored in favor of the scoped proxy.
targetDefinition.setAutowireCandidate(false);
targetDefinition.setPrimary(false);
// Register the target bean as separate bean in the factory.
registry.registerBeanDefinition(targetBeanName, targetDefinition);//这里将原始的bean定义信息注册到了spring容器,而bean的名称是scopedTarget.myMath
// Return the scoped proxy definition as primary bean definition
// (potentially an inner bean).
return new BeanDefinitionHolder(proxyDefinition, originalBeanName, definition.getAliases()); //这里将代理bean定义信息返回,bean的名称是原始的beanName,myMath,该beanHodler返回去后,会被注册到spring
}
总结:
一个被Scope注解标注的类,如果scope的proxyMode不是no 或者defualt,那么会在spring创建2个bean,一个是代理bean,类型为ScopedProxyFactoryBean.class,一个是原始的bean:
这里我们的MyMath类,生成了2个beanDefinition,一个是代理的beanDefinition,beanName为myMath,一个是原始的beanDefinition,beanName为scopedTarget.myMath;
三.ScopedProxyFactoryBean
下面我们要分析ScopedProxyFactoryBean的创建过程了,我们知道XXFactoryBean会有一个getObject()方法返回XX代理对象:先看ScopedProxyFactoryBean继承体系
通过继承图,我们知道,ScopedProxyFactoryBean实现了BeanFactoryAware接口,因此在ScopedProxyFactoryBean的创建过程中,会回调setBeanFactory(BeanFactory beanFactory),所以我们debug在该方法:
我们详细看看该方法:
@Override
public void setBeanFactory(BeanFactory beanFactory) {
if (!(beanFactory instanceof ConfigurableBeanFactory)) {
throw new IllegalStateException("Not running in a ConfigurableBeanFactory: " + beanFactory);
}
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
this.scopedTargetSource.setBeanFactory(beanFactory);
ProxyFactory pf = new ProxyFactory(); //创建代理工厂
pf.copyFrom(this);
pf.setTargetSource(this.scopedTargetSource);
Assert.notNull(this.targetBeanName, "Property 'targetBeanName' is required");
Class<?> beanType = beanFactory.getType(this.targetBeanName);//获取被代理类
if (beanType == null) {
throw new IllegalStateException("Cannot create scoped proxy for bean '" + this.targetBeanName +
"': Target type could not be determined at the time of proxy creation.");
}
if (!isProxyTargetClass() || beanType.isInterface() || Modifier.isPrivate(beanType.getModifiers())) {
pf.setInterfaces(ClassUtils.getAllInterfacesForClass(beanType, cbf.getBeanClassLoader())); //获取被代理类的所有实现的接口
}
// Add an introduction that implements only the methods on ScopedObject.
ScopedObject scopedObject = new DefaultScopedObject(cbf, this.scopedTargetSource.getTargetBeanName());
pf.addAdvice(new DelegatingIntroductionInterceptor(scopedObject));//这里添加了一个增强器,在执行目标方法时,会拦截
// Add the AopInfrastructureBean marker to indicate that the scoped proxy
// itself is not subject to auto-proxying! Only its target bean is.
pf.addInterface(AopInfrastructureBean.class);
this.proxy = pf.getProxy(cbf.getBeanClassLoader());//创建代理对象
}
创建代理对象过程,跟之前aop源码分析一和源码分析二的时侯分析的一样了,这里不重复了。
代理执行过程分析
上面是分析了spring容器对@Scope注解的BeanDefinition的处理,就是说如果加了@Scope并且指定了代理方式,那么spring就会spring容器中产生2个bean,
一个是原始的bean,原始的bean的beanName被命名为:scopedTarget.xx。
一个是代理的bean(准确来说是由ScopedProxyFactoryBean这个FactoryBean在回调setBeanFactory方法时,创建的代理对象,并通过FactoryBean接口的getObject方法将此代理对象返回)
我们继续留意一下ScopedProxyFactoryBean这个类里面,
setTargetBeanName的调用触发是ScopedProxyUtils#createScopedProxy,使用了proxyDefinition.getPropertyValues().add(“targetBeanName”, targetBeanName);,所以下面图示方法会触发
我们留一下,这个关键的targetSource的设置,因为这个aop挂钩,所以必须得知道AOP它的一个整体的逻辑。
那么,上面的getTarget方法为什么会被触发,这个就需要对Aop了解了。解释如下
问题汇总
而这个intercept方法为什么会被执行呢?
是因为调用了代理对象的方法!
那为什么调用代理对象就i到了intercept方法呢?
这个就跟代理类生成和代理对象创建相关了,具体参考框架学习汇总篇中关于Aop相关的。
那为什么会调用到代理对象的方法呢?
因为注入的是代理对象。
那我明明要注入的是目标对象呀,spring咋就给我注入代理对象了呢?
前面不是一直在说,@Scope注解的处理逻辑嘛,原始的bean换了个名字ScopedTarget.xxx,原来的xxx是代理对象了,那你要@Autowired的话,注入的不就是代理对象了嘛!
那这跟scope又有什么关系呢?
你一旦触发getBean,就会走下图逻辑,这不就跟spring定义的scope有关了嘛
那下面的代码为什么不加上注释就报错了呢?而加上才正常呢?
@Scope(value = "session", /*proxyMode = ScopedProxyMode.TARGET_CLASS*/) // 加上注释,启动会报错
@Component("mySessionScope")
public class MySessionScope {
private int count;
public void incr() {
count++;
}
public int getCount() {
System.out.println("getCount->" + this);
return count;
}
}
@RestController
public class ScopeController{
@Autowired
private MySessionScope mySessionScope;
@RequestMapping("testMySessionScope")
public String testMySessionScope() {
// MySessionScope mySessionScope = (MyReqScope) applicationContext.getBean("mySessionScope");
mySessionScope.incr();
System.out.println(mySessionScope.getCount());
return mySessionScope.toString();
}
}
因为,如果注释了的话,那在启动阶段ScopeController自动注入就会触发对MySessionScope的getBean,就会走SessionScope#get逻辑,而这个时候,当前线程是不存在请求相关的,也就是RequestContextHolder中没有绑定请求相关的对象,而SessionScope里面一验证,发现没有,则就报错了。
而如果取消注释了,那根据前面说的,spring就会添加2个bean,其中的由ScopedProxyFactoryBean#getObject创建的代理对象返回的那个bean,它在容器中的名字就是目标对象的名字,就被注入到ScopeController了,这样完成了注入,而在运行阶段,就通过代理机制在拦截器的拦截逻辑里从beanFactory中获取到真正的bean,来完成调用。这就是他们的区别
那上面那个例子,你要这么说的话,那为什么我加上注释,但是我在@Autowired上面加上@Lazy不一样也可以正常运行么?
像这样
@Scope(value = "session", /*proxyMode = ScopedProxyMode.TARGET_CLASS*/)
@Component("mySessionScope")
public class MySessionScope {
private int count;
public void incr() {
count++;
}
public int getCount() {
System.out.println("getCount->" + this);
return count;
}
}
@RestController
public class ScopeController{
@Lazy // 加上@Lazy哦
@Autowired
private MySessionScope mySessionScope;
@RequestMapping("testMySessionScope")
public String testMySessionScope() {
// MySessionScope mySessionScope = (MyReqScope) applicationContext.getBean("mySessionScope");
mySessionScope.incr();
System.out.println(mySessionScope.getCount());
return mySessionScope.toString();
}
}
额,这样是可以。是因为在启动阶段,在预实例化ScopeController的时候,就由@Autowired注解要自动注入MySessionScope,对吧。但是你加了个@Lazy,那么依赖描述符就会记录这个注解,那么在解析这个MySessionScope依赖的时候,就不是要实例化这个bean了,而是创建代理,具体看下图:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)