SpringBoot 启动流程
SpringBoot 启动流程
- 加载 resources/META-INF/spring.factories 中配置的 ApplicationContextInitializer 和 ApplicationListener。
/**
* 加载在框架内部使用的各种通用工厂 bean。
* spring.factories 文件必须满足 Properties 文件格式,属性的 key 是接口或抽象类的全限定类名,
* value 是一组由逗号分隔的实现类全类名。
*/
public final class SpringFactoriesLoader {
/**
* 工厂 bean 的搜索路径,可以在多个 jar 文件中出现 spring.factories 文件。
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
private SpringFactoriesLoader() {
}
/**
* 使用给定的类加载器,加载并实例化目标路径 FACTORIES_RESOURCE_LOCATION 下特定接口的实现类,
* 或抽象类的子类。创建的类实例根据 AnnotationAwareOrderComparator 进行排序。
*/
public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
Assert.notNull(factoryClass, "'factoryClass' must not be null");
ClassLoader classLoaderToUse = classLoader;
if (classLoaderToUse == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
final List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
if (logger.isTraceEnabled()) {
logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
}
final List<T> result = new ArrayList<>(factoryNames.size());
for (final String factoryName : factoryNames) {
result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
}
AnnotationAwareOrderComparator.sort(result);
return result;
}
/**
* 使用指定的类加载器,加载 FACTORIES_RESOURCE_LOCATION 路径下指定类型的 factory 全类名。
*/
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
// 获取目标 factory 全类名
final String factoryClassName = factoryClass.getName();
// 加载所有配置文件,并读取指定 factory 的实现类列表
return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 如果已经加载了 spring.factories 文件,则直接从缓存中获取
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) {
return result;
}
try {
// 读取资源 URL
final Enumeration<URL> urls = classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION);
result = new LinkedMultiValueMap<>();
while (urls.hasMoreElements()) {
final URL url = urls.nextElement();
final UrlResource resource = new UrlResource(url);
// 将资源加载到 Properties 文件中
final Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (final Map.Entry<?, ?> entry : properties.entrySet()) {
// 提取 factory 类名称作为 key
final String factoryClassName = ((String) entry.getKey()).trim();
/**
* 将配置的实现类按照 , 分隔,添加到 LinkedMultiValueMap 中。
* 内部通过 LinkedList 管理相同 factory 名称的各种实现类。
*/
for (final String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
result.add(factoryClassName, factoryName.trim());
}
}
}
// 加入缓存
cache.put(classLoader, result);
return result;
}
catch (final IOException ex) {
throw new IllegalArgumentException("Unable to load factories from location [" +
FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
@SuppressWarnings("unchecked")
private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
try {
final Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
if (!factoryClass.isAssignableFrom(instanceClass)) {
throw new IllegalArgumentException(
"Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
}
return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
}
catch (final Throwable ex) {
throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
}
}
}
- 加载 resources/META-INF/spring.factories 中配置的 SpringApplicationRunListener
/**
* SpringApplication 监听器,必须提供一个接受命令行参数 String[] 的
* public 构造函数完成实例的初始化。
*/
public interface SpringApplicationRunListener {
/**
* 1)加载完 SpringApplicationRunListener 实例后立即执行的方法,在 Application 初始化早期使用。
*/
void starting();
/**
* 2)environment 准备好时调用,在 ApplicationContext 创建之前。
*/
void environmentPrepared(ConfigurableEnvironment environment);
/**
* 3)在 ApplicationContext 创建并准备好时调用,此时资源还未加载。
*/
void contextPrepared(ConfigurableApplicationContext context);
/**
* 4)ApplicationContext 加载完成之后调用,但是 ApplicationContext 还未刷新。
* 组件扫描还未执行,BeanDefinition 还未注册。
*/
void contextLoaded(ConfigurableApplicationContext context);
/**
* 5)ApplicationContext 已经刷新并启动,但还未执行 CommandLineRunner 和 ApplicationRunner。
*/
void started(ConfigurableApplicationContext context);
/**
* 6)ApplicationContext 已经刷新并启动,所有的CommandLineRunner 和 ApplicationRunner 都已经执行。
*/
void running(ConfigurableApplicationContext context);
/**
* 运行此 ApplicationContext 过程中出现异常
*/
void failed(ConfigurableApplicationContext context, Throwable exception);
}
/**
* 用于发布 SpringApplicationEvent 的 SpringApplicationRunListener
*/
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
/**
* 目标应用程序
*/
private final SpringApplication application;
/**
* 命令行参数
*/
private final String[] args;
/**
* 简单的应用事件广播器
*/
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
initialMulticaster = new SimpleApplicationEventMulticaster();
for (final ApplicationListener<?> listener : application.getListeners()) {
initialMulticaster.addApplicationListener(listener);
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public void starting() {
initialMulticaster.multicastEvent(
new ApplicationStartingEvent(application, args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
application, args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
initialMulticaster.multicastEvent(new ApplicationContextInitializedEvent(
application, args, context));
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
for (final ApplicationListener<?> listener : application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
initialMulticaster.multicastEvent(
new ApplicationPreparedEvent(application, args, context));
}
@Override
public void started(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationStartedEvent(application, args, context));
}
@Override
public void running(ConfigurableApplicationContext context) {
context.publishEvent(
new ApplicationReadyEvent(application, args, context));
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
final ApplicationFailedEvent event = new ApplicationFailedEvent(application,
args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context's listeners instead
if (context instanceof AbstractApplicationContext) {
for (final ApplicationListener<?> listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
initialMulticaster.addApplicationListener(listener);
}
}
initialMulticaster.setErrorHandler(new LoggingErrorHandler());
initialMulticaster.multicastEvent(event);
}
}
private static class LoggingErrorHandler implements ErrorHandler {
private static Log logger = LogFactory.getLog(EventPublishingRunListener.class);
@Override
public void handleError(Throwable throwable) {
logger.warn("Error calling ApplicationEventListener", throwable);
}
}
}
- 创建 StandardServletEnvironment 并完成初始配置。
1)写入转换服务 ApplicationConversionService.getSharedInstance()
2)将命令行参数作为属性源加入到 environment.getPropertySources() 中
3)写入配置的 AdditionalProfile。
通过 EventPublishingRunListener 发布 ApplicationEnvironmentPreparedEvent 事件
加载 resources/META-INF/spring.factories 中配置的 EnvironmentPostProcessor,
并触发其 postProcessEnvironment 调用,完成 Environment 的后处理【主要用于加载属性源到 Environment 中】。将 Environment 绑定到 SpringApplication 中。
创建 ApplicationContext:AnnotationConfigServletWebServerApplicationContext
加载 resources/META-INF/spring.factories 中配置的 SpringBootExceptionReporter【FailureAnalyzers】
执行所有已加载的 ApplicationContextInitializer,附加自定义配置。
通过 EventPublishingRunListener 发布 ApplicationContextInitializedEvent 事件
创建 BeanDefinitionLoader,将应用程序主类解析为 BeanDefinition 并注册到 DefaultListableBeanFactory 中。
@Lazy:此 Bean 是否延迟初始化
@DependsOn:此 Bean 是否需要依赖其他 Bean
@Role:此 Bean 的组件角色
@Description:此 Bean 的描述信息
@Scope:bean 的代理默认和作用域
@Primary:依赖注入时存在多个候选 bean 时,优先注入此 bean
- 通过 EventPublishingRunListener 发布 ApplicationPreparedEvent 事件。
刷新此 ApplicationContext: AbstractApplicationContext#refresh()
AbstractApplicationContext#
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (startupShutdownMonitor) {
// 1)准备刷新上下文
prepareRefresh();
// 2)通知子类刷新内部的 ConfigurableListableBeanFactory
final ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3)准备在此 ApplicationContext 中使用的 BeanFactory
prepareBeanFactory(beanFactory);
try {
// 4)允许在 ApplicationContext 中对 BeanFactory 进行后处理
postProcessBeanFactory(beanFactory);
// 5)执行注册在当前 ApplicationContext 中的 beanFactory 后处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 6)将 BeanPostProcessor 注册到容器中
registerBeanPostProcessors(beanFactory);
// 7)初始化 MessageSource
initMessageSource();
// 8)初始化 SimpleApplicationEventMulticaster,用于实现事件发布
initApplicationEventMulticaster();
// 9)初始化其他特殊的 bean
onRefresh();
// 10)注册 ApplicationListener
registerListeners();
// 11)实例化所有非延迟的单例类
finishBeanFactoryInitialization(beanFactory);
// 12)清理资源缓存,初始化 LifecycleProcessor,并发布相关事件
finishRefresh();
}
catch (final BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 13)删除已经实例化的单例,避免资源泄漏
destroyBeans();
// 14)设置 active 标识
cancelRefresh(ex);
// 15)传播异常到外部
throw ex;
}
finally {
// 16)重设通用的缓存
resetCommonCaches();
}
}
}
prepareRefresh:清除类路径扫描器缓存、写入 closed、active 标识、验证所有必须的属性是否都能解析。
obtainFreshBeanFactory:写入 refreshed 标识,获取 DefaultListableBeanFactory。
prepareBeanFactory:写入 BeanPostProcessor、写入可以忽略的依赖注入接口,注册部分内部可解析的依赖接口等。
postProcessBeanFactory:注册 WebApplicationScope,注册部分内部可解析的依赖接口。
invokeBeanFactoryPostProcessors:
执行 SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
注册用于获取 MetadataReader 的 org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory 的 BeanDefinition。
实例化并执行所有的 BeanDefinitionRegistryPostProcessor【内部只注册了 ConfigurationClassPostProcessor】:
1)执行所有实现 PriorityOrdered 接口的 BeanDefinitionRegistry 后处理器【可以通过自定义 ApplicationContextInitializer 注册】。
实例化并注册用于解析配置类的解析器【ConfigurationClassPostProcessor】:org.springframework.context.annotation.internalConfigurationAnnotationProcessor。
使用 ConfigurationClassParser 解析主配置类并执行 ComponentScan、执行自动配置类的解析、各种导入 XML 文件的解析,将相关所有类的 BeanDefinition
注册到 DefaultListableBeanFactory 中。
2)执行所有实现 Ordered 接口的 BeanDefinitionRegistry 后处理器。
3)执行所有普通的 BeanDefinitionRegistry 后处理器。
1)实例化并顺序执行以下内置的 BeanFactoryPostProcessor 后处理器
无操作:org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor
无操作:org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor
使用 ConfigurationClassEnhancer 强化所有的配置类:org.springframework.context.annotation.ConfigurationClassPostProcessor
org.springframework.boot.context.config.ConfigFileApplicationListener$PropertySourceOrderingPostProcessor
2)
执行实现 PriorityOrdered 接口的 BeanFactoryPostProcessor 后处理器。
执行实现 Ordered 接口的 BeanFactoryPostProcessor 后处理器。
执行普通的 BeanFactoryPostProcessor 后处理器。
- registerBeanPostProcessors:
按照 【PriorityOrdered、Ordered、普通】的顺序将 BeanPostProcessor 添加到 BeanFactory 中。
org.springframework.context.support.ApplicationContextAwareProcessor
org.springframework.boot.web.servlet.context.WebApplicationContextServletContextAwareProcessor org.springframework.context.annotation.ConfigurationClassPostProcessor$ImportAwareBeanPostProcessor
org.springframework.context.support.PostProcessorRegistrationDelegate$BeanPostProcessorChecker
org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator
org.springframework.validation.beanvalidation.MethodValidationPostProcessor
org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor
org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor
org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor
org.springframework.context.annotation.CommonAnnotationBeanPostProcessor
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor
org.springframework.context.support.ApplicationListenerDetector
initMessageSource:注册 messageSource 单例【DelegatingMessageSource】
注册 applicationEventMulticaster 单例【SimpleApplicationEventMulticaster】,用于广播 ApplicationEvent。
onRefresh:设置主题,创建内嵌服务器【WebServer】,获取 ServletContextInitializer【DispatcherServletRegistrationBean】
初始化器并注册 DispatcherServlet 到 javax.servlet.ServletContext 和 org.apache.catalina.core.ApplicationContext 中。
注册 OrderedCharacterEncodingFilter、OrderedHiddenHttpMethodFilter、OrderedFormContentFilter、OrderedRequestContextFilter
等过滤器,也可注册自定义过滤器。
写入 ServletContextPropertySource、ServletConfigPropertySource 到 Environment 中。registerListeners:注册内置的 ApplicationListener、自定义的 ApplicationListener 到 ApplicationEventMulticaster 中。
finishBeanFactoryInitialization:冻结 DefaultListableBeanFactory 中注册的 BeanDefinition 信息,并实例化所有非延迟初始化的单例 bean。
在所有 eager 单例初始化完成之后,如果其实现了 SmartInitializingSingleton 接口,则触发其 afterSingletonsInstantiated 调用完成后处理。
bean 的实例化过程:
1)优先实例化所有其依赖的 bean
2)执行 InstantiationAwareBeanPostProcessor#postProcessBeforeInstantiation 预处理,
如果该方法手动创建了 bean,则直接返回,不进入框架创建 bean 的主流程。
3)实例化 bean
4)执行 MergedBeanDefinitionPostProcessor#postProcessMergedBeanDefinition 后处理,
AutowiredAnnotationBeanPostProcessor:解析 @Autowired、@Value、@Inject【JSR303】 注解元数据并缓存。
CommonAnnotationBeanPostProcessor:解析 @PostConstruct、@PreDestroy 注解并将目标方法加入 BeanDefinition 中,解析 @Resource 注解元数据并缓存。
5)将此 bean 加入到 singletonFactories 缓存中,以解决循环依赖问题。
6)执行 InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation 后处理,
如果该方法完成了 bean 的依赖属性注入并返回 false,则跳过框架的属性注入流程。
7)执行 InstantiationAwareBeanPostProcessor#postProcessProperties 依赖属性注入【直接属性注入、set 方法注入】。
@Resource、@Autowired、@Value、@Inject【JSR303】
8)执行其他方式的属性依赖注入,如 XML 配置方式。
9)执行 BeanNameAware、BeanClassLoaderAware、BeanFactoryAware 注入。
10)触发 BeanPostProcessor#postProcessBeforeInitialization 调用,
ApplicationContextAwareProcessor:EnvironmentAware、EmbeddedValueResolverAware、ResourceLoaderAware、ApplicationEventPublisherAware、MessageSourceAware、ApplicationContextAware 注入。
ServletContextAwareProcessor:ServletContextAware、ServletConfigAware 注入。
11)执行 InitializingBean#afterPropertiesSet 调用,执行自定义的初始化方法。
12)执行 BeanPostProcessor#postProcessAfterInitialization 调用。
AnnotationAwareAspectJAutoProxyCreator:目标类型上存在 AspectJ 切面注解、目标类型能匹配 Spring 内置的 Advisor【缓存通知、事务处理】,则为目标 bean 创建代理。
MethodValidationPostProcessor:目标类型上存在 @Validated 注解,方法参数或返回类型上存在 JSR303 校验约束,则为目标 bean 创建代理。
PersistenceExceptionTranslationPostProcessor:如果目标类型实现了 org.springframework.dao.support.PersistenceExceptionTranslator 接口,
则将本地资源异常转换为 DataAccessException。
ApplicationListenerDetector:目标 bean 实现了 ApplicationListener 接口,则将其加入到 AbstractApplicationContext.applicationListeners 中。
注册 DefaultLifecycleProcessor,查找所有实现 SmartLifecycle 接口的 bean && 如果它是 isAutoStartup(),则触发其 start() 方法调用。
通过 EventPublishingRunListener 发布 ContextRefreshedEvent 事件。
启动 WebServer。
通过 EventPublishingRunListener 发布 ServletWebServerInitializedEvent 事件。
清理各种缓存
ReflectionUtils.clearCache();
AnnotationUtils.clearCache();
ResolvableType.clearCache();
CachedIntrospectionResults.clearClassLoader(getClassLoader());
注册 Application 的 ShutdownHook。
触发 SpringApplicationRunListener 的 started 通知。
通过 EventPublishingRunListener 发布 ApplicationStartedEvent 事件。
按照 Order 对 ApplicationRunner、CommandLineRunner 进行排序,并顺序执行。
触发 SpringApplicationRunListener 的 running 通知。
通过 EventPublishingRunListener 发布 ApplicationReadyEvent 事件。
应用程序启动完成。
SpringBoot 启动流程的更多相关文章
- SpringBoot启动流程解析
写在前面: 由于该系统是底层系统,以微服务形式对外暴露dubbo服务,所以本流程中SpringBoot不基于jetty或者tomcat等容器启动方式发布服务,而是以执行程序方式启动来发布(参考下图ke ...
- SpringBoot启动流程分析(五):SpringBoot自动装配原理实现
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(六):IoC容器依赖注入
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(一):SpringApplication类初始化过程
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(二):SpringApplication的run方法
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(三):SpringApplication的run方法之prepareContext()方法
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程分析(四):IoC容器的初始化过程
SpringBoot系列文章简介 SpringBoot源码阅读辅助篇: Spring IoC容器与应用上下文的设计与实现 SpringBoot启动流程源码分析: SpringBoot启动流程分析(一) ...
- SpringBoot启动流程及其原理
Spring Boot.Spring MVC 和 Spring 有什么区别? 分别描述各自的特征: Spring 框架就像一个家族,有众多衍生产品例如 boot.security.jpa等等:但他们的 ...
- springboot启动流程(一)构造SpringApplication实例对象
所有文章 https://www.cnblogs.com/lay2017/p/11478237.html 启动入口 本文是springboot启动流程的第一篇,涉及的内容是SpringApplicat ...
随机推荐
- navicat for mysql 12 的破解安装和基本操作
需要安装Navicat软件 可以复制百度云链接,若失效,请联系我,我会尽快回复 将链接中的破解文件复制到软件安装的位置即完成破解 链接:https://pan.baidu.com/s/1sIkjsd3 ...
- python将str类型的数据变成datetime时间类型数据
如下: import datetime date_str = '2019_05_09' date_date = datetime.date(*map(int, date_str.split('_')) ...
- xml 字符串解析成通用的map
[quote]Stax解析技术:快速高效,根据流的形式解析xml,比dom解析技术更加快,dom解析技术是基于文档结构树的,会把整个dom文件树加载到内存进行解析[/quote] package co ...
- 使用nodejs开发一个markdown文档管理小系统(一)Using Nodejs to quickly develop a markdown management system
好多年没碰过前端jquery了,用一两天时间重温一下,刚好写个小工具, 不递归取文件夹和文件,只写一层,保持足够简单,验证和参数判断暂不写,毕竟只写了几个小时而已,功能算完备了,添加一个简单的管理员权 ...
- cudaDeviceProp结构体
struct cudaDeviceProp {char name[256]; //器件的名字size_t totalGlobalMem; //Global Memory 的byt ...
- ftp不能复制文件
解决办法: ie->工具->internet选项->安全->自定义级别->下载->文件下载->启用
- anaconda安装失败
2019.10版本怎么安装都不行换了2018.10安装ok
- 如何卸载Win10 RS3上预装的office2016
原因分析: 由于微软在Win10 1709(RS3版本)上改变了office 2016家庭和学生版的预装方式(预装office 2016改为Windows Store应用商店的即点即用程序),无法使用 ...
- linux下PHP扩展安装memcache模块
linux下PHP扩展安装memcache模块 roid 安装环境RHEL 4Php 5.2.6 所需软件libevent-1.4.6-stable.tar.gz (http://monkey.o ...
- jQuery attr() prop() data()用法及区别
.attr(),此方法从jq1.0开始一直存在,官方文档写的作用是读/写DOM的attribute值,其实1.6之前有时候是attribute,有时候又是property..prop(),此方法jq1 ...