Archive for the ‘tutorial series’ category

Basic 1: Java ,Maven และ Eclipse

February 2nd, 2010

Basic 1: Java ,Maven และ Eclipse (Basic Series)

1. เตรียม Java environment
สามารถ download ได้จาก http://java.sun.com/javase/downloads/index_jdk5.jsp
จากนั้น ก็ setup  JAVA_HOME  (D:\Java\jdk1.6.0_17)
เพิ่ม path ให้ชี้ไปที่  %JAVA_HOME%\bin

จากนั้น ก็เทส C:\>java –version

2. เตรียม Maven
สามารถ download ได้จาก  http://maven.apache.org/download.html
จากนั้น setup  M2_HOME (D:\java\apache-maven-2.2.1)
เพิ่ม path ให้ชี้ไปที่ %M2_HOME%\bin

จากนั้น ก็เทส C:\>mvn –version

2.1 ต่อมาต้องจัดการ configure ที่ Maven ก่อน แก้ settings.xml อยู่ใน D:\java\apache-maven-2.2.1\conf
set > local repository ให้ชี้ไปที่ <localRepository>C:/java/.m2/repository</localRepository> ซึ่ง ไฟล์ทุกอย่างที่ ใช้ Maven จัดการจะถูกเก็บไว้ในนี้
» Read more: Basic 1: Java ,Maven และ Eclipse

เริ่มต้นกับ Roo: The Tutorial (ภาค 1.0.0 GA)

January 5th, 2010

แปลและย่อมาจาก http://static.springsource.org/spring-roo/reference/html/beginning.html

เนื้อหาของ  Entry นี้คือ

  • project creation
  • creation and development of domain objects (JPA entities)
  • adding fields of different types to the domain objects
  • creating relationships between domain objects
  • automatic creation of integration tests
  • creating workspace artifacts to import the project into your IDE
  • automatic scaffolding of a Web tier
  • running the application in a Web container
  • controlling and securing access to different views in the application
  • customizing the look and feel of the Web UI for our business domain
  • creating and running Selenium tests
  • deployment and backup of your application

รายละเอียดของแอพพลิเคชั่น

แอพพลิเคชั่นที่เราจะสร้างด้วย Roo คือการรับออร์เดอร์ในร้านพิซซ่า โดยสิ่งที่เราจะทำนั้นก็เป็นเรื่องพื้นฐานเช่นการเพิ่มประเภทพิซซ่า การเพิ่มหน้า การสร้างใบสั่ง ซึ่งเราสามารถเขียนเป็น Class Diagram ได้คร่าวๆดังนี้

Step 1: Starting a Typical Project

ขั้นตอนแรกอย่ารอช้ามาสร้างโปรเจคเลยครับ (สำหรับคนที่ยังไม่ได้ติดตั้ง Roo ก็ให้ไปอ่านแถวๆนี้ครับ installing a JDK, Spring Roo) สร้างไดเรคทอรี่ชื่อ pizza ขึ้นมาก่อน

> mkdir pizza
> cd pizza
pizza>

ต่อไปเราจะมาดูความมหัศจจรย์ของ Roo อีกอย่างคือเรื่อง hint โดยให้เราลองพิมพ์ hint ลงไประบบจะทำการบอกขั้นตอนต่อไปที่เราควรจะทำออกมาอย่างคร่าวเพื่อช่วยให้เราไม่หลงทาง

pizza> roo
____  ____  ____
/ __ \/ __ \/ __ \
/ /_/ / / / / / / /
/ _, _/ /_/ / /_/ /
/_/ |_|\____/\____/    1.0.0.RELEASE [rev XXX]

Welcome to Spring Roo. For assistance press TAB or type "hint" then hit ENTER.
roo>
roo> hint
Welcome to Roo! We hope you enjoy your stay!

Before you can use many features of Roo, you need to start a new project.

To do this, type 'project' (without the quotes) and then hit TAB.

Enter a --topLevelPackage like 'com.mycompany.projectname' (no quotes).
When you've finished completing your --topLevelPackage, press ENTER.
Your new project will then be created in the current working directory.

Note that Roo frequently allows the use of TAB, so press TAB regularly.
Once your project is created, type 'hint' and ENTER for the next suggestion.
You're also welcome to visit http://forum.springframework.org for Roo help.
roo>

ต่อไปเราจะสร้างโปรเจคด้วย Roo Command กันโดยให้พิมพ์คำสั่งนี้ลงไป

roo> project --topLevelPackage com.springsource.roo.pizzashop
Created /Users/sschmidt/Development/workspaces/test9/pom.xml
Created SRC_MAIN_JAVA
Created SRC_MAIN_RESOURCES
Created SRC_TEST_JAVA
Created SRC_TEST_RESOURCES
Created SRC_MAIN_WEBAPP
Created SRC_MAIN_RESOURCES/META-INF/spring
Created SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
Created SRC_MAIN_RESOURCES/META-INF/spring/log4j.properties
com.springsource.roo.pizzashop roo>

ผลที่ได้คือเราจะได้โปรเจคที่มีโครงสร้างดังนี้ออกมา

ซึ่งโครงสร้างของโปรเจคที่ได้มาจะเป็น Maven Project Structure ทั่วไปอย่างที่เราคุ้นเคย ส่วนต่อไปคือเราจะระบุส่วนของการทำ Persistence โดยที่ตัวอย่างนี้เราจะใช้ HIBERNATE คู่กับ HYPERSONIC โดยที่เราไม่ต้องทำอะไรให้ยุ่งยากเหมือนแต่ก่อน สิ่งที่เราต้องทำคือพิมพ์คำสั่ง

com.springsource.roo.pizzashop roo>
com.springsource.roo.pizzashop roo> persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY
Created SRC_MAIN_RESOURCES/META-INF/persistence.xml
Created SRC_MAIN_RESOURCES/META-INF/spring/database.properties
Managed SRC_MAIN_RESOURCES/META-INF/spring/applicationContext.xml
Managed ROOT/pom.xml
com.springsource.roo.pizzashop roo>

ต่อไปเราจะทำการลองสร้าง Model ตัวแรกขึ้นมาก่อนโดยเราเลือกสร้าง Topping ขึ้นมาก่อน โดย Topping จะมี Field เพียง Field เดียวเป็น String ชื่อ “name” โดยเราสามารถใช้ Command ชื่อ “entity” เพื่อใช้สร้าง Model ออกมาให้เราได้

com.springsource.roo.pizzashop roo>
com.springsource.roo.pizzashop roo> entity --class ~.domain.Topping --testAutomatically
Created SRC_MAIN_JAVA/com/springsource/roo/pizzashop/domain
Created SRC_MAIN_JAVA/com/springsource/roo/pizzashop/domain/Topping.java
Created SRC_TEST_JAVA/com/springsource/roo/pizzashop/domain
Created SRC_TEST_JAVA/com/springsource/roo/pizzashop/domain/ToppingDataOnDemand.java
Created SRC_TEST_JAVA/com/springsource/roo/pizzashop/domain/ToppingIntegrationTest.java
Created SRC_MAIN_JAVA/com/springsource/roo/pizzashop/domain/Topping_Roo_Entity.aj
Created SRC_MAIN_JAVA/com/springsource/roo/pizzashop/domain/Topping_Roo_ToString.aj
Created SRC_MAIN_JAVA/com/springsource/roo/pizzashop/domain/Topping_Roo_Configurable.aj
Created SRC_TEST_JAVA/com/springsource/roo/pizzashop/domain/ToppingIntegrationTest_Roo_Configurable.aj
Created SRC_TEST_JAVA/com/springsource/roo/pizzashop/domain/ToppingDataOnDemand_Roo_DataOnDemand.aj
Created SRC_TEST_JAVA/com/springsource/roo/pizzashop/domain/ToppingIntegrationTest_Roo_IntegrationTest.aj
Created SRC_TEST_JAVA/com/springsource/roo/pizzashop/domain/ToppingDataOnDemand_Roo_Configurable.aj

Roo จะสร้างไฟล์ให้เราเยอะพอสมควร แต่อย่าเพิ่งตกใจงานเรายังไมเสร็จครับเรายังไม่ได้เพิ่ม Field ครับ

~.domain.Topping roo> field string --fieldName name --notNull --sizeMin 2
Managed SRC_MAIN_JAVA/com/springsource/roo/pizzashop/domain/Topping.java
Created SRC_MAIN_JAVA/com/springsource/roo/pizzashop/domain/Topping_Roo_JavaBean.aj
Managed SRC_TEST_JAVA/com/springsource/roo/pizzashop/domain/ToppingDataOnDemand_Roo_DataOnDemand.aj
Managed SRC_MAIN_JAVA/com/springsource/roo/pizzashop/domain/Topping_Roo_ToString.aj

ในเมื่อการสร้าง Model มันง่ายเสียขนาดนี้เราก็อย่าช้าอยู่เลยครับสร้างอีกสองตัวคือ Base กับ Pizza โดยใช้ Command นี้

entity --class ~.domain.Base --testAutomatically
field string --fieldName name --notNull --sizeMin 2
entity --class ~.domain.Pizza --testAutomatically
field string --fieldName name --notNull --sizeMin 2
field number --fieldName price --type java.lang.Float

หลังจากที่เราได้ Model พื้นฐานมาแล้วเราจะเริ่มใส่ Relation ระหว่าง Model ลงไปโดยจะเริ่มที่ Topping กับ Pizza ก่อนโดยที่เราจะกำหนด Relation ให้เป็น m:n เพราะ Pizza หนึ่งถาดสามารถมี Topping ได้หลายอย่าง และ Topping หนึ่งอย่างสามารถอยู่บน Pizza ได้หลายแบบ

~.domain.Pizza roo> field set --fieldName toppings --element ~.domain.Topping

หลังจากนั้นเราจะมากำหนด Relation ระหว่าง Pizza กับ Base โดยที่เราจะกำหนดให้เป็นแบบ 1:1

~.domain.Pizza roo> field reference --fieldName base --type ~.domain.Base

สุดท้ายสำหรับ Model เราจะสร้าง PizzaOrder ขึ้นมาเพื่อทำหน้าที่รับ Order จากลูกค้าโดยจะมีรายละเอียดตาม Command ดังนี้

entity --class ~.domain.PizzaOrder --testAutomatically
field string --fieldName name --notNull --sizeMin 2
field string --fieldName address --sizeMax 30
field number --fieldName total --type java.lang.Float
field date --fieldName deliveryDate --type java.util.Date
field set --fieldName pizzas --element ~.domain.Pizza

เมื่อเราได้ Model มาครบตามที่เราต้องการแล้ว ส่วนต่อไปที่เราจะทำคือการทำ Integration Test ซึ่งก็ยากจังๆ พิมพ์อีก Command

~.domain.PizzaOrder roo> perform tests
...
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running com.springsource.roo.pizzashop.domain.PizzaOrderIntegrationTest
Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.413 sec
Running com.springsource.roo.pizzashop.domain.ToppingIntegrationTest
Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.148 sec
Running com.springsource.roo.pizzashop.domain.PizzaIntegrationTest
Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.14 sec
Running com.springsource.roo.pizzashop.domain.BaseIntegrationTest
Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.097 sec

Results :

Tests run: 36, Failures: 0, Errors: 0, Skipped: 0

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESSFUL
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 17 seconds
[INFO] Finished at: Tue Dec 29 15:58:04 EST 2009
[INFO] Final Memory: 25M/79M
[INFO] ------------------------------------------------------------------------

เกือบสำเร็จแล้วครับ ถ้า Test ผ่านหมดเราก็ข้ามไปสร้าง Controller กันเลย แน่นอนผ่าน Command อีกแล้ว

~.domain.PizzaOrder roo> controller all --package ~.web

********* คำสั่งนี้บางครั้งทำครั้งเดียวไม่เสร็จ ต้องเรียกไปสาม สี่ครั้ง จนกระทั่ง Roo มันไม่สร้างอะไรออกมาอีกนั่นถึงแปลว่าเรียบร้อยแล้ว

จากนั้นให้เปิด Ternimal อันใหม่ขึ้นมาแล้วเข้าไปที่ pizza project จากนั้นสั่งให้มันทำงาน
Tomcat

>> mvn tomcat:run

Jetty

>> mvn jetty:run

ผลที่ได้คือ

SpringMVC ใน Spring 3.0 ง่ายจริงหรือ

December 23rd, 2009

Spring MVC ใน Spring 3.0 ง่ายจริงหรือ ??

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

หลังจากที่ Spring Framework 3.0 GA ปล่อยออกมาแล้ว ก็มีบทความต่างๆ ออกมา เช่น
- Spring Framework 3.0 goes GA
- Infoq :: What’s New in Spring 3.0
- TSS :: SpringSource takes Spring 3.0 to GA

และบทความ
MVC Simplifications in Spring 3.0 โดยผมอ้างอิงมาจากบทความนี้นะครับ มาดูกันว่า SpringMVC ใน Spring 3 มันง่ายจริงไหม ???

Let’s Go !!!!!

1. ว่าด้วยเรื่องการ configuration มีอะไรที่เปลี่ยนแปลงบ้าง

1.1 mvc:annotation-driven

...
<mvc:annotation-driven  />
...

เป็น tag สำหรับทำการ register HandlerMapping และ HandlerAdapter ลงใน request ที่จะส่งไปยัง @Controller และเพิ่มความสามารถอื่นๆ ไปด้วย ถ้ามี libraries ใน classpath เช่น
- สามารถใช้งาน Type ConversionService ในการแปลงชนิดของข้อมูล
- สนับสนุนการใช้งาน @NumberFormat สำหรับการจัดรูปแบบของข้อมูลชนิด Number
- สนับสนุนการใช้งาน @DateTimeFormat สำหรับการจัดรูปแบบของข้อมูลชนิด Date, Calendar และ Joda Time
- สนับสนุนการใช้งาน @Valid สำหรับ validate input ที่เข้ามายัง @Controller ( JSR 303 )
- สนับสนุนการอ่านและเขียน XML ด้วย JAXB
- สนับสนุนการอ่านและเขียน JSON ด้วย Jackson

1.2 mvc:view-controller

...
<mvc:view-controller path="/" view-name="welcome" />
...

กลไกภายในของ SpringMVC นั้นจะทำการ register [ "/" คู่กับ viewname ชื่อว่า welcome ] โดยที่ ParameterizableViewController จะทำการเลือก view มา render ต่อไป โดยจากตัวอย่างนั้น เมื่อมี request “/” เข้ามาจะทำการเลือก view ชื่อว่า welcome มา render กลับไป ซึ่ง view นั้นจะมีนามสกุลเป็น jsp เก็บไว้ใน folder ที่ config ไว้ในส่วน InternalResourceViewResolver [ ซับซ้อนดีจริงๆ ]

1.3 mvc:interceptors

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
</mvc:interceptors>

ทำการ register HandlerInterceptors ลงใน Controller ทุกๆ ตัว
จากตัวอย่างเป็นการเปลี่ยน Locale ของระบบ โดยส่ง parameter ชื่อว่า locale เข้ามายัง Controller

2. ว่าด้วยเรื่อง Data Binding และ Validation
มีบทความอธิบายแบบละเอียดที่นี่ Spring 3 Type Conversion and Validation

เป็นส่วนของการ binding และ validation ข้อมูลต่างๆ ที่ส่งเข้ามายัง Controller ซึ่งจะทำงานก่อนที่จะเข้าทำงานใน method ต่างๆ ของ Controller โดยสิ่งที่ทำประกอบไปด้วย
- ฺBinding ของมูลลงใน Model
- Conversion ข้อมูลก่อน binding ลงใน Model
- Validate ข้อมูลก่อน binding ลงใน Model
[ ดูๆ ไปเหมือน Conversion และ Validation phrase ใน JSF เลยนะครับ ]

ตัวอย่างของ method create ใน Controller

@RequestMapping(method=RequestMethod.POST)
public String create(@Valid Account account, BindingResult result) {
    if (result.hasErrors()) {
        return "account/createForm";
    }
    this.accounts.put(account.assignId(), account);
    return "redirect:/account/" + account.getId();
}

เมื่อมี request เข้ามายัง method create จะทำการ validate ข้อมูลที่ส่งเข้ามา กับ class Account [ จากการกำหนด @Valid ไว้ ] ถ้าเกิดข้อผิดพลาดก็จะส่งออกมายัง method นั้นๆ เลย

มาดูหน้าตาของ class Account ซึ่งเป็น model สำหรับเก็บข้อมูลว่าเป็นอย่างไร

public class Account {

	private Long id;

	@NotNull
	@Size(min=1, max=25)
	private String name;

	@NotNull
	@NumberFormat(style=Style.CURRENCY)
	private BigDecimal balance = new BigDecimal("1000");

	@NotNull
	@NumberFormat(style=Style.PERCENT)
	private BigDecimal equityAllocation = new BigDecimal(".60");

	@DateTimeFormat(style="S-")
	@Future
	private Date renewalDate = new Date(new Date().getTime() + 31536000000L);
...
...
...

จะเห็นได้ว่ามีการใช้งาน Annotation มากมาย เพื่อใช้สำหรับการ validate ข้อมุลในแต่ละ fields โดย Annotation ต่างๆ เช่น @NumberFormat, @DateTimeFormat เป็นต้น ซึ่งถูก register มาตั้งแต่ข้อ 1.1 แล้ว

ต่อมา

- ถ้าในขั้นตอนการ validate เกิดข้อผิดพลาดขึ้นมา จะทำการ render หน้า createForm ขึ้นมา
- ถ้าไม่มีข้อผิดพลาด จะทำการบันทึกข้อมูล และ redirect ไปยัง url /account/ ซึ่งจะเข้าทำงานใน method getView ใน Controller ดังนี้

@RequestMapping(value="{id}", method=RequestMethod.GET)
public String getView(@PathVariable Long id, Model model) {
	Account account = this.accounts.get(id);
	if (account == null) {
		throw new ResourceNotFoundException(id);
	}
	model.addAttribute(account);
	return "account/view";
}

- ใน method getView นี้จะรับจาก HTTP GET และมี parameter 1 ตัวชื่อว่า id ชนิดตัวเลข
- ใน method นี้มี feature หนึ่งที่น่าสนใจคือ ถ้าไม่เจอข้อมูลตาม id ที่ส่งเข้ามาแล้วจะโยน exception ออกมา

จากตัวอย่างคือ ResourceNotFoundException มีหน้าตาดังนี้

@ResponseStatus(value=HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {

	private Long resourceId;

	public ResourceNotFoundException(Long resourceId) {
		this.resourceId = resourceId;
	}

	public Long getResourceId() {
		return resourceId;
	}
}

ถ้าเกิด ResourceNotFoundException ขึ้นมาแล้วจะส่ง HTTP Status= 404 [ Page not found ] ออกไป โดยเราสามารถกำหนด error page ตาม HTTP Status ได้อีกด้วย ซึ่งเป็นความสามารถที่น่าสนใจไม่น้อยทีเดียวครับ

เท่าที่ทดสอบใช้งานมานั้น พบว่า SpringMVC ใน Spring 3.0 นั้นพยายามที่จะลดจำนวนการของ code รวมไปถึงการ configuration ลงไป แต่ก็ได้เพิ่ม Annotation เข้ามาอย่างมากกมายเช่นกัน ซึ่งมีทั้งข้อดีและข้อเสีย ตามแต่ละคนจะมองกันไปครับ แต่ถ้ามองว่ามันช่วยให้ง่ายขึ้น ก็ถือว่ามันมีประโยชน์ แต่ถ้ามองว่ามันยุ่งยากก็กลับไปใช้แบบเดิมก็ไม่แปลกครับ …

ส่วนผมจะลองใช้งานมันต่อไปครับ แล้วจะนำมาอธิบายในบทความต่อๆ ไปครับ ….

ส่วนตัวอย่างนั้น สามารถ checkout ออกมาจาก svn repositories ของ SpringSource ได้จากคำสั่ง

svn co https://src.springframework.org/svn/spring-samples/mvc-basic/trunk/

ปล. ในตัวอย่างนี้มีการใช้งาน UrlRewrite ด้วยนะครับ จะทำให้ url ที่ได้สวยงามยิ่งขึ้นครับ และนำมาอธิบายการใช้งานต่อไปครับ

SpringMVC: ตอนหนทางของ Request กว่าจะมาเป็น Response

November 21st, 2009

ใครเคยเขียน SpringMVC มาบ้างคงพอจะผ่านๆตามาบ้างกับ 6 ส่วนประกอบนี้
ซึ่งเมื่อจับมาวาดเป็นแผนภาพ ก็จะทำให้เข้าใจได้มากขึ้น
Diagram1
เริ่มต้นกันเลยดีกว่า

  1. มีใครสักคนส่ง Request เข้ามา ด่านแรกที่ต้องเจอคือ DispatcherServlet ถูกคอนฟิกไว้ใน web.xml ถ้า Reqest ตรงกับ url-pattern ที่กำหนด DispatcherServlet นี้ก็ทำงานไป
    * เรื่องชื่อของ DispatcherServlet ที่กำหนดไว้ก็สำคัญจะกล่าวต่อไป(รอไปก่อน)
  2. Request ที่เข้ามาถูกส่งไปตีความที่ HandlerMapping ว่า Controller ไหนจะเป็นผู้รับกรรมดี
    HandlerMapping มีอยู่หลายตัวเลือก ถ้าไม่เลือกเลย SpringMVC เองมีค่าพื้นฐานอยู่แล้วคือ BeanNameUrlHandlerMapping คู่กับ DefaultAnnotationHandlerMapping (สำหรับ Java5+) เขียนไปเดี่ยวก็งงมาดูตัวอย่างการใช้ BeanNameUrlHandlerMapping กันหน่อย

    <bean name="/home" class="com.springmvc.test2.mvc.HomeController" />
    

    อ่านชื่อของ BeanNameUrlHandlerMapping ก็คงพอรู้ว่ามันแมพกับ Controller ยังไง นั่นก็คือใช้ชื่อของ bean นั่นเอง

  3. หลังจาก DispatcherServlet รู้แล้วว่าเป็น Controller ตัวใด DispatcherServlet จะรอช้าอยู่ใย Controller ตัวนั้นก็ถูกเรียกทำงาน ผลลัพธ์จากการทำงานที่ได้คือ ModelAndView
  4. ModelAndView ประกอบด้วยสองส่วนคือ ชื่อของ View ซึ่งจะไปหากันต่อไปว่าชื่อที่ระบุนี้ตรงกับ View ตัวไหน ส่วน Model คือแมพอ็อพเจคที่จะเอาไปแสดงผลบน View
  5. หลังจากได้ชื่อ View มาแล้ว จะนำชื่อไปตีความที่ ViewResolver ซึ่งทำหน้าที่คล้ายคลึงกับ BeanNameUrlHandlerMapping คือแมพชื่อ View เข้ากับ View
  6. ตัวอย่างการแมพชื่อเข้ากับ View

    <bean name="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    
    	<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    
    	<property name="prefix" value="/WEB-INF/jsp/"/>
    
    	<property name="suffix" value=".jsp"/>
    
    </bean>
    

    Screenshot

  7. สั่งให้ View เรนเดอร์ผลลัพธ์เป็นอันจบ การเดินทางของ Request กว่าจะมาเป็น Response ได้เดินทางมาจนถึงจุดจบที่นี่เอง
  8. หมายเหตุ HandlerMapping และ ViewResolver สามารถเซ็ตการทำงานได้ในลักษณะของโช่(chain) คือมีได้หลายตัวสามารถเซ็ตลำดับผ่าน property order ไว้ต่อตอนหน้า
    หมายเหตุ2 ใครใช้ SpringMVC แบบ Annotation อาจไม่ได้เห็นภาพตรงตามนี้เนื่องจาก SpringMVC แบบ Annotation ช่วยลดข้อยุ่งยากในการคอนฟิก และที่เห็นชัดๆเลยคือการสร้าง Controller ตัดการ implements หรือ extends คลาสที่น่าสับสนออกไป ถ้าจำไม่ผิด Controller แบบเดิมมีตั้ง 7 ชนิด

Picture Credit: SpringInAction2