全注解搭建Spring Web MVC开发环境

3 篇文章 0 订阅

在以往开发JavaWeb项目中,我们可以在web.xml中配置三大组件:Listener,Filter,Servlet,在Servlet 3.0(Tomcat 7.0+)版本以后,
支持使用注解来配置这三大组件,并且可以使用注解在Servlet容器启动后初始化之前做一些事情,因此web.xml并不是必须的。

通过注解来注册三大组件

1. @WebListener
@WebListener
public class SmartListener implements ServletContextListener
{
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent)
    {
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent)
    {
    }

}

2. @WebFilter
有urlPatterns,initParams等注解属性可配置,与web.xml中配置Filter一致
@WebFilter("/*")
public class SmartFilter implements Filter
{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException
    {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException
    {
        chain.doFilter(request, response);
    }

    @Override
    public void destroy()
    {
    }

}

3. @WebServlet
有urlPatterns,loadOnStartup,initParams等注解属性可配置,与web.xml中配置Servlet一致
@WebServlet("/smartServlet")
public class SmartServlet extends HttpServlet
{
    private static final long serialVersionUID = 1093068431684465016L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        response.getWriter().println("SmartServlet GET");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        response.getWriter().println("SmartServlet POST");
    }

}

以往web.xml中经典的Spring Web MVC配置

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring/*.spring.xml</param-value>
</context-param>

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/dispatcher.spring.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

而在Servlet 3.0+版本中,我们可以通过在jar包配置ServletContainerInitializer接口的实现类,在容器启动时注册三大组件

Servlet容器启动时会扫描应用包中每一个jar包中ServletContainerInitializer接口的实现类,该实现类全类名必须在jar包
/META-INF/service/javax.servlet.ServletContainerInitializer文件中配置
/**
 * 把SmartServletContainerInitializer全类路径放入到/META-INF/service/javax.servlet.ServletContainerInitializer中
 */
public class SmartServletContainerInitializer implements ServletContainerInitializer
{
    /**
     * 在Servlet容器启动时调用onStartup方法
     */
    @Override
    public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException
    {
        // 这里可注册Servlet三大主键
        // servletContext.addListener(className);
        // servletContext.addFilter(filterName, filterClass).addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");
        // servletContext.addServlet(servletName, className).addMapping("/");
    }

}

ServletContainerInitializer实现类上面可以使用@HandlesTypes({XXX.class})注解,在启动执行onStartup方法时,会将该注解中配置的类
的所有子类作为参数Set<Class<?>>传入。

例如spring-web-4.3.16.RELEASE.jar中有
/META-INF/service/javax.servlet.ServletContainerInitializer文件,里面的内容为
org.springframework.web.SpringServletContainerInitializer

查看SpringServletContainerInitializer.java源码

@HandlesTypes({WebApplicationInitializer.class})
public class SpringServletContainerInitializer implements ServletContainerInitializer
{
    /**
    * 传入WebApplicationInitializer类的所有子类
    */
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException
    {
        List<WebApplicationInitializer> initializers = new LinkedList();
     
        if (webAppInitializerClasses != null)
        {
            for (Class<?> waiClass : webAppInitializerClasses)
            {
                // 过滤掉所有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) 
        {
            // 调用子类的onStartup方法
            initializer.onStartup(servletContext);
        }
    }
}

可以看到,在SpringServletContainerInitializer类中,Servlet容器启动时,会执行onStartup方法,并把类上注解
@HandlesTypes({WebApplicationInitializer.class})中WebApplicationInitializer.java接口的所有子类作为集合参数
webAppInitializerClasses传入,那我们就可以通过实现WebApplicationInitializer.java接口,从而在Servlet启动时注册
一些组件和启动Spring IOC容器

WebApplicationInitializer.java接口又提供了多层子接口
WebApplicationInitializer
    AbstractContextLoaderInitializer
        AbstractDispatcherServletInitializer
            AbstractAnnotationConfigDispatcherServletInitializer

别急,来一个个分析

AbstractContextLoaderInitializer.java中提供了onStartup具体实现

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer
{
    protected final Log logger = LogFactory.getLog(getClass());
   
    public void onStartup(ServletContext servletContext) throws ServletException
    {
        // 调用registerContextLoaderListener方法
        registerContextLoaderListener(servletContext);
    }

    protected void registerContextLoaderListener(ServletContext servletContext)
    {
        // 创建Spring IOC容器
        WebApplicationContext rootAppContext = createRootApplicationContext();
        if (rootAppContext != null)
        {
            ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
            listener.setContextInitializers(getRootApplicationContextInitializers());
            // 创建好的容器通过ContextLoaderListener保存到servletContext中
            servletContext.addListener(listener);
        }
        else
        {
            logger.debug("No ContextLoaderListener registered, as createRootApplicationContext() did not return an application context");
        }
    }

    /**
    * 对子类暴露createRootApplicationContext,用于创建Spring IOC容器
    */
    protected abstract WebApplicationContext createRootApplicationContext();

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

}

AbstractDispatcherServletInitializer.java

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer
{
	public static final String DEFAULT_SERVLET_NAME = "dispatcher";

	public void onStartup(ServletContext servletContext) throws ServletException
	{
        // 先创建Spring IOC容器
		super.onStartup(servletContext);

        // 再创建Spring Web MVC容器
		registerDispatcherServlet(servletContext);
	}

	protected void registerDispatcherServlet(ServletContext servletContext)
	{
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return empty or null");

        // 创建Spring Web MVC容器
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext,
				"createServletApplicationContext() did not return an application context for servlet [" + servletName
						+ "]");

		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        // 把dispatcherServlet添加到Servlet容器中
		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
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters))
		{
			for (Filter filter : filters)
			{
				registerServletFilter(servletContext, filter);
			}
		}

		customizeRegistration(registration);
	}

	protected String getServletName()
	{
        // Servlet名称默认提供
		return DEFAULT_SERVLET_NAME;
	}

    /**
    * 对子类暴露createServletApplicationContext方法,用于创建Spring Web MVC容器
    */
	protected abstract WebApplicationContext createServletApplicationContext();

	protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext)
	{
		return new DispatcherServlet(servletAppContext);
	}

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

    /**
    * 配置dispatcherServlet拦截的路径
    */
	protected abstract String[] getServletMappings();

	protected Filter[] getServletFilters()
	{
		return null;
	}

	protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter)
	{
		String filterName = Conventions.getVariableName(filter);
		FilterRegistration.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, new String[] { 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)
	{
	}
}

AbstractAnnotationConfigDispatcherServletInitializer.java

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer
{
    /**
    * 通过Java配置类创建Spring IOC容器,向下暴露getRootConfigClasses方法
    */
	protected WebApplicationContext createRootApplicationContext()
	{
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses))
		{
			AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
			rootAppContext.register(configClasses);
			return rootAppContext;
		}

		return null;
	}

    /**
    * 通过Java配置类创建Spring Web MVC容器,向下暴露getServletConfigClasses方法
    */
	protected WebApplicationContext createServletApplicationContext()
	{
		AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses))
		{
			servletAppContext.register(configClasses);
		}

		return servletAppContext;
	}

	protected abstract Class<?>[] getRootConfigClasses();

	protected abstract Class<?>[] getServletConfigClasses();
}

经过上面的分析,我们只需要实现AbstractAnnotationConfigDispatcherServletInitializer.java接口,
并实现getRootConfigClasses和getServletConfigClasses方法,就可以实现全注解配置Spring Web MVC开发环境

新建一个简单的Maven工程,打包方式选择war,如下图

在pom.xml中加入servlet-api,注意需要3.0+版本
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>

加入Spring Web MVC依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
加入Spring Aop依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>
加入数据源依赖等等
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.16.RELEASE</version>
</dependency>

<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>

此时IDE会报错,提示Web项目必须有web.xml,可以在pom.xml中配置
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-war-plugin</artifactId>
            <version>2.2</version>
            <configuration>
                <failOnMissingWebXml>false</failOnMissingWebXml>
            </configuration>
        </plugin>
    </plugins>
</build>

新建一个AbstractAnnotationConfigDispatcherServletInitializer接口实现类

public class SmartWebConfigServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException
	{
        // 调用父类方法创建两个容器
		super.onStartup(servletContext);

		CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
		// 注册字符编码过滤器,也可以复写getServletFilters方法来注册Filter
		servletContext.addFilter("characterEncodingFilter", characterEncodingFilter)
            .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*");

        // 相当于如下配置
        /* <filter>
            <filter-name>characterEncodingFilter</filter-name>
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <param-name>encoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
            <init-param>
                <param-name>forceEncoding</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>

        <filter-mapping>
            <filter-name>characterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping> */
	}

	@Override
	protected Class<?>[] getRootConfigClasses()
	{
		return new Class<?>[] { SmartApplicationConfig.class };

        // 相当于如下配置
        /* <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/*.spring.xml</param-value>
        </context-param>

        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener> */
	}

	@Override
	protected Class<?>[] getServletConfigClasses()
	{
		return new Class<?>[] { SmartDispatcherConfig.class };

        // 相当于如下配置
        /* <servlet>
            <servlet-name>dispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:spring/dispatcher.spring.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>
        </servlet>

        <servlet-mapping>
            <servlet-name>dispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping> */
	}

	@Override
	protected String[] getServletMappings()
	{
        // / 拦截所有请求,包括静态资源,但是不包括jsp
        // /* 拦截所有请求,包括jsp
		return new String[] { "/" };
	}

}

在上面SmartWebConfigServletInitializer.java类中,我们用SmartApplicationConfig.class代替了以往传统的Spring容器xml配置文件,
用SmartDispatcherConfig.class代替了Spring Web的xml配置文件

SmartApplicationConfig.java中,可以配置装配Bean,配置包扫描,导入properties配置,开启支持Aop,配置数据源,事务管理器等等

@ComponentScan(value = "com.realjt.smart", excludeFilters = {
		@Filter(type = FilterType.ANNOTATION, classes = { Controller.class, ControllerAdvice.class }) })
@PropertySource(value = { "classpath:jdbc.properties" }, encoding = "UTF-8", ignoreResourceNotFound = true)
@EnableAspectJAutoProxy
@EnableTransactionManagement
public class SmartApplicationConfig
{

}

SmartDispatcherConfig.java中,一样可以配置包扫描,使用@EnableWebMvc注解以及继承WebMvcConfigurerAdapter
来配置更多Spring Web MVC相关配置,更多配置可以参考Spring官方文档

@ComponentScan(value = "com.realjt.smart", useDefaultFilters = false, includeFilters = {
		@Filter(type = FilterType.ANNOTATION, classes = { Controller.class, ControllerAdvice.class }) })
@EnableWebMvc
public class SmartDispatcherConfig extends WebMvcConfigurerAdapter
{
    @Override
	public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)
	{
		// 开启静态资源放行
		configurer.enable();
	}

	@Override
	public void configureViewResolvers(ViewResolverRegistry registry)
	{
		// 注册视图解析器
		registry.jsp("/WEB-INF/jsp/", ".jsp");
	}

    @Override
	public void addInterceptors(InterceptorRegistry registry)
	{
        // 注册拦截器
	}

	@Override
	public void addFormatters(FormatterRegistry registry)
	{
	}

	@Override
	public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
	{
		Charset charset = Charset.forName("UTF-8");

		List<MediaType> mediaTypes = new ArrayList<>();
		mediaTypes.add(new MediaType("application", "json", charset));
		mediaTypes.add(new MediaType("text", "html", charset));

		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

		StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(charset);

		MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
		jackson2HttpMessageConverter.setSupportedMediaTypes(mediaTypes);

		jackson2HttpMessageConverter.setObjectMapper(objectMapper);

		converters.add(stringHttpMessageConverter);
		converters.add(jackson2HttpMessageConverter);
	}

}

至此,我们就可以编写Controller,Service,Repository等组件,并启动运行Web项目,默认访问index.jsp或index.html。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值