แสดงบทความที่มีป้ายกำกับ spring แสดงบทความทั้งหมด
แสดงบทความที่มีป้ายกำกับ spring แสดงบทความทั้งหมด

วันอาทิตย์ที่ 12 ธันวาคม พ.ศ. 2553

เชื่อม Struts กับ EJB3 ด้วย Spring

ลงไว้ที่ spring66 ด้วย ลงบล็อกส่วนตัวด้วยจะได้มีอะไรอัพเดทบ้าง =="

ได้มีโอกาศสอนการใช้ Struts 1.x ร่วมกับ EJB3 จริงๆอยากให้เค้าใช้ Spring MVC มากกว่า แต่กลัวเกิดปัญหาเรื่องความเชี่ยวชาญและความคุ้นเคยหากมาใช้ Spring MVC เดิมเค้าใช้ Struts อยู่แล้ว ไม่แน่ใจว่าระยะเวลาในการปรับให้เข้ากับเฟรมเวิร์คใหม่ด้วย เดี๋ยวมันจะออกทะเลเหมือนที่เกิดกับ Struts

ทีนี้การเชื่อม Struts กับ EJB3 จริงๆทำได้ไม่ยากคือ lookup JNDI ผ่าน InitialContext แต่การ lookup ทุกครั้งที่ใช้มันทำให้สมรรถนะออกมาไม่ดี วิธีส่วนใหญ่ที่ใช้กันคือสร้าง Delegate คลาสขึ้นมาเพื่อทำหน้าที่ในการติดต่อ EJB โดยเก็บ remote/local ที่ lookup เอาไว้ จะได้ไม่เสียเวลาเรียกใหม่

class  DelegateService {

private UserService userService;
//many EJB service
...

private DelegateService() {}

private static getEJB(String ejbName) {
.....
// process to call EJB (Session Bean)
.....
}
public static void getUserService() {
if (userService == null)
userService = getEJB("CustomerServiceBean/remote");
return userService;
}
...
}


เวลาเรียกใช้ Delegate ใน Action ก็จะได้ประมาณนี้

public class UserAction extends Action {
...
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
UserService service = DelegateService.getUserService();
...
}
}


ซึ่งตรงนี้เราจะใช้ Spring จัดการ โดยให้ String ติดต่อไปยัง EJB แทนหลังจากนั้นเอาอินสแตนท์ที่ได้ฉีดให้กับ Struts action โค้ดจะออกมาสะอาดขึ้น ตัว Action ก็ไม่ขึ้นกับ Delegate คลาส การทำ Unit testing ทำได้ง่ายขึ้นเยอะ และนอกจากนี้ยังสามารถใช้ Spring เป็นจุดเชื่อมต่อกับเทคโนโลยีหรือเฟรมเวิร์คอื่นได้อีกด้วย เขียนจาวาไม่ใช้ spring บาปใช่มั๊ย ^__^
Struts action ก็จะออกมาประมาณนี้

@Service("/users")
public class UserAction extends Action {
@Autowired
private UserService service;
...
}


ในตัวอย่าง UserAction เป็น Struts action และเป็น Spring bean ด้วย(จาก Annotation Service ของ Spring) ส่วน UserService คืออินเทอร์เฟสคลาสซึ่งอิมพลิเม็นท์อาจเป็น Session bean หรือเป็น POJO Service ที่อยู่ในฝั่ง web เองก็ได้ขึ้นกับการคอนฟิค Spring ว่าจะให้ฉีดอะไรลงมา

เข้าเรื่องการเชื่อม Spring เข้ากับ Struts มีสองวิธี

1. ActionSupport Classes
เริ่มด้วยวิธีง่ายๆก่อน Spring ได้เตรียม WebApplicationContextUtils เพื่อดึง bean ภายใต้ Spring context อีกที

ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
MyService service = (MyService) ctx.getBean("myService");


ต้องคอนฟิค Spring context ใน web.xml ด้วย

<web-app>
...
<listener>

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

</listener>

<context-param><!-- Doesn't need but config for mapping other config -->

<param-name>contextConfigLocation</param-name><!-- This parameter is specified as a list of paths -->

<param-value>

/WEB-INF/applicationContext.xml

</param-value>

</context-param>

</web-app>


เพื่อให้ง่ายต่อการใช้งาน Spring ได้เตรียมคลาสสำหรับ Struts action พื้นฐานต่างๆไว้คือ ActionSupport, DispatchActionSupport, LookupDispatchActionSupport และ MappingDispatchActionSupport คลาสเหล่านี้มีเครื่องมืออำนวจความสะดวกต่างๆเช่นเมธตอด getWebApplicationContext() เพื่อเรียก WebApplicationContext

public class UserAction extends DispatchActionSupport {

public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (log.isDebugEnabled()) {
log.debug("entering 'delete' method...");
}
WebApplicationContext ctx = getWebApplicationContext();
UserManager mgr = (UserManager) ctx.getBean("userManager");
// talk to manager for business logic
return mapping.findForward("success");
}
}


จะเห็นได้ว่าวิธีนี้เรียบง่ายแต่ไม่ค่อยมีพลังเท่าไหร่ ซึ่งในวิธีหลังนี้เราจะสร้าง Struts action ให้เป็น Spring bean ทำให้สามารถใช้ความสามารถต่างของ Spring กับ Action ของเราได้เช่นทำ Dependency Injection, AOP ฯลฯ

2. ContextLoaderPlugin
วิธีนี้ Struts action จะถูกสร้างและจัดการโดย Spring ทำให้สามารถใช้ความสามารถต่างๆของ Spring กับ Struts action ของเราได้

Spring เตรียม plugin สำหรับ Struts เพื่อโหลด Spring context โดยคอนฟิค struts-config.xml ดังนี้

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn"/>


กรณีไม่กำหนดไฟล์คอนฟิค Spring จะใช้ค่าพื้นฐานดังนี้ /WEB-INF/${action}-servlet.xml ซึ่ง action คือชื่อ servlet-name ของ ActionServlet ของ Struts ใน web.xml นั่นเอง หากต้องการระบุไฟล์คอนฟิคก็ทำได้ดังนี้

<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/action-servlet.xml,/WEB-INF/applicationContext.xml"/>
</plug-in>


หลังจากคอนฟิค plugin เสร็จขั้นตอนต่อไปเป็นการแม็พระหว่าง Struts กับ Spring มีสองวิธี แต่ทั้งคู่ใช้วิธีการแม็พที่เหมือนกันคือ Struts จะใช้ action path แม็พเข้ากับ bean name ของ Spring

struts-config.xml
<action path="/users" .../>


action-servlet.xml
<bean name="/users" class="com.whatever.struts.UserAction" .../>


2.1 DelegatingRequestProcessor
วิธีนี้อาศัยการปรับแต่ง controller ของ Struts โดยเซ็ต processorClass ใหม่ซึ่ง Spring เตรียมไว้คือ DelegatingRequestProcessor

struts-config.xml
<controller>
<set-property property="processorClass"
value="org.springframework.web.struts.DelegatingRequestProcessor"/>
</controller>


action path จะถูกแม็พเข้ากับ bean name ของ Spring การคอนฟิค action ของ Struts จึงไม่จำเป็นต้องกำหนด action type

<action path="/user" type="com.whatever.struts.UserAction"/>
<action path="/user"/><!-- type ไม่จำเป็น -->


ถ้าใช้ Struts module ต้องเติม module prefix หน้า bean name ของ Spring ด้วยเช่น <action path="/user"/> ที่มี module ชื่อ admin จะต้องกำหนด bean name ดังนี้ <bean name="/admin/user"/>

ถ้าใช้ Tiles กับ Struts ให้เปลี่ยน DelegatingRequestProcessor ไปใช้ DelegatingTilesRequestProcessor

2.2. DelegatingActionProxy
ใช้กรณีที่มีการปรับแต่ง controller ด้วย RequestProcessor ตัวอื่นทำให้ไม่สามารถใช้ DelegatingRequestProcessor หรือ DelegatingTilesRequestProcessor ได้ ทางแก้ก็คือแก้ไข RequestProcessor ใหม่เลียนแบบ DelegatingRequestProcessor หากไม่สามารถทำได้ ให้ใช้ DelegatingActionProxy เป็น action type แทนดังนี้

<action path="/user" type="org.springframework.web.struts.DelegatingActionProxy"/>


จบการเชื่อม Spring กับ Struts ขอสรุปอีกทีเป็นตัวอย่างการใช้วิธี 2.1 ร่วมกับ Spring Annotation
web.xml คอนฟิค Struts ตามปกติ

struts-config.xml
คอนฟิค plugin
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml"/>
</plug-in>

คอนฟิค controller
<controller>
<set-property property="processorClass"
value="org.springframework.web.struts.DelegatingRequestProcessor"/>
</controller>

คอนฟิค action
<action path="/user" ...>
<forward name="success" .../>
<forward name="failure" .../>
</action>


applicationContext.xml
<context:component-scan base-package="com.whatever.struts.actions" /> <!-- ระบุแพคเกจที่จะให้ Spring เข้ามองหา -->
<jee:remote-slsb id="userService" business-interface="com.whatever.ejb.service.UserService" jndi-name="UserServiceBean/remote" /><!-- lookup หา EJB -->


com.whatever.struts.actions.UserAction.java
@Service("/users") // สร้าง Struts Action ให้เป็น Spring bean และแม็พเข้ากับ action path ของ Struts
public class UserAction extends Action {
@Autowired
private UserService service;
...
}


ใช้ maven กำหนด library ที่จะใช้
pom.xml
...
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts-core</artifactId>
<version>${org.apache.struts}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts-taglib</artifactId>
<version>${org.apache.struts}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-struts</artifactId>
<version>${org.springframework.version}</version>
<exclusions>
<exclusion>
<groupId>struts</groupId>
<artifactId>struts</artifactId>
</exclusion>
</exclusions>
</dependency>
...


สั้นๆง่ายๆ แต่มีง่ายกว่านี้คือใช้ Spring MVC ซะ ชาบูๆ

วันศุกร์ที่ 20 พฤศจิกายน พ.ศ. 2552

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

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

เริ่มต้นกันเลยดีกว่า

  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.gable.train.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>


  7. สั่งให้ View เรนเดอร์ผลลัพธ์เป็นอันจบ การเดินทางของ Request กว่าจะมาเป็น Response ได้เดินทางมาจนถึงจุดจบที่นี่เอง

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

Picture Credit: SpringInAction2

วันอังคารที่ 28 กรกฎาคม พ.ศ. 2552

Transaction annotation-driven แล้ว Spring สร้างอะไรให้บ้าง

การใช้งาน @Transactional จะต้องกำหนดใน Spring Context ดังนี้

<tx:annotation-driven transaction-manager="transactionManager" />

มาดูกันว่า มันสร้างอะไรให้บ้าง

<aop:aspectj-autoproxy />
<bean id="transactionAttributeSource" class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource" />
<bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">
<property name="transactionManager" ref="transactionManager" />
<property name="transactionAttributeSource" ref="transactionAttributeSource" />
</bean>
<bean id="org.springframework.transaction.config.internalTransactionAdvisor" class="org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor">
<property name="transactionAttributeSource" ref="transactionAttributeSource" />
<property name="adviceBeanName" value="transactionInterceptor" />
</bean>

บรรทัดแรกสั่งให้ Spring ทำ auto proxy จาก Advisor ที่มีการคอนฟิกใน Spring Context
ที่ เหลือเป็นการคอนฟิก Advisor, Interceptor(เป็น Advice) และ TransactionAttributeSource ที่ทำหน้าที่ บอกว่า method ของ class ที่ถูกเรียกนี้มี TransactionAttribute ยังไง
ตัวที่บอกรายละเอียด Transaction พวก propagation, isolation, timeout ฯลฯ อยู่ที่ TransactionAttribute นี่แหละ

อันนี้เฉพาะโหมด proxy ของ annotation-driven ยังไม่ไล่โหมด aspectj แต่มันก็คือๆกันแหละแค่เปลี่ยนการทำ proxy จากใช้ Proxy ของ JDK เป็น cglib แทน

วันเสาร์ที่ 25 กรกฎาคม พ.ศ. 2552

ดูการสร้าง Bean ของ Spring JavaConfig

เริ่มแรกดูการคอนฟิก Spring Context ก่อนจากเดิมใช้ XML เป็น Java + Annotation

@Configuration
class MyConfig {
public @Bean MyBean myBean() {
new MyBean();
}
}


เพื่อให้ง่ายต่อความเข้าใจจะใช้ XML ร่วมกับ ConfigurationPostProcessor เพื่อทำ Bootstrapping JavaConfig

<beans>
<!-- first, define your individual @Configuration classes as beans -->
<bean class="myapp.MyConfig"/>

<!-- be sure to include the JavaConfig bean post-processor -->
<bean class="org.springframework.config.java.process.ConfigurationPostProcessor"/>
</beans>

จะเห็นได้ว่ามีการกำหนด MyConfig เป็น Spring Bean ร่วมกับใช้ ConfigurationPostProcessor ซึ่งเป็น BeanFactoryPostProcessor ชนิดหนึ่ง

การสร้าง Spring Bean แบบปกติตัวที่ทำหน้าที่สร้าง Instance Bean คือ InstantiationStrategy

Spring JavaConfig ก็ใช้หลักการเดียวกัน แต่จะใช้การสร้าง bean ผ่าน Factory Method นั่นคือ MyConfig.myBean() จะทำหน้าที่เป็น Factory Method นั่นเอง หากแปลงเป็น XML จะได้ประมาณนี้

<bean id="myconfig" class="myapp.MyConfig" />
<bean id="myBean" factory-bean="myconfig" factory-method="myBean" />


ส่วนเหตุที่มานั่งไล่ Spring JavaConfig เพราะกำลังเขียน BeanDefinitionParser อยากให้ Instance ที่สร้างใน BeanDefinitionParser ด้วยการ new Object ธรรมดานี่แหละขึ้นมาเป็น Spring Bean เลยมาดูสักหน่อยว่า Spring JavaConfig ใช้วิธีไหน

วันพุธที่ 15 กรกฎาคม พ.ศ. 2552

Distribution Spring, maven repository, and SpringIDE

You can find those on "Amazon S3 Web Frontend".
Distribution Spring at dist.springframework.org, in addition, you can find SpringIDE updatesite here too.
Spring Maven repository at maven.springframework.org.
Enjoy your spring anytime.