Servlet初始化相关问题,以及Spring容器初始化

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Servlet初始化相关问题,以及Spring容器初始化相关的知识,希望对你有一定的参考价值。

一、Servlet初始化

①Servlet在初始化的时候,是通过init(ServletConfig config) 或 init() 来执行的。 
ServletConfig 是一个接口,它怎样传递给他一格对象来进行初始化呢?其实,是这个对象是由 servlet 容
器来实例化的,由容器产生一格 ServletConfig 的实现类的对象,然后传递给 Servlet 

②我们有些时候可能在 Servlet 初始化时给它一些固定的配置参数,那么这些参数是怎样传递到 Servlet 
呢?其实,我们在 web.xml 中给 servlet 配置启动参数,在容器对 servlet 进行初始化的时候,会收集你所配置的参数,记录在 ServletConfig 的实现类中,所以你才可以通过 ServletConfig 对象的
public String getInitParameter(String name); 或 
public Enumeration getInitParameterNames(); 
方法来取得你已经配置好的参数,也就是说,你对servlet的配置都已经记录在ServletConfig对象中了。 
结论:你对Servlet的配置,在Servlet的初始化时都由容器来收集并且记录到ServletConfig的实现类中。 

③我们来看一个 Servlet 的配置 
<servlet> 
<servlet-name>index</servlet-name> 
<servlet-class>org.zy.pro.sw.servlet.IndexServlet</servlet-class> 
<init-param> 
<param-name>dbconfig</param-name> 
<param-value>/WEB-INF/dbconfig.xml</param-value> 
</init-param> 
</servlet> 
在此,我们实现对数据库的配置文件的加载。 
当 Servlet 初始化完成后,我们可以通过 
String dbconf=this.getServletConfig().getInitParameter("dbconfig") 
来取得我们的配置的参数的值。 
但是,我们仅能得到一个配置的字符串。之后我们可以通过配置文件取得我们的数据库的配置参数,然后

对数据库进行初始化。 
其实我们也可以通过传递一个类的名字串,然后再实例化。 
<init-param> 
<param-name>dbconfig</param-name> 
<param-value>org.zy.util.db.DBUtil</param-value> 
</init-param> 
我们先取得配置参数: 
String dbconf=this.getServletConfig().getInitParameter("dbconfig") ; 
然后通过 
Class.forName(dbconf).getInstance(); 
来实例化对象,就可以实现对数据库的调用了。 
结论:在 web.xml 中对 Servlet 的初始化,只能传递字符串类型的数据 
④  ServletContext 
ServletContext 是负责和 Servlet 的上文和下文交互,上面和 Servlet 容器交互,下面和 Servlet 中的请求
和相应进行交互。 在 ServletConfig 中,public ServletContext getServletContext(); 方法实现取得当前 ServletContext 的对象。 你可能要问, ServletContext 是一个接口,那么你如何取得他的对象呢?
其实这个问题和 ServletConfig 相同,都是在 Servlet 进行初始化的时候产生的对象,是由容器来初始化的。



转自:http://blog.163.com/ly_qmoa/blog/static/263360542008112081839777/
二、ServletContext详解
ServletContext,是一个全局的储存信息的空间,服务器启动之后,其就存在,服务器关闭,其才释放。request,一个用户可有多个;session,一个用户一个;而servletContext,所有用户共用一个。所以,为了节省空间,提高效率,ServletContext中,要放必须的、重要的、所有用户需要共享的线程又是安全的一些信息。

换一种方式说吧,运行在JAVA虚拟机中的每一个Web应用程序都有一个与之相关的Servlet上下文。ServletContext对象是Web服务器中的一个已知路径的根,Servlet上下文被定位于http://localhost:8080/项目名.以 /项目名 请求路径(称为上下文路径)开始的所有请求被发送到与此ServletContext关联的Web应用程序。一个ServletContext对象表示了一个Web应用程序的上下文。

Servlet上下文:Servlet上下文提供对应用程序中所有Servlet所共有的各种资源和功能的访问。Servlet上下文API用于设置应用程序中所有Servlet共有的信息。Servlet可能需要共享他们之间的共有信息。运行于同一服务器的Servlet有时会共享资源,如JSP页面、文件和其他Servlet。

举例:

如,做一个购物类的网站,要从数据库中提取物品信息,如果用session保存这些物品信息,每个用户都访问一便数据库,效率就太低了;所以要用来Servlet上下文来保存,在服务器开始时,就访问数据库,将物品信息存入Servlet上下文中,这样,每个用户只用从上下文中读入物品信息就行了。

3.1 ServletContext接口简介

ServletContext接口定义了运行servlet的web应用的servlet视图。容器供应商负责提供servlet容器内ServletContext接口的实现。使用ServletContext对象,servlet可以记录事件日志,获取资源的URL地址,并且设置和保存上下文内可以访问的其他servlet的属性。

ServletContext以web的已知路径为根路径。比如,假定一个servlet上下文位于http://www.mycorp.com/catalog。以/catalog请求路径开头的所有请求,已知为上下文路径,被路由到和该ServletContext关联的web应用。

3.2 ServletContext接口作用域

容器中部署的每一个web应用都有一个ServletContext接口的实例对象与之关联。如果容器被分布在多个虚拟机上,一个web应用将在每一个VM中有一个ServletContext实例。

不作为web应用一部分部署的容器中的servlet默认是“默认”web应用的一部分,有一个默认的ServletContext。在分布式容器中。默认ServletContext是非分布式的,并且必须只存在于一个VM中。

3.3 初始化参数

ServletContext接口的初始化参数允许servlet访问与web应用相关的上下文初始化参数,这些由应用开发人员在部署描述符中指定:

getInitParameter

getInitParameterNames

应用开发人员利用初始化参数传送配置信息。典型的例子是web管理员的e-mail地址或者一个持有关键数据的系统名称。

3.4 上下文属性

servlet可以通过名称将对象属性绑定到上下文。任何绑定到上下文的属性可以被同一个web应用的其他servlet使用。ServletContext接口的下列方法允许访问这种功能:

setAttribute

getAttribute

getAttributeNames

removeAttribute

3.4.1 分布式容器中的上下文属性

上下文属性对于创建它们的VM来说是本地的。这防止ServletContext属性存储于分布式容器的共享内存中。当信息需要在运行于分布式环境中的servlet之间共享时,信息被放入会话中,存储于数据库中,或者存储于EJB组件中。

3.5 资源

ServletContext接口通过下列方法提供对web应用组成的静态内容文档层级的直接访问,包括html,GIF和JPEG文件:

getResource

getResourceAsStream

getResource和getResourceAsStream方法以“/”开头的字符串为参数,它指定上下文根路径的资源相对路径。文档的层级可能存在于服务器的文件系统,war文件,远程服务器或者在一些其它位置中。

这些方法不用来获取动态内容。比如,在一个支持JSP规范1的容器中,getResource("/index.jsp")这种形式的方法调用将返回JSP源代码,而不是处理后的输出。
Web应用资源的完整列表可以使用getResourcePaths(String path)方法访问。该方法语义的完整信息可以在本规范的API文档中找到。

3.6 多个主机和ServletContext

Web服务器可能支持一个服务器上多个逻辑主机共享一个IP地址。这功能有时被称为“虚拟主机”。这种情况下,每一个逻辑主机必须有它自己的servlet上下文或者servlet上下文组。Servlet上下文不可以被多个虚拟主机共享。

3.7 重载考虑

尽管容器供应商因为对于易于开发而实现的类加载不做要求,但是任何那样的实现必须确保所有它们可能使用2的所有servlet和类,被加载在单个类加载器作用域内。必须保证应用应该如开发人员预想的那样运转。作为开发辅助,绑定监听器的会话通知的完整语义应当由容器支持,在类加载上会话终止的监听上使用。

上一代的容器创建新的类加载器以加载servlet,这和用来加载servlet上下文中使用的其他servlet或者类的类加载器不同。这可能造成servlet上下文内的对象引用指向一个意想不到的类或对象,造成意想不到的行为。需要阻止由新一代类加载器所引发的问题。

3.7.1 临时工作目录

每一个servlet上下文都需要一个临时存储目录。Servlet容器必须为每一个servlet上下文提供一个私有的临时目录,并且使它可以通过javax.servlet.context.tempdir上下文属性可用。这些属性关联的对象必须是java.io.File类型。

这项需求认可了很多servlet引擎实现中提供的常见便利。容器不需要在servlet重启时维持临时目录的内容,但是需要确保一个servlet上下文的临时目录的内容对于该servlet容器上运行的其他web应用的servlet上下文不可见。

三、Spring的Ioc容器初始化 

典型的web环境中,Spring IOC容器是怎样被载入和起作用的。 
简单的说,在web容器中,通过ServletContext为Spring的IOC容器提供宿主环境,对应的建立起一个IOC容器的体系。其中,首先需要建立的是根上下文,这个上下文持有的对象可以有业务对象,数据存取对象,资源,事物管理器等各种中间层对象。在这个上下文的基础上,和web MVC相关还会有一个上下文来保存控制器之类的MVC对象,这样就构成了一个层次化的上下文结构。在web容器中启动Spring应用程序就是一个建立这个上下文体系的过程。Spring为web应用提供了上下文的扩展接口
WebApplicationContext: 
Java代码 
public interface WebApplicationContext extends ApplicationContext {   
//这里定义的常量用于在ServletContext中存取根上下文   
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";  
......   
//对WebApplicationContext来说,需要得到Web容器的ServletContext   
ServletContext getServletContext();   
}  

而一般的启动过程,Spring会使用一个默认的实现,XmlWebApplicationContext - 这个上下文实现作为在web容器中的根上下文容器被建立起来,具体的建立过程在下面我们会详细分析。
Java代码 
public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext {  

/** 这是和web部署相关的位置信息,用来作为默认的根上下文bean定义信息的存放位置*/  
public static final String DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml";  
public static final String DEFAULT_CONFIG_LOCATION_PREFIX = "/WEB-INF/";   
public static final String DEFAULT_CONFIG_LOCATION_SUFFIX = ".xml";   

//我们又看到了熟悉的loadBeanDefinition,就像我们前面对IOC容器的分析中一样,这个加载工程在容器的refresh()的时候启动。   
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {  
//对于XmlWebApplicationContext,当然使用的是XmlBeanDefinitionReader来对bean定义信息来进行解析   
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);  

beanDefinitionReader.setResourceLoader(this);   
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));   

initBeanDefinitionReader(beanDefinitionReader);   
loadBeanDefinitions(beanDefinitionReader);   
}   

protected void initBeanDefinitionReader(XmlBeanDefinitionReader beanDefinitionReader) {  
}   
//使用XmlBeanDefinitionReader来读入bean定义信息   
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {  
String[] configLocations = getConfigLocations();   
if (configLocations != null) {   
for (int i = 0; i < configLocations.length; i++) {   
reader.loadBeanDefinitions(configLocations[i]);   
}   
}   
}   
//这里取得bean定义信息位置,默认的地方是/WEB-INF/applicationContext.xml   
protected String[] getDefaultConfigLocations() {   
if (getNamespace() != null) {   
return new String[] {DEFAULT_CONFIG_LOCATION_PREFIX + getNamespace() + DEFAULT_CONFIG_LOCATION_SUFFIX};  
}   
else {   
return new String[] {DEFAULT_CONFIG_LOCATION};   
}   
}   
}  

对于一个Spring激活的web应用程序,可以通过使用Spring代码声明式的指定在web应用程序启动时载入应用程序上下文(WebApplicationContext),Spring的ContextLoader是提供这样性能的类,我们可以使用 ContextLoaderServlet或者ContextLoaderListener的启动时载入的Servlet来实例化Spring IOC容器 - 为什么会有两个不同的类来装载它呢,这是因为它们的使用需要区别不同的Servlet容器支持的Serlvet版本。但不管是 ContextLoaderSevlet还是 ContextLoaderListener都使用ContextLoader来完成实际的WebApplicationContext的初始化工作。这个ContextLoder就像是Spring Web应用程序在Web容器中的加载器booter。当然这些Servlet的具体使用我们都要借助web容器中的部署描述符来进行相关的定义。
下面我们使用ContextLoaderListener作为载入器作一个详细的分析,这个Servlet的监听器是根上下文被载入的地方,也是整个 Spring web应用加载上下文的第一个地方;从加载过程我们可以看到,首先从Servlet事件中得到ServletContext,然后可以读到配置好的在web.xml的中的各个属性值,然后ContextLoder实例化WebApplicationContext并完成其载入和初始化作为根上下文。当这个根上下文被载入后,它被绑定到web应用程序的ServletContext上。任何需要访问该ApplicationContext的应用程序代码都可以从WebApplicationContextUtils类的静态方法来得到:

Java代码 
WebApplicationContext getWebApplicationContext(ServletContext sc)  

以Tomcat作为Servlet容器为例,下面是具体的步骤: 
1.Tomcat 启动时需要从web.xml中读取启动参数,在web.xml中我们需要对ContextLoaderListener进行配置,对于在web应用启动入口是在ContextLoaderListener中的初始化部分;从Spring MVC上看,实际上在web容器中维护了一系列的IOC容器,其中在ContextLoader中载入的IOC容器作为根上下文而存在于 ServletContext中。
Java代码 
//这里对根上下文进行初始化。   
public void contextInitialized(ServletContextEvent event) {   
//这里创建需要的ContextLoader   
this.contextLoader = createContextLoader();   
//这里使用ContextLoader对根上下文进行载入和初始化   
this.contextLoader.initWebApplicationContext(event.getServletContext());   
}  

通过ContextLoader建立起根上下文的过程,我们可以在ContextLoader中看到: 
Java代码 
public WebApplicationContext initWebApplicationContext(ServletContext servletContext)  
throws IllegalStateException, BeansException {   
//这里先看看是不是已经在ServletContext中存在上下文,如果有说明前面已经被载入过,或者是配置文件有错误。   
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {  
//直接抛出异常   
.........   
}   

...............   
try {   
// 这里载入根上下文的父上下文   
ApplicationContext parent = loadParentContext(servletContext);   

//这里创建根上下文作为整个应用的上下文同时把它存到ServletContext中去,注意这里使用的ServletContext的属性值是   
//ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,以后的应用都是根据这个属性值来取得根上下文的 - 往往作为自己上下文的父上下文  
this.context = createWebApplicationContext(servletContext, parent);   
servletContext.setAttribute(   
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);   
..........   

return this.context;   
}   
............   
}  

建立根上下文的父上下文使用的是下面的代码,取决于在web.xml中定义的参数:locatorFactorySelector,这是一个可选参数: 
Java代码 
protected ApplicationContext loadParentContext(ServletContext servletContext)   
throws BeansException {   

ApplicationContext parentContext = null;   

String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);  
String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);  

if (locatorFactorySelector != null) {   
BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector);  
........   
//得到根上下文的父上下文的引用   
this.parentContextRef = locator.useBeanFactory(parentContextKey);   
//这里建立得到根上下文的父上下文   
parentContext = (ApplicationContext) this.parentContextRef.getFactory();   
}   

return parentContext;   
}  

得到根上下文的父上下文以后,就是根上下文的创建过程: 
Java代码 
protected WebApplicationContext createWebApplicationContext(   
ServletContext servletContext, ApplicationContext parent) throws BeansException {  
//这里需要确定我们载入的根WebApplication的类型,由在web.xml中配置的contextClass中配置的参数可以决定我们需要载入什么样的ApplicationContext,  
//如果没有使用默认的。   
Class contextClass = determineContextClass(servletContext);   
.........   
//这里就是上下文的创建过程   
ConfigurableWebApplicationContext wac =   
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);   
//这里保持对父上下文和ServletContext的引用到根上下文中   
wac.setParent(parent);   
wac.setServletContext(servletContext);   

//这里从web.xml中取得相关的初始化参数   
String configLocation = servletContext.getInitParameter(CONFIG_LOCATION_PARAM);  
if (configLocation != null) {   
wac.setConfigLocations(StringUtils.tokenizeToStringArray(configLocation,   
ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));   
}   
//这里对WebApplicationContext进行初始化,我们又看到了熟悉的refresh调用。   
wac.refresh();   
return wac;   
}  

初始化根ApplicationContext后将其存储到SevletContext中去以后,这样就建立了一个全局的关于整个应用的上下文。这个根上下文会被以后的DispatcherServlet初始化自己的时候作为自己ApplicationContext的父上下文。这个在对 DispatcherServlet做分析的时候我们可以看看到。

3.完成对ContextLoaderListener的初始化以后,Tomcat开始初始化DispatchServlet,- 还记得我们在web.xml中队载入次序进行了定义。DispatcherServlet会建立自己的ApplicationContext,同时建立这个自己的上下文的时候会从ServletContext中得到根上下文作为父上下文,然后再对自己的上下文进行初始化,并最后存到 ServletContext中去供以后检索和使用。
可以从DispatchServlet的父类FrameworkServlet的代码中看到大致的初始化过程,整个ApplicationContext的创建过程和ContextLoder创建的过程相类似:
Java代码 
protected final void initServletBean() throws ServletException, BeansException {  
.........   
try {   
//这里是对上下文的初始化过程。   
this.webApplicationContext = initWebApplicationContext();   
//在完成对上下文的初始化过程结束后,根据bean配置信息建立MVC框架的各个主要元素   
initFrameworkServlet();   
}   
........   
}  

对initWebApplicationContext()调用的代码如下: 
Java代码 
protected WebApplicationContext initWebApplicationContext() throws BeansException {  
//这里调用WebApplicationContextUtils静态类来得到根上下文   
WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());  

//创建当前DispatcherServlet的上下文,其上下文种类使用默认的在FrameworkServlet定义好的:DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;  
WebApplicationContext wac = createWebApplicationContext(parent);   
........   
if (isPublishContext()) {   
//把当前建立的上下文存到ServletContext中去,注意使用的属性名是和当前Servlet名相关的。   
String attrName = getServletContextAttributeName();   
getServletContext().setAttribute(attrName, wac);   
}   
return wac;   
}  

其中我们看到调用了WebApplicationContextUtils的静态方法得到根ApplicationContext: 
Java代码 
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {  
//很简单,直接从ServletContext中通过属性名得到根上下文   
Object attr = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);  
.......   
return (WebApplicationContext) attr;   
}   
然后创建DispatcherServlet自己的WebApplicationContext:   
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)  
throws BeansException {   
.......   
//这里使用了BeanUtils直接得到WebApplicationContext,ContextClass是前面定义好的DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;  
ConfigurableWebApplicationContext wac =   
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(getContextClass());  

//这里配置父上下文,就是在ContextLoader中建立的根上下文   
wac.setParent(parent);   

//保留ServletContext的引用和相关的配置信息。   
wac.setServletContext(getServletContext());   
wac.setServletConfig(getServletConfig());   
wac.setNamespace(getNamespace());   

//这里得到ApplicationContext配置文件的位置   
if (getContextConfigLocation() != null) {   
wac.setConfigLocations(   
StringUtils.tokenizeToStringArray(   
getContextConfigLocation(), ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));  
}   

//这里调用ApplicationContext的初始化过程,同样需要使用refresh()   
wac.refresh();   
return wac;   
}  

4. 然后就是DispatchServlet中对Spring MVC的配置过程,首先对配置文件中的定义元素进行配置 - 请注意这个时候我们的WebApplicationContext已经建立起来了,也意味着DispatcherServlet有自己的定义资源,可以需要从web.xml中读取bean的配置信息,通常我们会使用单独的xml文件来配置MVC中各个要素定义,这里和web容器相关的加载过程实际上已经完成了,下面的处理和普通的Spring应用程序的编写没有什么太大的差别,我们先看看MVC的初始化过程:
Java代码 
protected void initFrameworkServlet() throws ServletException, BeansException {  
initMultipartResolver();   
initLocaleResolver();   
initThemeResolver();   
initHandlerMappings();   
initHandlerAdapters();   
initHandlerExceptionResolvers();   
initRequestToViewNameTranslator();   
initViewResolvers();   
}  

5. 这样MVC的框架就建立起来了,DispatchServlet对接受到的HTTP Request进行分发处理由doService()完成,具体的MVC处理过程我们在doDispatch()中完成,其中包括使用Command模式建立执行链,显示模型数据等,这些处理我们都可以在DispatcherServlet的代码中看到:
Java代码 
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {  
......   
try {   
doDispatch(request, response);   
}   
.......   
}  

实际的请求分发由doDispatch(request,response)来完成: 
Java代码 
protected void doDispatch(final HttpServletRequest request, HttpServletResponse response) throws Exception {  
.......   
// 这是Spring定义的执行链,里面放了映射关系对应的handler和定义的相关拦截器。   
HandlerExecutionChain mappedHandler = null;   

......   
try {   
//我们熟悉的ModelAndView在这里出现了。   
ModelAndView mv = null;   
try {   
processedRequest = checkMultipart(request);   

//这里更具request中的参数和映射关系定义决定使用的handler   
mappedHandler = getHandler(processedRequest, false);   

......   
//这里是handler的调用过程,类似于Command模式中的execute.   
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());   
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());   

.......   
//这里将模型数据通过视图进行展现   
if (mv != null && !mv.wasCleared()) {   
render(mv, processedRequest, response);   
}   
........   
}  

这样具体的MVC模型的实现就由bean配置文件里定义好的view resolver,handler这些类来实现用户代码的功能。 
总结上面的过程,我们看到在web容器中,ServletContext可以持有一系列的web上下文,而在整个web上下文中存在一个根上下文来作为其它 Servlet上下文的父上下文。这个根上下文是由ContextLoader载入并进行初始化的,对于我们的web应用, DispatcherSerlvet载入并初始化自己的上下文,这个上下文的父上下文是根上下文,并且我们也能从ServletContext中根据 Servlet的名字来检索到我们需要的对应于这个Servlet的上下文,但是根上下文的名字是由Spring唯一确定的。这个 DispactcherServlet建立的上下文就是我们开发Spring MVC应用的IOC容器。 
具体的web请求处理在上下文体系建立完成以后由DispactcherServlet来完成,上面对MVC的运作做了一个大致的描述,下面我们会具体就SpringMVC的框架实现作一个详细的分析。   
 

以上是关于Servlet初始化相关问题,以及Spring容器初始化的主要内容,如果未能解决你的问题,请参考以下文章

在使用 SpringMVC 时,Spring 容器是如何与 Servlet 容器进行交互的?

Spring实战Spring容器初始化完成后执行初始化数据方法

Spring实战Spring容器初始化完成后执行初始化数据方法

HTTP Servlet的生命周期

springboot和servlet和tomcat

嵌入式 servlet 容器不处理 Spring Boot 中的 META-INF/资源