Hello Coder


  • 首页

  • 归档

  • 标签

  • 搜索
close

Servlet 3 中的异步处理

发表于 2017-05-09

转载

在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。即便是像Spring、Struts这样的高层框架也脱离不了这样的桎梏,因为他们都是建立在Servlet之上的。为了解决这样的问题,Servlet 3.0引入了异步处理,然后在Servlet 3.1中又引入了非阻塞IO来进一步增强异步处理的性能。

在Servlet 3.0中,我们可以从HttpServletRequest对象中获得一个AsyncContext对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另一个线程处理的过程便构成了Servlet 3.0中的异步处理。

演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package davenkin.servlet;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/syncHello")
public class SyncHelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
new LongRunningProcess().run();
response.getWriter().write("Hello World!");
}
}

为了模拟长时处理过程,创建了一个LongRunningProcess类,其run()方法将随机地等待2秒之内的一个时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package davenkin.servlet;
import java.util.concurrent.ThreadLocalRandom;
/**
* Created by yteng on 3/14/17.
*/
public class LongRunningProcess {
public void run() {
try {
int millis = ThreadLocalRandom.current().nextInt(2000);
String currentThread = Thread.currentThread().getName();
System.out.println(currentThread + " sleep for " + millis + " milliseconds.");
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

此时的SyncHelloServlet将顺序地先执行LongRunningProcess的run()方法,然后将将HelloWorld返回给客户端,这是一个典型的同步过程。

阅读全文 »

Spring MVC 处理响应返回值

发表于 2017-05-08

HandlerMethodReturnValueHandler是RequestMappingHandlerAdapter用来处理当含有@RequestMapping的方法调度完成后,后面要进行的事情。
首先是HandlerMethodReturnValueHandler的自定义注册:

1
2
3
4
5
<mvc:annotation-driven>
<mvc:return-value-handlers>
<bean class="org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler"></bean>
</mvc:return-value-handlers>
</mvc:annotation-driven>

在启动AnnotationDrivenBeanDefinitionParser来解析mvc:annotation-driven标签的过程中,会注册我们所配置的HandlerMethodReturnValueHandler,如下:

1
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);
1
2
3
4
5
6
7
private ManagedList<?> getReturnValueHandlers(Element element, ParserContext parserContext) {
Element handlersElement = DomUtils.getChildElementByTagName(element, "return-value-handlers");
if (handlersElement != null) {
return extractBeanSubElements(handlersElement, parserContext);
}
return null;
}

然后将会这些自定义的HandlerMethodReturnValueHandler设置到RequestMappingHandlerAdapter的customReturnValueHandlers属性中

RequestMappingHandlerAdapter的两个重要属性:

  • customReturnValueHandlers:存放我们自定义的HandlerMethodReturnValueHandler
  • returnValueHandlers:存放最终所有的HandlerMethodReturnValueHandler
1
2
3
4
5
6
7
8
9
10
11
12
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
private List<HandlerMethodArgumentResolver> customArgumentResolvers;
private HandlerMethodArgumentResolverComposite argumentResolvers;
private HandlerMethodArgumentResolverComposite initBinderArgumentResolvers;
//...
private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
//...
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

returnValueHandlers的属性类型为HandlerMethodReturnValueHandlerComposite,里面也有一个list集合,来存放所有的HandlerMethodReturnValueHandler。

阅读全文 »

Spring MVC 应用上下文

发表于 2017-05-08

先看上文:Spring MVC ApplicationContext

ServletContext

javaee标准规定了,servlet容器需要在应用项目启动时,给应用项目初始化一个ServletContext作为公共环境容器存放公共信息。ServletContext中的信息都是由容器提供的。

例子

通过自定义contextListener获取web.xml中配置的参数

  1. 容器启动时,找到配置文件中的context-param作为键值对放到ServletContext中
  2. 然后找到listener,容器调用它的contextInitialized(ServletContextEvent event)方法,执行其中的操作

例如:在web.xml中配置

1
2
3
4
5
6
7
<context-param>
<param-name>key</param-name>
<param-value>hello</param-value>
</context-param>
<listener>
<listener-class>com.zsr.contextlistener.listener.ContextListenerTest</listener-class>
</listener>

配置好之后,在该类中获取对应的参数信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.zsr.contextlistener.listener;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class ContextListenerTest implements ServletContextListener {
public void contextDestroyed(ServletContextEvent event) {
System.out.println("*************destroy ContextListener*************");
}
@SuppressWarnings("unused")
public void contextInitialized(ServletContextEvent event) {
System.out.println("*************init ContextListener*************");
ServletContext servletContext = event.getServletContext();
System.out.println("key:"+servletContext.getInitParameter("key"));
}
}

执行流程

  web.xml在<context-param></context-param>标签中声明应用范围内的初始化参数

  1. 启动一个WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml.读两个节点: <listener></listener>和 <context-param></context-param>
  2. 紧接着,容器创建一个ServletContext(上下文)。在该应用内全局共享。
  3. 容器将<context-param></context-param>转化为键值对,并交给ServletContext.
  4. 容器创建<listener></listener>中的类实例,即创建监听.该监听器必须实现自ServletContextListener接口
  5. 在监听中会有contextInitialized(ServletContextEvent event)初始化方法
  6. 得到这个context-param的值之后,你就可以做一些操作了.注意,这个时候你的WEB项目还没有完全启动完成.这个动作会比所有的Servlet都要早.换句话说,这个时候,对<context-param>中的键值做的操作,将在你的WEB项目完全启动之前被执行.

web.xml中可以定义两种参数:

  • 全局参数(ServletContext),通过<context-param>声明
1
2
3
4
<context-param>
<param-name>key</param-name>
<param-value>hello</param-value>
</context-param>

参数在servlet里面可以通过getServletContext().getInitParameter("key")得到

  • servlet参数,通过在servlet中声明   

    1
    2
    3
    4
    <init-param>
    <param-name>param1</param-name>
    <param-value>avalible in servlet init()</param-value>
    </init-param>

参数只能在servlet的init()方法中通过this.getInitParameter("param1")取得

Spring上下文容器配置

 Spring提供了实现ServletContextListener接口的上下文初始化监听器:org.springframework.web.context.ContextLoaderListener

Spring为我们提供的IOC容器,需要我们指定容器的配置文件,然后由该监听器初始化并创建该容器。要求你指定配置文件的地址及文件名称,一定要使用:contextConfigLocation作为参数名称。

1
2
3
4
5
6
7
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:web-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

该监听器,默认读取/WEB-INF/下的web-context.xml文件。但是通过context-param指定配置文件路径后,便会去你指定的路径下读取对应的配置文件,并进行初始化。

执行流程

ServletContext是由Servlet容器初始化的,那Spring的ContextLoaderListener又做了什么初始化呢?

  1. servlet容器启动,为应用创建一个“全局上下文环境”:ServletContext
  2. 容器调用web.xml中配置的contextLoaderListener,初始化WebApplicationContext上下文环境(即IOC容器),加载context-param指定的配置文件信息到IOC容器中。WebApplicationContext在ServletContext中以键值对的形式保存
  3. 容器初始化web.xml中配置的servlet,为其初始化自己的上下文信息servletContext,并加载其设置的配置信息到该上下文中。将WebApplicationContext设置为它的父容器。
  4. 此后的所有servlet的初始化都按照3步中方式创建,初始化自己的上下文环境,将WebApplicationContext设置为自己的父上下文环境。

    img

​ 对于作用范围而言,在DispatcherServlet中可以引用由ContextLoaderListener所创建的ApplicationContext中的内容,而反过来不行。

​ 当Spring在执行ApplicationContext的getBean(BeanName)时,如果在自己context中找不到对应的bean,则会在父ApplicationContext中去找。这也解释了为什么我们可以在DispatcherServlet中获取到由ContextLoaderListener对应的ApplicationContext中的bean。

参考

Spring-MVC理解之一:应用上下文webApplicationContext

init-method、@PostConstruct、afterPropertiesSet孰先孰后

发表于 2017-05-08

Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:

  • 通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
  • 通过<bean> 元素的init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;
  • 在指定方法上加上@PostConstruct/@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用。

这三种方式是完全等同的吗,孰先孰后?

演示

编写一个简单的测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class InitSequenceBean implements InitializingBean {
public InitSequenceBean() {
System.out.println("InitSequenceBean: constructor");
}
@PostConstruct
public void postConstruct() {
System.out.println("InitSequenceBean: postConstruct");
}
public void initMethod() {
System.out.println("InitSequenceBean: init-method");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitSequenceBean: afterPropertiesSet");
}
}

并且在配置文件中添加如下Bean定义:

1
<bean class="InitSequenceBean" init-method="initMethod"></bean>

启动Spring容器,观察输出结果,就可知道三者的先后顺序了:

1
2
3
4
InitSequenceBean: constructor
InitSequenceBean: postConstruct
InitSequenceBean: afterPropertiesSet
InitSequenceBean: init-method

通过上述输出结果,三者的先后顺序也就一目了然了:

Constructor > @PostConstruct > InitializingBean > init-method

PostConstruct为何率先于InitializingBean执行呢?

CommonAnnotationBeanPostProcessor

通过Debug并查看调用栈,发现了这个类org.springframework.context.annotation.CommonAnnotationBeanPostProcessor,从命名上,我们就可以得到某些信息—这是一个BeanPostProcessor。BeanPostProcessor的postProcessBeforeInitialization是在Bean生命周期中afterPropertiesSet和init-method之前执被调用的。

CommonAnnotationBeanPostProcessor这个类,继承自InitDestroyAnnotationBeanPostProcessor。InitDestroyAnnotationBeanPostProcessor顾名思义,就是在Bean初始化和销毁的时候所作的一个前置/后置处理器。

查看InitDestroyAnnotationBeanPostProcessor类下的postProcessBeforeInitialization方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
LifecycleMetadata metadata = findLifecycleMetadata(bean.getClass());
try {
metadata.invokeInitMethods(bean, beanName);
}
catch (InvocationTargetException ex) {
throw new BeanCreationException(beanName, "Invocation of init method failed", ex.getTargetException());
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Couldn't invoke init method", ex);
}
return bean;
}

查看findLifecycleMetadata方法,继而我们跟踪到buildLifecycleMetadata这个方法体中,看下buildLifecycleMetadata这个方法体的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private LifecycleMetadata buildLifecycleMetadata(final Class clazz) {
final LifecycleMetadata newMetadata = new LifecycleMetadata();
final boolean debug = logger.isDebugEnabled();
ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
public void doWith(Method method) {
if (initAnnotationType != null) {
if (method.getAnnotation(initAnnotationType) != null) {
newMetadata.addInitMethod(method);
if (debug) {
logger.debug("Found init method on class [" + clazz.getName() + "]: " + method);
}
}
}
if (destroyAnnotationType != null) {
if (method.getAnnotation(destroyAnnotationType) != null) {
newMetadata.addDestroyMethod(method);
if (debug) {
logger.debug("Found destroy method on class [" + clazz.getName() + "]: " + method);
}
}
}
}
});
return newMetadata;
}

在这里会去判断某方法有没有被initAnnotationType/destroyAnnotationType注释,如果有,则添加到init/destroy队列中,后续一一执行。

initAnnotationType/destroyAnnotationType注释是什么?我们在CommonAnnotationBeanPostProcessor的构造函数中看到下面这段代码:

1
2
3
4
5
6
public CommonAnnotationBeanPostProcessor() {
setOrder(Ordered.LOWEST_PRECEDENCE - 3);
setInitAnnotationType(PostConstruct.class);
setDestroyAnnotationType(PreDestroy.class);
ignoreResourceType("javax.xml.ws.WebServiceContext");
}

一言以蔽之,@PostConstruct注解后的方法在BeanPostProcessor前置处理器中就被执行了,所以当然要先于InitializingBean和init-method执行了。

参考:

源码解析:init-method、@PostConstruct、afterPropertiesSet孰先孰后

Spring Bean 生命周期

发表于 2017-05-05

Spring Bean 生命周期


BeanFactory和ApplicationContext是Spring两种很重要的容器,前者提供了最基本的依赖注入的支持,而后者在继承前者的基础进行了功能的拓展,例如增加了事件传播,资源访问和国际化的消息访问等功能。

概括来说主要有四个阶段:实例化,初始化,使用,销毁。

Bean 作用范围

常用的Bean作用范围:singleton和prototype

  • singleton

在默认情况下,Spring的ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存于容器中.虽然启动时会花费一些时间,但带来两个好处:首先对Bean提前的实例化操作会及早发现一些潜在的配置问题.其次Bean以缓存的方式保存,当运行时使用到该Bean时就无须再实例化了,加快了运行效率.如果用户不希望在容器启动时提前实例化singleton的Bean,可以通过lazy-init属性进行控制.

  • prototype

在默认情况下,Spring容器在启动时不实例化prototype的Bean.此外,Spring容器将prototype的Bean交给调用者后,就不再管理它的生命周期.

ApplicationContext Bean生命周期:

Spring Bean的完整生命周期从创建Spring容器开始,直到最终Spring容器销毁Bean,这其中包含了一系列关键点。

流程图

img

若容器注册了以上各种接口,程序那么将会按照以上的流程进行。下面将仔细讲解各接口作用。

阅读全文 »
1…131415…31
David

David

Develop Notes

155 日志
37 标签
GitHub Weibo
© 2016 - 2020 David
由 Hexo 强力驱动
主题 - NexT.Pisces