@Scope注解的用法及源码分析

@Scope注解 - 我的processon文件

示例

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了,而是创建代理,具体看下图:
在这里插入图片描述

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值