Spring web


  

8 Spring web

8.1 Servlet 3.0

  Servlet2.0的时候,我们定义的ServletFilterListener、包括SpringMVCDispatcherServlet都需要在web.xml中进行注册,但是从Servlet3.0之后,也可以使用注解快速搭建web应用,需要注意的一点时Servlet30是需TomCat 7以上的,当然Servlet3.0JSR315规范Jsr网站

  开始搭建Servlet 3.0环境

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8" %>
<!
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title></title>
</head>
    <body>
        <h2>Hello World!</h2>
        <a href="/hello">hello</a>
    </body>
</html>
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello");
    }
}

JSR规范:在容器启动的时候,会扫描当前应用每一个jar包里边/META-INF/services/javax.servlet.ServletContainerInitializer
文件中所指定的实现类、启动并运行这个实现类的方法

//容器启动的时候会把HandlesTypes指定的这个类型下面的子类(实现类、子接口)传递过来
@HandlesTypes(value = {HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
    /**
     *
     * @param set  感兴趣类型的所有子类型
     * @param servletContext 用来代表当前应用的ServletContext,每个应用一个
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型");
        for (Class<?> c :set){
            System.out.println(c);
        }
        System.out.println("应用启动");
    }
}
public interface HelloService {
}
public interface HelloServiceExt extends HelloService {
}
public interface HelloServiceImpl extends HelloService {
}

在这里插入图片描述

8.2 Servlet3.0注册组件

由于Servlet3.0使用全注解的方式,没有了web.xml的配置文件,所以我们使用自定义的ServletContainerInitializer在Tomcat启动的时候来添加组件

//容器启动的时候会把HandlesTypes指定的这个类型下面的子类(实现类、子接口)传递过来
@HandlesTypes(value = {HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer {
    /**
     *
     * @param set  感兴趣类型的所有子类型
     * @param servletContext 用来代表当前应用的ServletContext,每个应用一个
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型");
        for (Class<?> c :set){
            System.out.println(c);
        }
        System.out.println("应用启动");


        ServletRegistration.Dynamic userServlet = servletContext.addServlet("userServlet", new UserServlet());
        userServlet.addMapping("/user");

        servletContext.addListener(UserListner.class);

        FilterRegistration.Dynamic userFilter = servletContext.addFilter("userFilter", UserFilter.class);
        userFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*");
    }
}
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("tomcat......");
    }
}
public class UserFilter  implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("UserFilter ....Filter");
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}
public class UserListner implements ServletContextListener {
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed....");
    }

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        //这块也可以添加组件
        ServletContext servletContext = sce.getServletContext();
        servletContext.addFilter("",UserFilter.class);

        System.out.println("contextInitialized");
    }
}

8.3 Servlet3.0整合SpringMVC

Spring整合SpringMVC官方给了两种方式,可以参考Spring的官方文档参考手册

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}
<web-app>
	
	<!--注册一个监听器在Web容器Tomcat启动的时候加载配置文件,启动Spring容器,称为父容器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

	<!--注册中央调度器,来加载SpringMvc的容器,称为子容器-->
    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

以上两种是Spring官方文档给的两种方式,这里不适用以上两种方式,首先加入Spring-webmvc的依赖

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.3.9.RELEASE</version>
        </dependency>

    </dependencies>

可以看出,在加载了Spring-webmvc的依赖之后,会自动加载一下依赖
在这里插入图片描述
标红的这个文件中配置了

org.springframework.web.SpringServletContainerInitializer

1、web容器在启动的时候会扫描每个jar包 下的META-INF/services/java.servlet.ServletContainerInitializer
2、加载这个文件的启动类
也就是说web容器中会自动扫描这个类,执行web容器的初始化工作

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {


	@Override
	public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				//如果感兴趣的类不是接口、不是抽象的、并且是WebApplicationInitializer旗下的
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						//则为这个类创建实例
						initializers.add((WebApplicationInitializer) waiClass.newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

3、所以Spring容器一启动会加载@HandlesTypes(WebApplicationInitializer.class)指定的WebApplicationInitializer接口下的所有组件
4、并为这些组件创建对象
在这里插入图片描述
我们挨个分析WebApplicationInitializer 下的三个实现类
1、AbstractContextLoaderInitializer

public abstract class implements WebApplicationInitializer {

	/** Logger available to subclasses */
	protected final Log logger = LogFactory.getLog(getClass());


	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}

	protected void registerContextLoaderListener(ServletContext servletContext) {
		//创建一个根容器
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
			
			//创建一个容器监听器
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			listener.setContextInitializers(getRootApplicationContextInitializers());
			servletContext.addListener(listener);
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}

	protected abstract WebApplicationContext createRootApplicationContext();

	protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
		return null;
	}

}

2、AbstractDispatcherServletInitializer

protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return empty or null");
		
		//创建一个web的ioc容器
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext,
				"createServletApplicationContext() did not return an application " +
				"context for servlet [" + servletName + "]");
		//创建了一个Servlet
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
		
		//将创建的Servlet添加到Tomcat容器中
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		Assert.notNull(registration,
				"Failed to register servlet with name '" + servletName + "'." +
				"Check if there is another servlet registered under the same name.");

		registration.setLoadOnStartup(1);
		//子类去实现
		registration.addMapping(getServletMappings());
		registration.setAsyncSupported(isAsyncSupported());

		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}

		customizeRegistration(registration);
	}

3、AbstractAnnotationConfigDispatcherServletInitializer
注解方式配置的DispatcherServlet初始化器

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
		extends AbstractDispatcherServletInitializer {

	//传入一个主配置类创建一个根容器
	@Override
	protected WebApplicationContext createRootApplicationContext() {
		//获取配置类信息,是抽象的可以自定义
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
			rootAppContext.register(configClasses);
			return rootAppContext;
		}
		else {
			return null;
		}
	}
	@Override
	protected WebApplicationContext createServletApplicationContext() {
		//创建web的ioc容器:
		AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
		//获取配置类是抽象的
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			servletAppContext.register(configClasses);
		}
		return servletAppContext;
	}

总结:以注解方式启动SpringMVC容器:继承AbstractAnnotationConfigDispatcherServletInitializer,实现抽象方法指定DispatcherServlet的配置信息,Spring官方也是这样描述容器的关系的

在这里插入图片描述

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    /**
     * 获取根容器的配置类:(Spring的配置文件)父容器
     * @return
     */
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{RootConfig.class};
    }

    /**
     * 获取web容器的配置类(SpringMVC配置文件)子容器
     * @return
     */
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{AppConfig.class};
    }

    /**
     * 获取DispatcherServlet的映射信息
     * 注意: "/" 是拦截所有请求(包括静态资源(xx.js,xx.png),但是不包括*.jsp);
     *      "/*" 也是拦截所有请求,*jsp也拦截,当然是找不到处理方法的,jsp是由tomcat的jsp引擎解析的
     * @return
     */
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
//Spring容器不扫描Controller父容器
@ComponentScan(value = "com.chengzi",excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
})
public class RootConfig {
}
//SpringMVC只扫描Controller:子容器
@ComponentScan(value = "com.chengzi",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
public class AppConfig {
}
@Controller
public class HelloController {

    @Autowired
    HelloSevice helloService;

    @ResponseBody
    @RequestMapping("/hello")
    public String hello(){
        String tom = helloService.hello("tom");
        return tom;
    }
}
@Service
public class HelloSevice {

    public String hello(String name) {
        return "hi"+ name;
    }
}

8.4 SpringMVC定制

没有了xml配置文件,如何配置视图解析器、静态资源映射、拦截器。。。等?,可以参见Spring的官方文档
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
开启SpringMVC定制功能

//SpringMVC只扫描Controller:子容器
@ComponentScan(value = "com.chengzi",includeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION,classes = {Controller.class})
},useDefaultFilters = false)
@EnableWebMvc
/**
 * <mvc:annotation-driven/> 相当于开启了注解驱动
 */
public class AppConfig extends WebMvcConfigurerAdapter {

    /**
     * 视图解析器
     * @param registry
     *
     */
    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        //默认所有的页面都从 /WEB/INF/ **.jsp
        //registry.jsp();
        registry.jsp("/WEB-INF/views/",".jsp");
    }
    /**
     * 开启tomcat对静态资源的访问,否则图片等静态资源会被当做请求处理
     */
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
    }
}
public class MyFirstInterceptor implements HandlerInterceptor {

    /**
     *目标方法执行之前执行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle....");
        return true;
    }

    /**
     * 目标方法执行正确之后执行
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle.....");
        Class<?> aClass = handler.getClass();
        System.out.println(aClass);
    }

    /**
     * 页面响应之后执行
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

8.5 Servlet3.0异步请求处理

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

@WebServlet(value = "/hello")
public class HelloServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(Thread.currentThread() + "start.....");
        try {
            sayHello();
        } catch (Exception e) {
            e.printStackTrace();
        }
        resp.getWriter().write("hello");
        System.out.println(Thread.currentThread() + "end.....");
    }
    public void sayHello() throws Exception{
        System.out.println(Thread.currentThread() + "processing.....");
        Thread.sleep(3000);
    }
}

控制台打印结果:

Thread[http-nio-8080-exec-3,5,main]start.....
Thread[http-nio-8080-exec-3,5,main]processing.....
Thread[http-nio-8080-exec-3,5,main]end.....
@WebServlet(value = "/async",asyncSupported = true)
public class HelloAsyncServlet extends HttpServlet {

    @Override
    protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
        //1.支持异步处理asyncSupported = true
        //2.开启异步处理
        System.out.println("主线程start..:" + Thread.currentThread() +":::" + System.currentTimeMillis() );
        final AsyncContext startAsync = req.startAsync();

        //3.业务逻辑进行异步处理
        startAsync.start(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("副线程start..:" + Thread.currentThread()+":::" + System.currentTimeMillis() );
                    sayHello();
                    startAsync.complete();
                    //获取到异步上下文
                    AsyncContext asyncContext = req.getAsyncContext();
                    ServletResponse response = asyncContext.getResponse();
                    //4.获取响应
                    response.getWriter().write("111");

                } catch (Exception e) {
                    System.out.println("副线程end..:" + Thread.currentThread()+":::" + System.currentTimeMillis() );
                    e.printStackTrace();
                }
            }
        });
        System.out.println("主线程end..:" + Thread.currentThread()+":::" + System.currentTimeMillis() );
    }
    public void sayHello() throws Exception{
        System.out.println(Thread.currentThread() + "processing....."+":::" + System.currentTimeMillis() );
        Thread.sleep(3000);
    }
}
主线程start..:Thread[http-nio-8080-exec-4,5,main]:::1569572865431
主线程end..  :Thread[http-nio-8080-exec-4,5,main]:::1569572865435
副线程start..:Thread[http-nio-8080-exec-5,5,main]:::1569572865436
Thread[http-nio-8080-exec-5,5,main]processing...:::1569572865436
副线程end..  :Thread[http-nio-8080-exec-5,5,main]:::1569572868437

在这里插入图片描述
在这里插入图片描述

8.6 SpringMVC异步请求返回

在这里插入图片描述

@Controller
public class AsyncController {

    @RequestMapping("/async01")
    @ResponseBody
    public Callable<String> async01(){
        System.out.println("主线程start..."+Thread.currentThread() + "=>" + System.currentTimeMillis());

        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println("副线程start..."+Thread.currentThread() + "=>" + System.currentTimeMillis());
                Thread.sleep(10000);
                System.out.println("副线程end....."+Thread.currentThread() + "=>" + System.currentTimeMillis());
                return "Callable....";
            }
        };
        System.out.println("主线程end....."+Thread.currentThread() + "=>" + System.currentTimeMillis());
        return callable;
    }
}

控制台打印结果:

preHandle....
主线程start...Thread[http-nio-8080-exec-6,5,main]=>1569575843000
主线程end.....Thread[http-nio-8080-exec-6,5,main]=>1569575843001
副线程start...Thread[MvcAsync1,5,main]=>1569575843010
副线程end.....Thread[MvcAsync1,5,main]=>1569575853010
preHandle....
postHandle.....

1、控制器返回Callable
2、Spring异步处理:将Callable提交到一个TaskExecutor,任务执行器,使用一个隔离的线程进行执行
3、DispatcherServlet和所有的Filter退出web容器的线程,但是 response保持打开的状态
4、Callable返回结果,SpringMVC将所有请求重新派发给容器,恢复之前的处理
5、根据Callable返回结果,SpringMVC继续进行视图渲染等(从收请求->渲染视图)

preHandle执行了两次,异步的情况下拦截器并不能拦截到业务逻辑的处理,异步拦截器:
(1)原生API的AsyncListener
(2)SpringMVC的情况下实现AsyncHandlerInterceptor

8.7 SpringMVC异步请求

在这里插入图片描述

@Controller
public class AsyncController {

    @GetMapping("/quotes")
    @ResponseBody
    public DeferredResult<String> quotes() {
        DeferredResult<String> deferredResult = new DeferredResult<String>((long)3000,"创建订单超时");
        DeferredResultQueue.save(deferredResult);
        return deferredResult;
    }


    @GetMapping("/create")
    @ResponseBody
    public String create() {
        //创建订单
        String s = UUID.randomUUID().toString();
        DeferredResult deferredResult = DeferredResultQueue.get();
        deferredResult.setResult(s);
        return "success";
    }
 }
public class DeferredResultQueue {
    private static Queue<DeferredResult<String>> queue = new ConcurrentLinkedQueue<>();

    public static void save(DeferredResult<String> deferredResult){
        queue.add(deferredResult);

    }
    public static DeferredResult get(){
         return  queue.poll();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值