Setup Spring MVC project without web.xml

Servlet 3.0 specification

สมัยก่อนถ้าเราจะเขียน Servlet เราจะต้องสร้าง Servlet Class หน้าตาประมาณนี้

public class MyServlet extends HttpServlet {
    public void doGet (HttpServletRequest req, 
                       HttpServletResponse res) {
                //....
    }
}

แล้วไปเพิ่มข้อมูลใน web.xml หน้าตาประมาณนี้

<web-app>
  <servlet>
          <servlet-name>MyServlet</servlet-name>
          <servlet-class>samples.MyServlet</servlet-class>
  </servlet>

  <servlet-mapping>
          <servlet-name>MyServlet</servlet-name>
          <url-pattern>/MyApp</url-pattern>
  </servlet-mapping>
...

</web-app>

ซึ่งพอมาถึง Servlet version 3.0 เราไม่ต้องใช้ web.xml ก็ได้ (จะใช้ก็ไม่มีใครว่านะ แต่อาจจะดูเสียชาติเกิดไปหน่อย) หน้าตาของโค้ดก็จะประมาณนี้

@Servlet(urlMappings={"/MyApp"})
public class MyServlet {
    @GET
    public void handleGet(HttpServletRequest req, 
                          HttpServletResponse res) {
                ....
    }
}

ไม่ต้องง้อ web.xml เราก็สามารถสร้าง Servlet ได้แล้ว เย้!

แล้วมันเกี่ยวอะไรกับ Spring Framework???

เกี่ยวครับงานหนักด้วย เนื่องจากว่าสมัยก่อนเราจะต้องไปลงทะเบียน Dispatcher ของ Spring ใน web.xml ประมาณนี้

<web-app>
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>

แล้วทีนี้ ถ้าไม่มี web.xml แล้วเราจะไป config spring ที่ใหนหล่ะ????

ทางออก

ทางทีมงาน Spring Framework เขาเตรียมคลาสไว้ให้ใช้โดยไม่ต้องง้อ web.xml เหมือนกัน ซึ่งเพิ่งมีให้ใช้ใน Spring Framework version 3.1 (Version ต่ำกว่านี้อดนะจ๊ะ) เพียงแค่เราไป implements WebApplicationInitializer ก็ใช้งานได้แล้ว

ตัวอย่าง

เครื่องมือที่ผมใช้มีดังนี้ ถ้าจะทำตามก็ไม่ต้องห่วงเรื่อง version นะครับ เอาตามที่ท่านสะดวกหล่ะกัน

  1. Eclipse Juno SR2
  2. JDK 7 update 21
  3. Spring Framework 3.2.2
  4. Apache Tomcat 7.0.39
  5. log4j 1.2.17

เปิด Eclipse แล้ว Add server ให้พร้อมด้วยนะครับ เมื่อพร้อมแล้วก็ลุยกันเลย

  1. New Dynamic Web Project ตั้งชื่อให้เรียบแล้ว และให้สังเกตที่ Dynamic web module version ด้วยนะครับว่าต้องเป็น 3.0
    New Dynamic Web Project
    อย่าลืมดูว่า version ต้องเป็น 3.0
  2. ลบ src เดิมออกแล้วเพิ่มดังรูป แล้วกด Finish ได้เลย ข้อนี้สามารถข้ามไปได้นะครับ แต่เดี๋ยวถ้าติดตามตอนต่อๆ ไป จะทราบข้อดีเองว่าทำไมผมถึงทำแบบนี้
    Add new src
    ไม่ทำไม่เป็นไร แต่ถ้าทำจะดีมาก🙂
  3. เมื่อ Eclipse สร้างโปรเจคให้เสร็จแล้ว สังเกตุที่ WEB-INF จะไม่มีไฟล์ web.xml
    No web.xml
    ถ้ามีไฟล์ web.xml แสดงว่าท่านไปสั่งให้ eclipse สร้างไฟล์ในขั้นตอนก่อนกดปุ่ม Fisnish
  4. Copy jar file ของ Spring ไปไว้ที่ WEB-INF -> lib
    • commons-logging-1.1.2.jar
    • log4j-1.2.17.jar
    • spring-beans-3.2.2.RELEASE.jar
    • spring-context-3.2.2.RELEASE.jar
    • spring-core-3.2.2.RELEASE.jar
    • spring-expression-3.2.2.RELEASE.jar
    • spring-web-3.2.2.RELEASE.jar
    • spring-webmvc-3.2.2.RELEASE.jar
  5. สร้างไฟล์ log4j.properties ไว้ใน src/main/resources/ โดย config ตามต้องการ ผมใช้แค่ stdout ประมาณนี้

    # Root logger option
    log4j.rootLogger=DEBUG, stdout

    # Direct log messages to stdout
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.Target=System.out
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

  6. มาถึงขั้นตอนพระเอกของงานแล้ว สร้างคลาสที่ implements WebApplicationInitializer
    package com.magicalcyber.springmvc.web.config;
    
    import java.util.Set;
    
    import javax.servlet.FilterRegistration;
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRegistration;
    
    import org.apache.log4j.Logger;
    import org.springframework.web.WebApplicationInitializer;
    import org.springframework.web.context.ContextLoaderListener;
    import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
    import org.springframework.web.filter.CharacterEncodingFilter;
    import org.springframework.web.filter.HiddenHttpMethodFilter;
    import org.springframework.web.servlet.DispatcherServlet;
    
    public class WebAppInitializer implements WebApplicationInitializer {
    	private static final Logger logger = Logger
    			.getLogger(WebAppInitializer.class);
    
    	/**
    	 * 
    	 * 
    	 * @see org.springframework.web.WebApplicationInitializer#onStartup(javax.servlet
    	 *      .ServletContext)
    	 */
    	@Override
    	public void onStartup(ServletContext servletContext)
    			throws ServletException {
    
    		// Create the root appcontext
    		AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
    		rootContext.register(RootConfig.class);
    
    		// if you're not passing in the config classes to the constructor,
    		// refresh
    		rootContext.refresh();
    
    		// Manage the lifecycle of the root appcontext
    		servletContext.addListener(new ContextLoaderListener(rootContext));
    		servletContext.setInitParameter("defaultHtmlEscape", "true");
    
    		// now the config for the Dispatcher servlet
    		AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();
    		mvcContext.register(WebMvcConfig.class);
    
    		// Filters
    		// http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/web/filter/package-summary.html
    
    		// Enables support for DELETE and PUT request methods with web browser
    		// clients
    		// http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/web/filter/HiddenHttpMethodFilter.html
    
    		FilterRegistration.Dynamic fr = servletContext.addFilter(
    				"hiddenHttpMethodFilter", new HiddenHttpMethodFilter());
    		fr.addMappingForUrlPatterns(null, true, "/*");
    
    		fr = servletContext.addFilter("encodingFilter",
    				new CharacterEncodingFilter());
    		fr.setInitParameter("encoding", "UTF-8");
    		fr.setInitParameter("forceEncoding", "true");
    		fr.addMappingForUrlPatterns(null, true, "/*");
    
    		// The main Spring MVC servlet.
    		ServletRegistration.Dynamic appServlet = servletContext.addServlet(
    				"appServlet", new DispatcherServlet(mvcContext));
    		appServlet.setLoadOnStartup(1);
    		Set<String> mappingConflicts = appServlet.addMapping("/");
    
    		if (!mappingConflicts.isEmpty()) {
    			for (String s : mappingConflicts) {
    				logger.error("Mapping conflict: " + s);
    			}
    			throw new IllegalStateException(
    					"'appServlet' cannot be mapped to '/' under Tomcat versions <= 7.0.14");
    		}
    
    	}
    }
    

    ถ้าสังเกตุดีๆ เห็นว่า มันคือ config ใน web.xml แค่เอามาเขียนไว้ในโค้ด

  7. สร้างอีกสองคลาส ซึ่งเป็นคลาส

    Class สำหรับเก็บ Bean กลางที่ไม่ใช่ MVC

    package com.magicalcyber.springmvc.web.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    @ComponentScan(basePackages = { "com.magicalcyber.springmvc.service" })
    public class RootConfig {
    
    }
    
    

    Class สำหรับเก็บ Bean ของ Spring MVC

    package com.magicalcyber.springmvc.web.config;
    
    import java.util.Locale;
    import java.util.Properties;
    
    import org.apache.log4j.Logger;
    import org.springframework.context.MessageSource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.support.ReloadableResourceBundleMessageSource;
    import org.springframework.web.servlet.LocaleResolver;
    import org.springframework.web.servlet.ViewResolver;
    import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
    import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
    import org.springframework.web.servlet.i18n.SessionLocaleResolver;
    import org.springframework.web.servlet.view.InternalResourceViewResolver;
    
    @Configuration
    @EnableWebMvc
    @ComponentScan(basePackages = { "com.magicalcyber.springmvc.web" })
    public class WebMvcConfig extends WebMvcConfigurerAdapter {
    
    	private static final String MESSAGE_SOURCE = "/WEB-INF/classes/messages";
    
    	private static final Logger logger = Logger.getLogger(WebMvcConfig.class);
    
    	/**
    	 * @return the view resolver
    	 */
    	@Bean
    	public ViewResolver viewResolver() {
    		logger.debug("setting up view resolver");
    
    		InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    		viewResolver.setPrefix("/WEB-INF/views/");
    		viewResolver.setSuffix(".jsp");
    		return viewResolver;
    	}
    
    	@Bean(name = "messageSource")
    	public MessageSource configureMessageSource() {
    		logger.debug("setting up message source");
    		ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
    		messageSource.setBasename(MESSAGE_SOURCE);
    		messageSource.setCacheSeconds(5);
    		messageSource.setDefaultEncoding("UTF-8");
    		return messageSource;
    	}
    
    	@Bean
    	public LocaleResolver localeResolver() {
    		SessionLocaleResolver lr = new org.springframework.web.servlet.i18n.SessionLocaleResolver();
    		lr.setDefaultLocale(Locale.ENGLISH);
    		return lr;
    	}
    
    	@Override
    	public void addResourceHandlers(ResourceHandlerRegistry registry) {
    		logger.debug("setting up resource handlers");
    		registry.addResourceHandler("/resources/").addResourceLocations(
    				"/resources/**");
    	}
    
    	@Override
    	public void configureDefaultServletHandling(
    			DefaultServletHandlerConfigurer configurer) {
    		logger.debug("configureDefaultServletHandling");
    		// if the spring dispatcher is mapped to / then forward non handled
    		// requests
    		// (e.g. static resource) to the container's "default servlet"
    		configurer.enable();
    	}
    
    	@Override
    	public void addInterceptors(final InterceptorRegistry registry) {
    		registry.addInterceptor(new LocaleChangeInterceptor());
    	}
    
    	@Bean
    	public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
    		SimpleMappingExceptionResolver b = new SimpleMappingExceptionResolver();
    
    		Properties mappings = new Properties();
    		mappings.put("org.springframework.web.servlet.PageNotFound", "p404");
    		mappings.put("org.springframework.dao.DataAccessException",
    				"dataAccessFailure");
    		mappings.put("org.springframework.transaction.TransactionException",
    				"dataAccessFailure");
    		b.setExceptionMappings(mappings);
    		return b;
    	}
    }
    
    
  8. จบการ Config เพียงเท่านี้ ต่อไปให้ลอง Start Server ดูว่ามีปัญหาอะไรหรือเปล่า ซึ่งถ้าไม่มีปัญหาอะไรก็ลุยต่อได้
    First start server
    ผมยังไม่เจอปัญหาตอน start server ซึ่งถ้าเจอก็โพสต์ถามได้ครับ

Hello World!

Setup เสร็จแล้วจะไม่ให้ลองก็ไม่ได้ ไม่รู้ว่าจะใช้ได้จริงหรือเปล่า ต้องบูชาด้วย Hello World! ถึงจะรู้

  1. สร้าง Controller Class หน้าตาประมาณนี้
    package com.magicalcyber.springmvc.web.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class HelloWorldController {
    	@RequestMapping("/helloworld")
    	public String helloWorld(Model model) {
    		model.addAttribute("message", "Hello World!");
    		return "helloWorld";
    	}
    }
    

    จากการ Setup ก่อนหน้านี้ เราได้สั่งให้ Spring ทำการ Scan package com.magicalcyber.springmvc.web ซึ่งคลาสนี้ก็อยู่ใน controller และมี @Controller ทำให้เราไม่ต้องไปเพิ่ม Bean Config

  2. สร้างโฟลเดอร์ WEB-INF/views (เพราะ config เราอ้างว่าจะเก็บ view ไว้ที่นี่) แล้วสร้างไฟล์ helloWorld.jsp ไว้ที่นี่ (controller บอกว่าจะวิ่งมาหาไฟล์นี้) ซึ่งไฟล์นี้ก็แค่แสดงข้อความโดยใช้โค้ด ${ message } ก็พอแล้ว
    <%@ page language="java" contentType="text/html; charset=US-ASCII"
        pageEncoding="US-ASCII"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
    <title>Insert title here</title>
    </head>
    <body>
    ${ message }
    </body>
    </html>
    
  3. ทำการ Start Server หากไม่มีปัญหาอะไรแล้วก็เปิด Browser แล้วพิมพ์ url http://localhost:8080/springmvc/helloworld ควรจะได้หน้าตาดังนี้
    Hello World!
    สวัสดีชาวโลก!!!

ส่งท้าย

สำหรับท่านที่ยังใหม่กับ Spring Framework อาจจะมึนๆ ซักหน่อยนะครับ หากชินกับสไตล์ของพวก config.xml ก็ยังคงใช้งานได้เหมือนเดิมนะครับ ยังไม่ต้องรีบเปลี่ยนก็ได้

อ้างอิง

One thought on “Setup Spring MVC project without web.xml

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s