前言

上一章学习了【依赖来源】,本章主要讨论SpringBean的作用域,我们这里讨论的Bean的作用域,很大程度都是默认只讨论依赖来源为【Spring BeanDefinition】的作用域,因为在我们的业务开发中,我们都是Spring框架的使用者,我们自定义的bean几乎全部都是属于【Spring BeanDefinition】的。后续文章以这个为默认前提。

作用域概览

来源 说明
singleton 默认的spring bean作用域,一个BeanFactory有且仅有一个实例,重要
prototype 原型作用域,每一次的依赖查找和依赖注入都会生成新的bean对象,重要
request 将SpringBean存储在ServletRequest上下文中,不重要
session 将SpringBean存储在HttpSession上下文中,不重要
application 将SpringBean存储在ServletContext上下文中,不重要

由于目前的开发模式基本都是前后端分离,以前我们写JSP的时候需要从后端的response对象中获取部分数据进行展示,现在这些模板技术[JSP,Freemarker,Velocity等等]已经边缘化,这里不会重点讨论后三者作用域。

Singleton作用域

首先我们看一下Spring官方文档的描述:

Only one shared instance of a singleton bean is managed, and all requests for beans with an ID or IDs that match that bean definition result in that one specific bean instance being returned by the Spring container.

To put it another way, when you define a bean definition and it is scoped as a singleton, the Spring IoC container creates exactly one instance of the object defined by that bean definition. This single instance is stored in a cache of such singleton beans, and all subsequent requests and references for that named bean return the cached object. The following image shows how the singleton scope works:



Spring’s concept of a singleton bean differs from the singleton pattern as defined in the Gang of Four (GoF) patterns book. The GoF singleton hard-codes the scope of an object such that one and only one instance of a particular class is created per ClassLoader. The scope of the Spring singleton is best described as being per-container and per-bean. This means that, if you define one bean for a particular class in a single Spring container, the Spring container creates one and only one instance of the class defined by that bean definition. The singleton scope is the default scope in Spring. To define a bean as a singleton in XML, you can define a bean as shown in the following example:

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>

核心点已经高亮出来,对Spring来说,Singleton是指在一个容器中,一般是BeanFactory,只存在一个特定ID的Bean。

singleton作用域也是默认的作用域。

Prototype作用域

依然先看一下Spring官方文档的描述:

The non-singleton prototype scope of bean deployment results in the creation of a new bean instance every time a request for that specific bean is made. That is, the bean is injected into another bean or you request it through a getBean() method call on the container. As a rule, you should use the prototype scope for all stateful beans and the singleton scope for stateless beans.

我们应该让所有的有状态bean是prototype scope,让所有的无状态bean是singleton scope。

The following diagram illustrates the Spring prototype scope:



(A data access object (DAO) is not typically configured as a prototype, because a typical DAO does not hold any conversational state. It was easier for us to reuse the core of the singleton diagram.)

DAO一般不应该被设置为prototype作用域,因为常规的DAO不应该包含任何会话状态。所以应该配置为singleton作用域。它这里只是举例说明。

The following example defines a bean as a prototype in XML:

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>

In contrast to the other scopes, Spring does not manage the complete lifecycle of a prototype bean. The container instantiates, configures, and otherwise assembles a prototype object and hands it to the client, with no further record of that prototype instance. Thus, although initialization lifecycle callback methods are called on all objects regardless of scope, in the case of prototypes, configured destruction lifecycle callbacks are not called. The client code must clean up prototype-scoped objects and release expensive resources that the prototype beans hold. To get the Spring container to release resources held by prototype-scoped beans, try using a custom bean post-processor, which holds a reference to beans that need to be cleaned up.

与其他作用域不同,Spring并不管理prototype bean的整个生命周期。容器实例化、配置和以其他方式组装prototype对象并将其交给客户端,而不再记录该prototype实例。

In some respects, the Spring container’s role in regard to a prototype-scoped bean is a replacement for the Java new operator. All lifecycle management past that point must be handled by the client. (For details on the lifecycle of a bean in the Spring container, see Lifecycle Callbacks.)

在某些方面,Spring的prototype作用域是Java里new这种行为的一种替代,这种作用域的生命周期都被客户端控制。生命周期管理请参考Lifecycle Callbacks章节。

点击查看[示例代码]
public class BeanScopeDemo implements DisposableBean {

    @Bean
// 默认 scope 就是 "singleton"
public static User singletonUser() {
return createUser();
} @Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public static User prototypeUser() {
return createUser();
} private static User createUser() {
User user = new User();
user.setId(System.nanoTime());
return user;
} @Autowired
@Qualifier("singletonUser")
private User singletonUser; @Autowired
@Qualifier("singletonUser")
private User singletonUser1; @Autowired
@Qualifier("prototypeUser")
private User prototypeUser; @Autowired
@Qualifier("prototypeUser")
private User prototypeUser1; @Autowired
@Qualifier("prototypeUser")
private User prototypeUser2; @Autowired
private Map<String, User> users; @Autowired
private ConfigurableListableBeanFactory beanFactory; // Resolvable Dependency public static void main(String[] args) { // 创建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 注册 Configuration Class(配置类) -> Spring Bean
applicationContext.register(BeanScopeDemo.class); applicationContext.addBeanFactoryPostProcessor(beanFactory -> {
beanFactory.addBeanPostProcessor(new BeanPostProcessor() { @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.printf("%s Bean 名称:%s 在初始化后回调...%n", bean.getClass().getName(), beanName);
return bean;
}
});
}); // 启动 Spring 应用上下文
applicationContext.refresh(); scopedBeansByLookup(applicationContext); scopedBeansByInjection(applicationContext); // 显示地关闭 Spring 应用上下文
applicationContext.close();
} private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) { for (int i = 0; i < 3; i++) {
// singletonUser 是共享 Bean 对象
User singletonUser = applicationContext.getBean("singletonUser", User.class);
System.out.println("singletonUser = " + singletonUser);
// prototypeUser 是每次依赖查找均生成了新的 Bean 对象
User prototypeUser = applicationContext.getBean("prototypeUser", User.class);
System.out.println("prototypeUser = " + prototypeUser);
}
} private static void scopedBeansByInjection(AnnotationConfigApplicationContext applicationContext) {
BeanScopeDemo beanScopeDemo = applicationContext.getBean(BeanScopeDemo.class); System.out.println("beanScopeDemo.singletonUser = " + beanScopeDemo.singletonUser);
System.out.println("beanScopeDemo.singletonUser1 = " + beanScopeDemo.singletonUser1); System.out.println("beanScopeDemo.prototypeUser = " + beanScopeDemo.prototypeUser);
System.out.println("beanScopeDemo.prototypeUser1 = " + beanScopeDemo.prototypeUser1);
System.out.println("beanScopeDemo.prototypeUser2 = " + beanScopeDemo.prototypeUser2); System.out.println("beanScopeDemo.users = " + beanScopeDemo.users);
} @Override
public void destroy() throws Exception { System.out.println("当前 BeanScopeDemo Bean 正在销毁中..."); this.prototypeUser.destroy();
this.prototypeUser1.destroy();
this.prototypeUser1.destroy();
this.prototypeUser2.destroy();
// 获取 BeanDefinition
for (Map.Entry<String, User> entry : this.users.entrySet()) {
String beanName = entry.getKey();
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (beanDefinition.isPrototype()) { // 如果当前 Bean 是 prototype scope
User user = entry.getValue();
user.destroy();
}
} System.out.println("当前 BeanScopeDemo Bean 销毁完成");
}
}
结论一:
Singleton Bean 无论依赖查找还是依赖注入,均为同一个对象
Prototype Bean 无论依赖查找还是依赖注入,均为新生成的对象 结论二:
如果依赖注入集合类型的对象,Singleton Bean 和 Prototype Bean 均会存在一个于你指定的集合中,
并且该集合里的Prototype Bean和其他地方依赖注入的Prototype Bean是不一样的对象。相当于又生成了一个。 结论三:
无论是 Singleton 还是 Prototype Bean 均会执行初始化方法回调
不过仅 Singleton Bean 会执行销毁方法回调,所以prototype bean的销毁需要客户端自己控制。

未深入研究的作用域

1.Request作用域:作用域为同一个 Http Request。

2.Session作用域:作用域为同一个 Http Session。

3.Application作用域:作用域为同一个WEB容器,可以看做Web应用中的单例模式。

4.WebSocket作用域:作用域为同一个WebSocket应用。

前后端分离后,不重要,暂不记录。

自定义Bean作用域

Spring官方文档有介绍自定义作用域,下例创建了一个作用域为当前线程的作用域:如果在不同的线程中,调用同一个spring容器的依赖查找或者依赖注入某个bean,每个线程都会分别创建一个该bean的示例。

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import org.springframework.core.NamedThreadLocal;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable; import java.util.HashMap;
import java.util.Map; /**
* ThreadLocal 级别 Scope
*/
public class ThreadLocalScope implements Scope { public static final String SCOPE_NAME = "thread-local"; private final NamedThreadLocal<Map<String, Object>> threadLocal = new NamedThreadLocal("thread-local-scope") { public Map<String, Object> initialValue() {
return new HashMap<>();
}
}; @Override
public Object get(String name, ObjectFactory<?> objectFactory) { // 非空
Map<String, Object> context = getContext(); Object object = context.get(name); if (object == null) {
object = objectFactory.getObject();
context.put(name, object);
} return object;
} @NonNull
private Map<String, Object> getContext() {
return threadLocal.get();
} @Override
public Object remove(String name) {
Map<String, Object> context = getContext();
return context.remove(name);
} @Override
public void registerDestructionCallback(String name, Runnable callback) {
// TODO
} @Override
public Object resolveContextualObject(String key) {
Map<String, Object> context = getContext();
return context.get(key);
} @Override
public String getConversationId() {
Thread thread = Thread.currentThread();
return String.valueOf(thread.getId());
}
}

Spring官方文档我们学习到要使用一个自定义作用域有2步:

1.创建一个自定义作用域(Creating a Custom Scope),如上面的代码示例,其实只是一个create

2.把你的作用域注册到spring(Using a Custom Scope),让spring容易知道存在这样一个新的scope

将作用域注册到spring容器中,基本的API是:ConfigurableBeanFactory#registerScope

package org.springframework.beans.factory.config;
public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
/**
* Register the given scope, backed by the given Scope implementation.
* @param scopeName the scope identifier
* @param scope the backing Scope implementation
*/
void registerScope(String scopeName, Scope scope);
}

下例展示了如何将作用域注册到spring容器中:

public class ThreadLocalScopeDemo {

    @Bean
@Scope(ThreadLocalScope.SCOPE_NAME) //这里将一个Bean的作用域设置为我们自己的作用域
public User user() {
return createUser();
} private static User createUser() {
User user = new User();
user.setId(System.nanoTime());
return user;
} public static void main(String[] args) { // 创建 BeanFactory 容器
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
// 注册 Configuration Class(配置类) -> Spring Bean
applicationContext.register(ThreadLocalScopeDemo.class); applicationContext.addBeanFactoryPostProcessor(beanFactory -> {
// 注册自定义 scope
beanFactory.registerScope(ThreadLocalScope.SCOPE_NAME, new ThreadLocalScope());
});
// 启动 Spring 应用上下文
applicationContext.refresh();
//依赖查找
scopedBeansByLookup(applicationContext);
// 关闭 Spring 应用上下文
applicationContext.close();
} private static void scopedBeansByLookup(AnnotationConfigApplicationContext applicationContext) { for (int i = 0; i < 3; i++) {
Thread thread = new Thread(() -> {
// user在相同线程是共享Bean对象,在不同线程是不同的对象
User user = applicationContext.getBean("user", User.class);
System.out.printf("[Thread id :%d] user = %s%n", Thread.currentThread().getId(), user);
}); // 启动线程
thread.start();
// 强制线程执行完成
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

这样我们就实现了一个例子,把自定义scope的使用说清了,下面我们研究一下这个接口:org.springframework.beans.factory.config.Scope

这可是和BeanDefinition在同一个包中,相当于是SpringIOC的基础核心包。

这个接口从Spring2.0就出现了,接口定义的方法如下:

public interface Scope {
Object get(String name, ObjectFactory<?> objectFactory); @Nullable
Object remove(String name); void registerDestructionCallback(String name, Runnable callback); @Nullable
Object resolveContextualObject(String key); @Nullable
String getConversationId();
}
方法名 说明
get(String name, ObjectFactory<?> objectFactory) 返回一个属于当前自定义Scope的对象
remove(String name) 将一个指定name的对象从当前Scope中移除
registerDestructionCallback(String name, Runnable callback) 注册一个回调,当某个name的对象在当前Scope被销毁时执行。
resolveContextualObject(String key) 解析给定键的上下文对象(如果有)。例如,HttpServletRequest对象的key:"request" ,不太好理解,但是查询调用的地方,发现是BeanExpressionContext.java在使用,看起来是SpEL表达式(或理解为JSP页面)使用时,用"request"表示servletRequest
getConversationId() 获取一个当前scope的会话id,对上例ThreadLocalScope来说,就是当前线程的线程id

这里需要注意的是:之前我们在学习依赖查找时,比如我们调用applicationContext.getBean(String name)的时候,内部的代码会判断一个bean是singleton还是prototype,也会和我们上述的Scope接口打交道,singleton与prototype是最最基础的2个作用域,它们是通过在org.springframework.beans.factory.config.ConfigurableBeanFactory类中定义了2个常量字符串来表示,而每一个BeanDefinition中的定义都是包含2个方法:boolean isPrototype();boolean isSingleton();

1.org.springframework.beans.factory.config.ConfigurableBeanFactory#SCOPE_SINGLETON

2.org.springframework.beans.factory.config.ConfigurableBeanFactory#SCOPE_PROTOTYPE

依赖查找最核心的方法:

AbstractBeanFactory.doGetBean(final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly)

	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { final String beanName = transformedBeanName(name);
Object bean; // Eagerly check singleton cache for manually registered singletons.
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
//省略...
}
else {
// Fail if we're already creating this bean instance: We're assumably within a circular reference.
if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
} // Check if bean definition exists in this factory.
BeanFactory parentBeanFactory = getParentBeanFactory();
if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
// 省略。。。
} if (!typeCheckOnly) {
markBeanAsCreated(beanName);
} try {
final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args); // 省略... // 判断BeanDefinition是不是单例
if (mbd.isSingleton()) {
//省略...单例bean的创建方式
} else if (mbd.isPrototype()) {
//判断BeanDefinition是不是prototype
//省略...prototype bean的创建方式
}
else {
//其他作用域的bean的创建,在这里和Scope接口打交道的
String scopeName = mbd.getScope();
final Scope scope = this.scopes.get(scopeName);
if (scope == null) {
throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
}
try {
//这里调用了Scope.get()
Object scopedInstance = scope.get(beanName, () -> {
beforePrototypeCreation(beanName);
try {
return createBean(beanName, mbd, args);
}
finally {
afterPrototypeCreation(beanName);
}
});
bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
}
catch (IllegalStateException ex) {
throw new BeanCreationException(beanName,
"Scope '" + scopeName + "' is not active for the current thread; consider " +
"defining a scoped proxy for this bean if you intend to refer to it from a singleton",
ex);
}
}
}
catch (BeansException ex) {
cleanupAfterBeanCreationFailure(beanName);
throw ex;
}
}
// 省略...
}

所以这里的Scope接口的学习都是为了学习自定义Scope的使用,一般业务代码的编写是不会使用到的,但是在我们新的SpringCloud生态圈中就出现了一个自定义Scope:RefreshScope。

SpringCloud中的RefreshScope

(a)RefreshScope简介

在如下spring模块中引入了一个自定义Scope,叫RefreshScope。基于上面对Scope接口的学习,我们可以认为:@RefreshScope 是scopeName="refresh"的@Scope.

  <groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-context</artifactId>

SpringCloud context中存在2个RefreshScope.class:

  1. org.springframework.cloud.context.config.annotation.RefreshScope 这是注解,是从Scope注解派生出来的。
  • @RefreshScope等于@Scope("refresh")
  1. org.springframework.cloud.context.scope.refresh.RefreshScope 这是Scope接口(上面介绍过)的实现类。
public class RefreshScope extends GenericScope implements ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>, Ordered {
...
public RefreshScope() {
super.setName("refresh");
}
...
}

(b)RefreshScope实现类的注册

GenericScope注册自己(实现类是RefreshScope)

public class GenericScope implements Scope, BeanFactoryPostProcessor...{
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
beanFactory.registerScope(this.name, this); //name=refresh this=RefreshScope对象
...
}
}

(c)@RefreshScope注解修饰的Bean

当Spring容器启动的时候,我们自定义的bean会被注册到IoC容器中,在该过程中:

org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean方法中,会解析我们自定义Bean对象的Scope注解的属性等。

	private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) { AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
} abd.setInstanceSupplier(supplier);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd); //这里解析Scope注解元信息
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

(d)Scope接口的实现类RefreshScope注入到容器的过程

到此,我们还有一个疑问没有解决:

RefreshScope实现类并不是一个Bean,并未被容器管理到,它是怎么被注入到IoC容器中的呢?

答案是:org.springframework.cloud.autoconfigure.RefreshAutoConfiguration

在SpringCloud中,RefreshAutoConfiguration自动装配的时候,会初始化RefreshScope实例。

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RefreshScope.class)
@ConditionalOnProperty(name = RefreshAutoConfiguration.REFRESH_SCOPE_ENABLED, //spring.cloud.refresh.enabled
matchIfMissing = true)
@AutoConfigureBefore(HibernateJpaAutoConfiguration.class)
public class RefreshAutoConfiguration {
@Bean
@ConditionalOnMissingBean(RefreshScope.class)
public static RefreshScope refreshScope() {
return new RefreshScope();
}

配置spring.cloud.refresh.enabled默认值true配置在:

/Users/baitao/.m2/repository/org/springframework/cloud/spring-cloud-commons/2.2.0.RELEASE/spring-cloud-commons-2.2.0.RELEASE.jar!/META-INF/additional-spring-configuration-metadata.json

{
"properties": [
{
"defaultValue": "true",
"name": "spring.cloud.refresh.enabled",
"description": "Enables autoconfiguration for the refresh scope and associated features.",
"type": "java.lang.Boolean"
}
...
]
}

总:

1.SpringCloud程序的存在一个自动装配的类,这个类默认情况下会自动初始化一个RefreshScope实例,该实例是GenericScope的子类,然后注册到容器中。(RefreshAutoConfiguration.java, )

2.当容器启动的时候,GenericScope会自己把自己注册到scope中(ConfigurableBeanFactory#registerScope)(GenericScope)

3.然后当自定义的Bean(被@RefreshScope修饰)注册的时候,会被容器读取到其作用域为refresh。(AnnotatedBeanDefinitionReader#doRegisterBean)

通过上面三步,一个带有@RefreshScope的自定义Bean就被注册到容器中来,其作用域为refresh。

4.当我们后续进行以来查找的时候,会绕过Singleton和Prototype分支,进入最后一个分支,通过调用Scope接口的get()获取到该refresh作用域的实例。(AbstractBeanFactory.doGetBean)

TODO

待完善的点

RefreshScope被触发的场景,工作项目中的RefreshScope测试。

1.RefreshScope被触发:Spring Boot Actuator Endpoint

2.Web hook(Git hook)

3.RefreshScope被触发:Spring Cloud Bus

【Spring】IoC容器 - Spring Bean作用域Scope(含SpringCloud中的RefreshScope )的更多相关文章

  1. spring IOC容器实例化Bean的方式与RequestContextListener应用

    spring IOC容器实例化Bean的方式有: singleton 在spring IOC容器中仅存在一个Bean实例,Bean以单实例的方式存在. prototype 每次从容器中调用Bean时, ...

  2. Spring IOC容器创建bean过程浅析

    1. 背景 Spring框架本身非常庞大,源码阅读可以从Spring IOC容器的实现开始一点点了解.然而即便是IOC容器,代码仍然是非常多,短时间内全部精读完并不现实 本文分析比较浅,而完整的IOC ...

  3. spring-framework-中文文档一:IoC容器、介绍Spring IoC容器和bean

    5. IoC容器 5.1介绍Spring IoC容器和bean 5.2容器概述 本章介绍Spring Framework实现控制反转(IoC)[1]原理.IoC也被称为依赖注入(DI).它是一个过程, ...

  4. Spring IoC 容器和 bean 对象

    程序的耦合性: 耦合性(Coupling),又叫耦合度,是对模块间关联程度的度量.耦合的强弱取决于模块间接口的复杂性.调用模块的方式以及通过界面传送数据的多少.模块间的耦合度是指模块之间的依赖关系,包 ...

  5. spring揭密学习笔记(3)-spring ioc容器:Spring的IoC容器之BeanFactory

    1. Spring的IoC容器和IoC Service Provider的关系 Spring的IoC容器和IoC Service Provider所提供的服务之间存在一定的交集,二者的关系如图4-1所 ...

  6. Spring IOC容器对bean的生命周期进行管理的过程

    1.通过构造器或者工厂方法创建bean的实例 2.为bean的属性设置值和对其他bean的引用 3.将bean的实例传递给bean的后置处理器BeanPostProcessor的postProcess ...

  7. Spring IOC 容器源码分析 - 获取单例 bean

    1. 简介 为了写 Spring IOC 容器源码分析系列的文章,我特地写了一篇 Spring IOC 容器的导读文章.在导读一文中,我介绍了 Spring 的一些特性以及阅读 Spring 源码的一 ...

  8. MyEclipse Spring 学习总结一 Spring IOC容器

    一.Spring IOC容器---- Spring AllicationContext容器 程序的结构如下: 1.首先在MyEclipse 创建创建Java Project 2.创建好后,添加spin ...

  9. 当你的Spring IOC 容器(即applicationContext.xml文件)忘记配到web.xml 文件中时

    当你的Spring IOC 容器忘记配到web.xml 文件中时,启动服务器就会报错. 部分错误如下: Caused by: org.springframework.beans.factory.NoS ...

随机推荐

  1. 揭秘盒马鲜生 Android 短视频秒播优化方案

    短视频作为内容重要的承载方式,是吸引用户的重点,短视频的内容与体验直接关系到用户是否愿意长时停留.因此,体验的优化就显得尤为重要.上一篇我们分享了 iOS 短视频秒播优化,这篇我们来聊聊 Androi ...

  2. adb - Performing Push Install adb: error: failed to get feature set: more than one 解决方案

    问题背景 直接 adb install 包名  无法安装成功 解决方案 安装 adb -s 10.200.241.215:5555 install test.apk 删除 adb -s 10.200. ...

  3. ActiveMQ和消息中间件概念

    一.概念

  4. 依赖注入Bean属性——手动装配Bean

    一.构造方法注入 其中,可以根据不同的参数列表调用不同的重载的构造方法: 其中,基本数据类型没有包,引用类型都有包路径,基本类型对应封装类: 二.通过property标签调用类的set方法注入 三.通 ...

  5. Linux find命令实例教程 15个find命令用法

    除了在一个目录结构下查找文件这种基本的操作,你还可以用find命令实现一些实用的操作,使你的命令行之旅更加简易.本文将介绍15种无论是于新手还是老鸟都非常有用的Linux find命令.首先,在你的h ...

  6. 【PHP数据结构】队列的相关逻辑操作

    在逻辑结构中,我们已经学习了一个非常经典的结构类型:栈.今天,我们就来学习另外一个也是非常经典的逻辑结构类型:队列.相信不少同学已经使用过 redis . rabbitmq 之类的缓存队列工具.其实, ...

  7. Orchard Core入门配方和主题

    包含Orchard Core入门配方和主题 可以通过两个不同的NuGet包使用Orchard Core. OrchardCore.Application.Cms.Core.Targets Orchar ...

  8. Jmeter监控技术实战

    性能测试中监控的意义 为性能分析提供依据 监控方案 serverAgent jmeter的插件,监控颗粒度不高,界面简陋 服务器中启动 jmeter中添加插件 Nmon Grafana 优秀监控方案所 ...

  9. appium和selenium不同与相同之处

    原文来自: https://www.cnblogs.com/zhengshuheng/p/6370398.html selenium是web端的自动化,appium是app端的自动化,它继承了webd ...

  10. 使用form插件 和ajax 结合使用 没有调用success的原因

    当我做文件上传的时候出现不调用success方法  将datype: 'json' 注释掉后成功响应 浏览器显示200状态码,并且响应头为json格式,格式ajax不认为它是json,所以一直执行错误 ...