Spring注解驱动开发之web
前言:现今SpringBoot、SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解、原理,比如@Conditional、@Import、@EnableXXX等。如果掌握这些底层原理、注解,那么我们对这些高层框架就能做到高度定制,使用的游刃有余
一、servlet3.0规范
1、新增的注解支持
在servlet3.0之前的话,我们要添加Servlet、Filter、Listener都需要在web.xml中注册,而在servlet3.0添加了注解支持:
@WebServlet: 用于将一个类声明为 Servlet,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为 Servlet,如:
@WebServlet(urlPatterns = {"/simple"}, asyncSupported = true,
loadOnStartup = -, name = "SimpleServlet", displayName = "ss",
initParams = {@WebInitParam(name = "username", value = "tom")}
)
public class SimpleServlet extends HttpServlet{ … }
@WebFilter: 用于将一个类声明为过滤器,该注解将会在部署时被容器处理,容器将根据具体的属性配置将相应的类部署为过滤器;
@WebListener:该注解用于将类声明为监听器,被 @WebListener 标注的类必须实现对应的监听器接口
@WebInitParam:该注解通常不单独使用,而是配合 @WebServlet 或者 @WebFilter 使用。它的作用是为 Servlet 或者过滤器指定初始化参数,这等价于 web.xml 中 <servlet> 和 <filter> 的 <init-param> 子标签。
2、runtimes pluggability(运行时插件能力)
在使用实现了servlet3.0规范的servlet容器中,Servlet容器启动会扫描当前应用里面每一个jar包的ServletContainerInitializer的实现,前提是ServletContainerInitializer的实现类必须绑定在META-INF/services/javax.servlet.ServletContainerInitializer中,文件的内容就是ServletContainerInitializer实现类的全类名
//容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口等)传递过来;
//传入感兴趣的类型;
@HandlesTypes(value={HelloService.class})
public class MyServletContainerInitializer implements ServletContainerInitializer { /**
* 应用启动的时候,会运行onStartup方法;
*
* Set<Class<?>> arg0:感兴趣的类型的所有子类型;
* ServletContext arg1:代表当前Web应用的ServletContext;一个Web应用一个ServletContext;
*
* 1)、使用ServletContext注册Web组件(Servlet、Filter、Listener)
* 2)、使用编码的方式,在项目启动的时候给ServletContext里面添加组件;
* 必须在项目启动的时候来添加;
* 1)、ServletContainerInitializer得到的ServletContext;
* 2)、ServletContextListener得到的ServletContext;
*/
@Override
public void onStartup(Set<Class<?>> arg0, ServletContext sc) throws ServletException {
// TODO Auto-generated method stub
System.out.println("感兴趣的类型:");
for (Class<?> claz : arg0) {
System.out.println(claz);
} //注册组件 ServletRegistration
ServletRegistration.Dynamic servlet = sc.addServlet("userServlet", new UserServlet());
//配置servlet的映射信息
servlet.addMapping("/user"); //注册Listener
sc.addListener(UserListener.class); //注册Filter FilterRegistration
FilterRegistration.Dynamic filter = sc.addFilter("userFilter", UserFilter.class);
//配置Filter的映射信息
filter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*"); } }
使用该特性,现在我们可以在不修改已有 Web 应用的前提下,只需将按照一定格式打成的 JAR 包放到 WEB-INF/lib 目录下,即可实现新功能的扩充(比如注册三大组件),不需要额外的配置;
二、SpringMVC注解启动
在之前使用SpringMVC时,很多时候都是在web.xml中配置的方式来启动,而从SpringMVC 3.1开始就使用了servlet3.0的插件机制,可通过配置类的方式来启动SpringMVC
1、SpringServletContainerInitializer
在spring的web模块的jar包下存在META-INF/services/javax.servlet.ServletContainerInitializer,该文件中指定ServletContainerInitializer的实现类为SpringServletContainerInitializer,可知在web容启动时会加载这个类,来看看这个类:
@HandlesTypes(WebApplicationInitializer.class)//容器启动的时候会将WebApplicationInitializer类型下面的子类(实现类,子接口等)传递过来
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//webAppInitializerClasses就是WebApplicationInitializer类型
@Override
public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// 将webAppInitializerClasses集合中的非抽象,不是接口类型的class实例化并添加到initializer中
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);
//遍历执行initializers集合中WebApplicationInitializer.onStartup(servletContext)方法
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
} }
接下来看看SpringServletContainerInitializer使用@HandlesTypes引入的WebApplicationInitializer接口(只定义了一个onStartup方法)的子类:
2、AbstractContextLoaderInitializer
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { /** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass()); //该方法会在web容器启动时SpringServletContainerInitializer.onStartup中被调用
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//注册加载上下文的监听器
registerContextLoaderListener(servletContext);
} protected void registerContextLoaderListener(ServletContext servletContext) {
//调用createRootApplicationContext()创建根容器,需要具体的实现类去实现该抽象方法获取根容器
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;
} }
AbstractContextLoaderInitializer主要的功能:
调用创建createRootApplicationContext()创建根容器,
注册了监听器ContextLoaderListener(extends ContextLoader implements ServletContextListener)
3、AbstractDispatcherServletInitializer:
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer { /**
* The default servlet name. Can be customized by overriding {@link #getServletName}.
*/
public static final String DEFAULT_SERVLET_NAME = "dispatcher"; //重写了AbstractContextLoaderInitializer.onStartup(ServletContext)
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//维持父类的实现
super.onStartup(servletContext);
//添加了注册DispatcherServlet的步骤
registerDispatcherServlet(servletContext);
} protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
//调用createServletApplicationContext()创建web的ioc容器(管理Controller等springmvc的组件),需要子类去实现该抽象方法去获取web容器
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
//创建了前端控制器DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//使用servletContext添加DispatcherServlet
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);
//具体的路径映射规则需要子类实现getServletMappings()
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported()); Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
} customizeRegistration(registration);
} protected String getServletName() {
return DEFAULT_SERVLET_NAME;
} protected abstract WebApplicationContext createServletApplicationContext(); protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
} protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
} protected abstract String[] getServletMappings(); protected Filter[] getServletFilters() {
return null;
} protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);
if (registration == null) {
int counter = -1;
while (counter == -1 || registration == null) {
counter++;
registration = servletContext.addFilter(filterName + "#" + counter, filter);
Assert.isTrue(counter < 100,
"Failed to register filter '" + filter + "'." +
"Could the same Filter instance have been registered already?");
}
}
registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
} private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
} protected boolean isAsyncSupported() {
return true;
} protected void customizeRegistration(ServletRegistration.Dynamic registration) {
} }
AbstractDispatcherServletInitializer 的主要功能:
创建一个web的ioc容器:createServletApplicationContext();
创建了DispatcherServlet:createDispatcherServlet();
将创建的DispatcherServlet添加到ServletContext中,并设置路径映射等;
4、AbstractAnnotationConfigDispatcherServletInitializer
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
//实现了AbstractContextLoaderInitializer.createRootApplicationContext(),创建根容器
@Override
protected WebApplicationContext createRootApplicationContext() {
//获取根容器的配置类
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
//创建ioc容器
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
//注册组件
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
} //实现了AbstractDispatcherServletInitializer.createServletApplicationContext(),创建web的ioc容器
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
//获取web ioc容器的配置类
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
//
servletAppContext.register(configClasses);
}
return servletAppContext;
} //抽象方法 子类实现 返回根容器的配置类
protected abstract Class<?>[] getRootConfigClasses(); //抽象方法 子类实现 返回web ioc容器的配置类
protected abstract Class<?>[] getServletConfigClasses(); }
AbstractAnnotationConfigDispatcherServletInitializer (注解方式配置的DispatcherServlet初始化器) 主要作用:
创建根容器:createRootApplicationContext(),调用getRootConfigClasses()获取配置类
创建web的ioc容器: createServletApplicationContext(),调用getServletConfigClasses()获取配置类
5、以注解方式来启动SpringMVC:
上面我们分析了三个抽象类的功能,最终需要我们需要继承AbstractAnnotationConfigDispatcherServletInitializer,实现对应抽象方法来指定DispatcherServlet的配置信息
//web容器启动的时候创建对象;调用方法来初始化容器以前前端控制器
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { //获取根容器的配置类;(Spring的配置文件) 父容器;
@Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{RootConfig.class};
} //获取web容器的配置类(SpringMVC配置文件) 子容器;
@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{AppConfig.class};
} //获取DispatcherServlet的映射信息
// /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
// /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[]{"/"};
} }
3、定制SpringMVC
在一个配置类中添加@EnableWebMvc注解,开启SpringMVC定制配置功能,类似于使用xml的<mvc:annotation-driven/>标签:
@EnableWebMvc
public class WebConfig {
}
实现WebMvcConfigurer,配置组件(视图解析器、视图映射、静态资源映射、拦截器。。。)
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Override configuration methods...
}
当然有时候我们不需要配置所有的组件,没必要去实现WebMvcConfigurer所有方法,而WebMvcConfigurerAdapter实现了WebMvcConfigurer接口的所有方法(空实现),我们只要继承该类重写我们需要实现的方法即可:
@EnableWebMvc
@Configuration
public class AppConfig extends WebMvcConfigurerAdapter { //定制 //视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// TODO Auto-generated method stub
//默认所有的页面都从 /WEB-INF/ xxx .jsp
//registry.jsp();
registry.jsp("/WEB-INF/views/", ".jsp");
} //静态资源访问
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// TODO Auto-generated method stub
configurer.enable();
} //拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// TODO Auto-generated method stub
//super.addInterceptors(registry);
registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
} }
注意:WebMvcConfigurer
在5.0版本中已经被弃用了,spring的api文档有说明:as of 5.0 WebMvcConfigurer
has default methods (made possible by a Java 8 baseline) and can be implemented directly without the need for this adapter 大概意思:从5.0开始,WebMvcConfigurer具有默认方法(从Java 8开始,接口可以有默认方法)并且可以直接实现而无需此适配器
最后@EnableWebMvc做了什么:
使用@Import(DelegatingWebMvcConfiguration.class)引入了DelegatingWebMvcConfiguration,是一个配置类,继承了WebMvcConfigurationSupport:
WebMvcConfigurationSupport:使用@Bean为我们默认添加了很多springmvc组件,同时留下了一些空方法给子类重写来添加组件(模板方法模式)
DelegatingWebMvcConfiguration:使用@Autowired(required = false)注入所有的WebMvcConfigurer的实现类,重写WebMvcConfigurationSupport添加组件的方法 ,实际上是依次调用WebMvcConfigurer对应的方法来添加组件
所以其实可以不使用@EnableWebMvc,直接继承WebMvcConfigurationSupport,自己去实现对应的添加组件的方法也是可以的,当然还是推荐使用WebMvcConfigurer
Spring注解驱动开发之web的更多相关文章
- Spring注解驱动开发之AOP
前言:现今SpringBoot.SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解.原理,比如@Conditional.@Import.@Ena ...
- Spring注解驱动开发之Ioc容器篇
前言:现今SpringBoot.SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解.原理,比如@Conditional.@Import.@Ena ...
- Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用
Spring 注解驱动(二)Servlet 3.0 注解驱动在 Spring MVC 中的应用 Spring 系列目录(https://www.cnblogs.com/binarylei/p/1019 ...
- 0、Spring 注解驱动开发
0.Spring注解驱动开发 0.1 简介 <Spring注解驱动开发>是一套帮助我们深入了解Spring原理机制的教程: 现今SpringBoot.SpringCloud技术非常火热,作 ...
- 【Spring注解驱动开发】聊聊Spring注解驱动开发那些事儿!
写在前面 今天,面了一个工作5年的小伙伴,面试结果不理想啊!也不是我说,工作5年了,问多线程的知识:就只知道继承Thread类和实现Runnable接口!问Java集合,竟然说HashMap是线程安全 ...
- 【Spring注解驱动开发】组件注册-@ComponentScan-自动扫描组件&指定扫描规则
写在前面 在实际项目中,我们更多的是使用Spring的包扫描功能对项目中的包进行扫描,凡是在指定的包或子包中的类上标注了@Repository.@Service.@Controller.@Compon ...
- 【Spring注解驱动开发】使用@Scope注解设置组件的作用域
写在前面 Spring容器中的组件默认是单例的,在Spring启动时就会实例化并初始化这些对象,将其放到Spring容器中,之后,每次获取对象时,直接从Spring容器中获取,而不再创建对象.如果每次 ...
- 【Spring注解驱动开发】面试官:如何将Service注入到Servlet中?朋友又栽了!!
写在前面 最近,一位读者出去面试前准备了很久,信心满满的去面试.没想到面试官的一个问题把他难住了.面试官的问题是这样的:如何使用Spring将Service注入到Servlet中呢?这位读者平时也是很 ...
- 【Spring注解驱动开发】关于BeanPostProcessor后置处理器,你了解多少?
写在前面 有些小伙伴问我,学习Spring是不是不用学习到这么细节的程度啊?感觉这些细节的部分在实际工作中使用不到啊,我到底需不需要学习到这么细节的程度呢?我的答案是:有必要学习到这么细节的程度,而且 ...
随机推荐
- Python的字符串和列表和字典的方法/函数
字符串 S.find()#可指定范围查找字串,返回索引值,否则返回-1 S.index()#同find,只是找不到的之后返回异常 S.count()#返回找到字串的个数 S.lower()#转小写 S ...
- MongoDB 操作手冊CRUD 更新 update
改动记录 概述 MongoDB提供了update()方法用于更新记录. 这种方法接受下面參数: 一个更新条件的JSON对象用于匹配记录,一个更新操作JSON对象用于声明更新操作,和一个选项JS ...
- 使用7zip压解各种文件的经常使用命令
7zip简单介绍 格式支持 压缩解压缩 仅支持解压缩 安装 Debian Ubuntu ArchLinux 使用 打包 解压 列出文件的信息列表 检查包的完整性 更新压缩包 删除包里的文件 7zip简 ...
- GCC编译动态和静态链接库例子
我们通常把一些公用函数制作成函数库,供其它程序使用.函数库分为静态库和动态库两种.静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库.动态库在程序编译时并不会被连接到目标代码中,而是 ...
- javaScript定义函数的三种方式&变量的作用域
一.函数定义 方式1.普通方式定义函数 function 函数名(參数n){ 函数体 } function add(a,b){ return a+b; } 方式2.直接量定义函数 var 函数名=fu ...
- java反射(1)
反射反射,程序员的快乐. 第一次了解反射这个概念是从<<大话设计>>中所了解到的.当时只是对概念的模糊了解,具体对它的机制并不清楚.最近在学习并实践SSH框架,其中Spring ...
- 定时任务 bash 对远程数据库 备份 读写
1g表 每行都有可能被更新,故全表备份 检测备份是否在进行 [root@hadoop1 ~]# netstat --numeric-ports | grep 3306tcp 0 ...
- JDBC连接数据库核心代码
1.Oracle数据库 Class.forName("oracle.jdbc.driver.OracleDriver").newInstance(); String url ...
- action 与 action 之间的跳转
这个要看你的两个action是不是在通一个package下了 <!-- 相同package下调用其它action --><action name="loginUser&qu ...
- java jdbc 与mysql连接的基本步骤
Java与mysql链接的基本步骤: 第一步:注册驱动 方法一: DriverManager.registerDriver(new com.mysql.jdbc.Driver()); 方法二:设置属性 ...