- 浏览: 468368 次
- 性别:
- 来自: 杭州
文章分类
- 全部博客 (146)
- Maven (3)
- Quartz (10)
- Hessian (3)
- JDK (42)
- eclipse (4)
- 设计思想 (6)
- XML (8)
- JavaMail (1)
- Spring (11)
- mina (1)
- HsqlDb (1)
- Cache (2)
- Tool (6)
- 心情 (5)
- JQuery (0)
- Hadoop (5)
- Hbase (3)
- 自动构建 (7)
- JNDI (0)
- 代码赏析 (5)
- Oracle (1)
- Excel (4)
- Effective Java (5)
- JAXB (4)
- fdafasdf (1)
- ccc (0)
- web (3)
- concurrent (1)
- CVS (1)
- eclipse plugin (2)
- Apache (10)
最新评论
-
chxiaowu:
nice!
Quartz实现固定执行次数 -
zxjlwt:
学习了。http://surenpi.com
自定义ClassLoader -
kadlly:
public static final Logger log ...
Hessian 权限认证 -
spring_springmvc:
java程序语言学习教程 地址http://www.zuida ...
Java-Final -
liushuiwuyan:
[img][/img]
设计模式-单例
以前只知道继承HttpServlet,然后Servlet容器就会自动调用doGet/doPost方法来提供service服务, 却不知道为什么,知道HttpServlet有生命周期init==>service==>destroy,生命周期由Servlet容器管理,却不知道怎么管理的,闲来无事就选择Tomcat作为研究对象。
其实以我做Java十几年的经验, 看源码似乎已成习惯:
1. Servlet init.
其实并不是所有的Servlet都是Servlet容器初始化就加载的,而是Servlet被第一次调用的时候加载.
web 2.4 configration
以上代码是配置Servlet load on startup, 可能有些人会对这里的数字1感兴趣,我配置成2行不行.
org.apache.catalina.core.StandardContext, 在容器启动的时候会调用startInternal方法:
我想不需要我解释,大家也知道这个数字(1)的意义了吧,服务器内部也只是用treeMap来排序罢了,但是不管你配置了多少,只要是数字并且大于0,那么都回在服务器启动的时候初始化.
其实这种例子可是很多的哦,servlet继承struts1.x的时候就是这么配置的:
struts中的ActionServlet必须在容器初始化的时候实例化,这样就能在Servlet提供service服务前执行.
2. Servlet service.
经过以上实例化的Servlet就能提供service服务了.
org.apache.catalina.core.StandardWrapperValve
如果Servlet没有在容器初始化的时候实例化,那么会执行初始化。使用完servlet之后把当前Servlet实例放回到对象池中.
3. Servlet destroy.
当容器停止,或Servlet到期时,容器会调用servlet.destroy().
这就是HttpServlet的生命周期,init方法可以放一些资源的初始化,destroy方法可以放一些资源的释放.
其实以我做Java十几年的经验, 看源码似乎已成习惯:
1. Servlet init.
其实并不是所有的Servlet都是Servlet容器初始化就加载的,而是Servlet被第一次调用的时候加载.
web 2.4 configration
<servlet> <display-name>InitServlet</display-name> <servlet-name>InitServlet</servlet-name> <servlet-class>com.InitServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet>
以上代码是配置Servlet load on startup, 可能有些人会对这里的数字1感兴趣,我配置成2行不行.
org.apache.catalina.core.StandardContext, 在容器启动的时候会调用startInternal方法:
// Load and initialize all "load on startup" servlets if (ok) { if (!loadOnStartup(findChildren())){ log.error("Error loadOnStartup"); ok = false; } }
/** * Load and initialize all servlets marked "load on startup" in the * web application deployment descriptor. * * @param children Array of wrappers for all currently defined * servlets (including those not declared load on startup) */ public boolean loadOnStartup(Container children[]) { // Collect "load on startup" servlets that need to be initialized TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>(); for (int i = 0; i < children.length; i++) { Wrapper wrapper = (Wrapper) children[i]; int loadOnStartup = wrapper.getLoadOnStartup(); if (loadOnStartup < 0) continue; Integer key = Integer.valueOf(loadOnStartup); ArrayList<Wrapper> list = map.get(key); if (list == null) { list = new ArrayList<>(); map.put(key, list); } list.add(wrapper); } // Load the collected "load on startup" servlets for (ArrayList<Wrapper> list : map.values()) { for (Wrapper wrapper : list) { try { wrapper.load(); } catch (ServletException e) { getLogger().error(sm.getString("standardWrapper.loadException", getName()), StandardWrapper.getRootCause(e)); // NOTE: load errors (including a servlet that throws // UnavailableException from tht init() method) are NOT // fatal to application startup, excepted if failDeploymentIfServletLoadedOnStartupFails is specified if(getComputedFailCtxIfServletStartFails()) { return false; } } } } return true; }
/** * Load and initialize an instance of this servlet, if there is not already * at least one initialized instance. This can be used, for example, to * load servlets that are marked in the deployment descriptor to be loaded * at server startup time. */ public synchronized Servlet loadServlet() throws ServletException { if (unloading) { throw new ServletException( sm.getString("standardWrapper.unloading", getName())); } // Nothing to do if we already have an instance or an instance pool if (!singleThreadModel && (instance != null)) return instance; PrintStream out = System.out; if (swallowOutput) { SystemLogHandler.startCapture(); } Servlet servlet; try { long t1=System.currentTimeMillis(); // Complain if no servlet class has been specified if (servletClass == null) { unavailable(null); throw new ServletException (sm.getString("standardWrapper.notClass", getName())); } InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager(); try { servlet = (Servlet) instanceManager.newInstance(servletClass); } catch (ClassCastException e) { unavailable(null); // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.notServlet", servletClass), e); } catch (Throwable e) { e = ExceptionUtils.unwrapInvocationTargetException(e); ExceptionUtils.handleThrowable(e); unavailable(null); // Added extra log statement for Bugzilla 36630: // http://issues.apache.org/bugzilla/show_bug.cgi?id=36630 if(log.isDebugEnabled()) { log.debug(sm.getString("standardWrapper.instantiate", servletClass), e); } // Restore the context ClassLoader throw new ServletException (sm.getString("standardWrapper.instantiate", servletClass), e); } if (multipartConfigElement == null) { MultipartConfig annotation = servlet.getClass().getAnnotation(MultipartConfig.class); if (annotation != null) { multipartConfigElement = new MultipartConfigElement(annotation); } } processServletSecurityAnnotation(servlet.getClass()); // Special handling for ContainerServlet instances if ((servlet instanceof ContainerServlet) && (isContainerProvidedServlet(servletClass) || ((Context) getParent()).getPrivileged() )) { ((ContainerServlet) servlet).setWrapper(this); } classLoadTime=(int) (System.currentTimeMillis() -t1); if (servlet instanceof SingleThreadModel) { if (instancePool == null) { instancePool = new Stack<>(); } singleThreadModel = true; } initServlet(servlet); fireContainerEvent("load", this); loadTime=System.currentTimeMillis() -t1; } finally { if (swallowOutput) { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0) { if (getServletContext() != null) { getServletContext().log(log); } else { out.println(log); } } } } return servlet; }
private synchronized void initServlet(Servlet servlet) throws ServletException { if (instanceInitialized && !singleThreadModel) return; // Call the initialization method of this servlet try { instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT, servlet); if( Globals.IS_SECURITY_ENABLED) { boolean success = false; try { Object[] args = new Object[] { facade }; SecurityUtil.doAsPrivilege("init", servlet, classType, args); success = true; } finally { if (!success) { // destroy() will not be called, thus clear the reference now SecurityUtil.remove(servlet); } } } else { servlet.init(facade); } instanceInitialized = true; instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet); } catch (UnavailableException f) { instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet, f); unavailable(f); throw f; } catch (ServletException f) { instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet, f); // If the servlet wanted to be unavailable it would have // said so, so do not call unavailable(null). throw f; } catch (Throwable f) { ExceptionUtils.handleThrowable(f); getServletContext().log("StandardWrapper.Throwable", f ); instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT, servlet, f); // If the servlet wanted to be unavailable it would have // said so, so do not call unavailable(null). throw new ServletException (sm.getString("standardWrapper.initException", getName()), f); } }
我想不需要我解释,大家也知道这个数字(1)的意义了吧,服务器内部也只是用treeMap来排序罢了,但是不管你配置了多少,只要是数字并且大于0,那么都回在服务器启动的时候初始化.
其实这种例子可是很多的哦,servlet继承struts1.x的时候就是这么配置的:
servlet> <servlet-name>action</servlet-name> <servlet-class>org.apache.struts.action.ActionServlet</servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>3</param-value> </init-param> <init-param> <param-name>detail</param-name> <param-value>3</param-value> </init-param> <load-on-startup>0</load-on-startup> </servlet>
struts中的ActionServlet必须在容器初始化的时候实例化,这样就能在Servlet提供service服务前执行.
2. Servlet service.
经过以上实例化的Servlet就能提供service服务了.
org.apache.catalina.core.StandardWrapperValve
servlet = null; Context context = (Context) wrapper.getParent(); // Check for the application being marked unavailable if (!context.getState().isAvailable()) { response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardContext.isUnavailable")); unavailable = true; } // Check for the servlet being marked unavailable if (!unavailable && wrapper.isUnavailable()) { container.getLogger().info(sm.getString("standardWrapper.isUnavailable", wrapper.getName())); long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) { response.setDateHeader("Retry-After", available); response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); } else if (available == Long.MAX_VALUE) { response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("standardWrapper.notFound", wrapper.getName())); } unavailable = true; } // Allocate a servlet instance to process this request try { if (!unavailable) { servlet = wrapper.allocate(); } } catch (UnavailableException e) { container.getLogger().error( sm.getString("standardWrapper.allocateException", wrapper.getName()), e); long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) { response.setDateHeader("Retry-After", available); response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); } else if (available == Long.MAX_VALUE) { response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("standardWrapper.notFound", wrapper.getName())); } } catch (ServletException e) { container.getLogger().error(sm.getString("standardWrapper.allocateException", wrapper.getName()), StandardWrapper.getRootCause(e)); throwable = e; exception(request, response, e); } catch (Throwable e) { ExceptionUtils.handleThrowable(e); container.getLogger().error(sm.getString("standardWrapper.allocateException", wrapper.getName()), e); throwable = e; exception(request, response, e); servlet = null; } // Identify if the request is Comet related now that the servlet has been allocated boolean comet = false; if (servlet instanceof CometProcessor && request.getAttribute( Globals.COMET_SUPPORTED_ATTR) == Boolean.TRUE) { comet = true; request.setComet(true); } MessageBytes requestPathMB = request.getRequestPathMB(); DispatcherType dispatcherType = DispatcherType.REQUEST; if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC; request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,dispatcherType); request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, requestPathMB); // Create the filter chain for this request ApplicationFilterFactory factory = ApplicationFilterFactory.getInstance(); ApplicationFilterChain filterChain = factory.createFilterChain(request, wrapper, servlet); // Reset comet flag value after creating the filter chain request.setComet(false); // Call the filter chain for this request // NOTE: This also calls the servlet's service() method try { if ((servlet != null) && (filterChain != null)) { // Swallow output if needed if (context.getSwallowOutput()) { try { SystemLogHandler.startCapture(); if (request.isAsyncDispatching()) { ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch(); } else if (comet) { filterChain.doFilterEvent(request.getEvent()); request.setComet(true); } else { filterChain.doFilter(request.getRequest(), response.getResponse()); } } finally { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0) { context.getLogger().info(log); } } } else { if (request.isAsyncDispatching()) { ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch(); } else if (comet) { request.setComet(true); filterChain.doFilterEvent(request.getEvent()); } else { filterChain.doFilter (request.getRequest(), response.getResponse()); } } } } catch (ClientAbortException e) { throwable = e; exception(request, response, e); } catch (IOException e) { container.getLogger().error(sm.getString( "standardWrapper.serviceException", wrapper.getName(), context.getName()), e); throwable = e; exception(request, response, e); } catch (UnavailableException e) { container.getLogger().error(sm.getString( "standardWrapper.serviceException", wrapper.getName(), context.getName()), e); // throwable = e; // exception(request, response, e); wrapper.unavailable(e); long available = wrapper.getAvailable(); if ((available > 0L) && (available < Long.MAX_VALUE)) { response.setDateHeader("Retry-After", available); response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, sm.getString("standardWrapper.isUnavailable", wrapper.getName())); } else if (available == Long.MAX_VALUE) { response.sendError(HttpServletResponse.SC_NOT_FOUND, sm.getString("standardWrapper.notFound", wrapper.getName())); } // Do not save exception in 'throwable', because we // do not want to do exception(request, response, e) processing } catch (ServletException e) { Throwable rootCause = StandardWrapper.getRootCause(e); if (!(rootCause instanceof ClientAbortException)) { container.getLogger().error(sm.getString( "standardWrapper.serviceExceptionRoot", wrapper.getName(), context.getName(), e.getMessage()), rootCause); } throwable = e; exception(request, response, e); } catch (Throwable e) { ExceptionUtils.handleThrowable(e); container.getLogger().error(sm.getString( "standardWrapper.serviceException", wrapper.getName(), context.getName()), e); throwable = e; exception(request, response, e); } // Release the filter chain (if any) for this request if (filterChain != null) { if (request.isComet()) { // If this is a Comet request, then the same chain will be used for the // processing of all subsequent events. filterChain.reuse(); } else { filterChain.release(); } } // Deallocate the allocated servlet instance try { if (servlet != null) { wrapper.deallocate(servlet); } } catch (Throwable e) { ExceptionUtils.handleThrowable(e); container.getLogger().error(sm.getString("standardWrapper.deallocateException", wrapper.getName()), e); if (throwable == null) { throwable = e; exception(request, response, e); } } // If this servlet has been marked permanently unavailable, // unload it and release this instance try { if ((servlet != null) && (wrapper.getAvailable() == Long.MAX_VALUE)) { wrapper.unload(); } } catch (Throwable e) { ExceptionUtils.handleThrowable(e); container.getLogger().error(sm.getString("standardWrapper.unloadException", wrapper.getName()), e); if (throwable == null) { throwable = e; exception(request, response, e); } } long t2=System.currentTimeMillis(); long time=t2-t1; processingTime += time; if( time > maxTime) maxTime=time; if( time < minTime) minTime=time; }
如果Servlet没有在容器初始化的时候实例化,那么会执行初始化。使用完servlet之后把当前Servlet实例放回到对象池中.
/** * Return this previously allocated servlet to the pool of available * instances. If this servlet class does not implement SingleThreadModel, * no action is actually required. * * @param servlet The servlet to be returned * * @exception ServletException if a deallocation error occurs */ @Override public void deallocate(Servlet servlet) throws ServletException { // If not SingleThreadModel, no action is required if (!singleThreadModel) { countAllocated.decrementAndGet(); return; } // Unlock and free this instance synchronized (instancePool) { countAllocated.decrementAndGet(); instancePool.push(servlet); instancePool.notify(); } }
3. Servlet destroy.
当容器停止,或Servlet到期时,容器会调用servlet.destroy().
/** * Stop this component and implement the requirements * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}. * * @exception LifecycleException if this component detects a fatal error * that prevents this component from being used */ @Override protected synchronized void stopInternal() throws LifecycleException { setAvailable(Long.MAX_VALUE); // Send j2ee.state.stopping notification if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.state.stopping", this.getObjectName(), sequenceNumber++); broadcaster.sendNotification(notification); } // Shut down our servlet instance (if it has been initialized) try { unload(); } catch (ServletException e) { getServletContext().log(sm.getString ("standardWrapper.unloadException", getName()), e); } // Shut down this component super.stopInternal(); // Send j2ee.state.stoppped notification if (this.getObjectName() != null) { Notification notification = new Notification("j2ee.state.stopped", this.getObjectName(), sequenceNumber++); broadcaster.sendNotification(notification); } // Send j2ee.object.deleted notification Notification notification = new Notification("j2ee.object.deleted", this.getObjectName(), sequenceNumber++); broadcaster.sendNotification(notification); }
/** * Unload all initialized instances of this servlet, after calling the * <code>destroy()</code> method for each instance. This can be used, * for example, prior to shutting down the entire servlet engine, or * prior to reloading all of the classes from the Loader associated with * our Loader's repository. * * @exception ServletException if an exception is thrown by the * destroy() method */ @Override public synchronized void unload() throws ServletException { // Nothing to do if we have never loaded the instance if (!singleThreadModel && (instance == null)) return; unloading = true; // Loaf a while if the current instance is allocated // (possibly more than once if non-STM) if (countAllocated.get() > 0) { int nRetries = 0; long delay = unloadDelay / 20; while ((nRetries < 21) && (countAllocated.get() > 0)) { if ((nRetries % 10) == 0) { log.info(sm.getString("standardWrapper.waiting", countAllocated.toString(), getName())); } try { Thread.sleep(delay); } catch (InterruptedException e) { // Ignore } nRetries++; } } if (instanceInitialized) { PrintStream out = System.out; if (swallowOutput) { SystemLogHandler.startCapture(); } // Call the servlet destroy() method try { instanceSupport.fireInstanceEvent (InstanceEvent.BEFORE_DESTROY_EVENT, instance); if( Globals.IS_SECURITY_ENABLED) { try { SecurityUtil.doAsPrivilege("destroy", instance); } finally { SecurityUtil.remove(instance); } } else { instance.destroy(); } instanceSupport.fireInstanceEvent (InstanceEvent.AFTER_DESTROY_EVENT, instance); // Annotation processing if (!((Context) getParent()).getIgnoreAnnotations()) { ((StandardContext)getParent()).getInstanceManager().destroyInstance(instance); } } catch (Throwable t) { t = ExceptionUtils.unwrapInvocationTargetException(t); ExceptionUtils.handleThrowable(t); instanceSupport.fireInstanceEvent (InstanceEvent.AFTER_DESTROY_EVENT, instance, t); instance = null; instancePool = null; nInstances = 0; fireContainerEvent("unload", this); unloading = false; throw new ServletException (sm.getString("standardWrapper.destroyException", getName()), t); } finally { // Write captured output if (swallowOutput) { String log = SystemLogHandler.stopCapture(); if (log != null && log.length() > 0) { if (getServletContext() != null) { getServletContext().log(log); } else { out.println(log); } } } } } // Deregister the destroyed instance instance = null; if (isJspServlet && jspMonitorON != null ) { Registry.getRegistry(null, null).unregisterComponent(jspMonitorON); } if (singleThreadModel && (instancePool != null)) { try { while (!instancePool.isEmpty()) { Servlet s = instancePool.pop(); if (Globals.IS_SECURITY_ENABLED) { try { SecurityUtil.doAsPrivilege("destroy", s); } finally { SecurityUtil.remove(s); } } else { s.destroy(); } // Annotation processing if (!((Context) getParent()).getIgnoreAnnotations()) { ((StandardContext)getParent()).getInstanceManager().destroyInstance(s); } } } catch (Throwable t) { t = ExceptionUtils.unwrapInvocationTargetException(t); ExceptionUtils.handleThrowable(t); instancePool = null; nInstances = 0; unloading = false; fireContainerEvent("unload", this); throw new ServletException (sm.getString("standardWrapper.destroyException", getName()), t); } instancePool = null; nInstances = 0; } singleThreadModel = false; unloading = false; fireContainerEvent("unload", this); }
这就是HttpServlet的生命周期,init方法可以放一些资源的初始化,destroy方法可以放一些资源的释放.
相关推荐
### Servlet生命周期详解 #### 一、引言 在Java Web开发中,Servlet作为一种重要的技术,被广泛应用于构建动态网页和处理客户端请求。了解Servlet的生命周期对于深入理解和掌握Servlet的工作机制至关重要。本文将...
初始化阶段是整个Servlet生命周期的第一个关键步骤,它标志着Servlet的开始。此阶段主要由以下步骤组成: 1. **Servlet容器加载Servlet类**:Servlet容器负责加载Servlet类,并将.Class文件的数据读入内存。这一...
### Servlet 生命周期演示代码详解 #### 一、Servlet 生命周期概述 在深入分析代码之前,我们先来了解一下 Servlet 的生命周期。Servlet 的生命周期主要包括三个阶段:初始化 (`init` 方法)、请求处理 (`service` ...
### SERVLET生命周期与JSP生命周期比较 #### 一、引言 在现代Web开发中,Servlet和JSP是两种非常重要的技术,它们都属于Java EE平台的一部分,主要用于构建动态Web应用程序。这两种技术各有特点,但又紧密相关,...
**Servlet生命周期** Servlet的生命周期可以分为三个主要阶段:初始化、服务和销毁。 1. **初始化阶段**: - 当Servlet首次被请求或者在web应用启动时,容器(如Tomcat)会加载Servlet类,并调用`init()`方法进行...
### Servlet 生命周期详解 #### 一、概述 Servlet 是 Java Web 开发的核心技术之一,用于处理客户端的 HTTP 请求并返回响应。了解 Servlet 的生命周期对于更好地控制和优化 Web 应用程序至关重要。Servlet 的生命...
#### 三、Servlet生命周期详解 ##### 实例化 - **按需创建**:默认情况下,当第一次HTTP请求到达时,容器会创建Servlet的实例。 - **预加载**:通过在`web.xml`中配置`<load-on-startup>`标签,可以在应用启动时...
Servlet生命周期是Java Web开发中一个关键的概念,它描述了Servlet从创建到销毁的整个过程,这个过程由Servlet容器(如Tomcat)进行管理。Servlet生命周期主要分为三个阶段:初始化阶段、运行阶段和销毁阶段。 1. ...
Servlet生命周期和模板设计模式是Java Web开发中的两个关键概念,它们在构建动态Web应用程序时起着至关重要的作用。 首先,让我们深入理解Servlet的生命周期。Servlet是Java编程语言中的一种接口,用于扩展服务器的...
在Servlet生命周期中,init()方法只会被调用一次,service()方法会在每次用户请求时被调用,destroy()方法只会被调用一次,在Servlet生命周期结束时被调用。Servlet生命周期的正确实现是保证Servlet正确运行的关键。
java servlet生命周期 java servlet生命周期
servlet生命周期详细图解,矢量图。 详细的解释请参考本人博客:http://blog.csdn.net/dwyers/article/details/38435949
关于对Servlet声明周期的图例示意!
Servlet生命周期主要包括三个阶段:加载、初始化和销毁。当Web容器启动或者第一次接收到对Servlet的请求时,Servlet会被加载并实例化。然后,调用`init()`方法进行初始化,这个方法可以用来设置Servlet的初始状态...
Java WEB 篇七 Servlet 生命周期