顯示具有 Spring 4 標籤的文章。 顯示所有文章
顯示具有 Spring 4 標籤的文章。 顯示所有文章
2015-03-05 10:24

用 Spring 取得 JSP 執行後的字串內容

為了取得執行後的字串內容,我們需要建立 Response 並且替換 PrintWriter,這樣才有辦法取得執行後的內容。

接著可以從 Spring 的 ViewResolver 去取得 View,再透過 View 去處理 render 前的包裝,最後才由 dispatcher 真正去處理 render 的動作。

想到要建立 Request 跟 Response 就感覺讓人頭痛,還好 Spring 有提供 Mock 的類別可以簡單地去建立 Request 跟 Response。


JspRenderer

package com.orion.webmvc.util;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.ui.Model;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;


public class JspRenderer {

    @Autowired
    private ServletContext servletContext;


    private ViewResolver viewResolver;

    public void setViewResolver(ViewResolver viewResolver) {
        this.viewResolver = viewResolver;
    }


    public String render(String viewName, Model model) 
        throws IOException 
    {
        return render(viewName, model.asMap());
    }

    public String render(String viewName, Map<String,Object> modelMap) 
        throws IOException 
    {

        RendererRequest request = new RendererRequest(servletContext);
        RendererResponse response = new RendererResponse();

        try {
            /* 透過 ViewResolver 取得 View 進行 render 的動作 */
            View view = viewResolver.resolveViewName(
                viewName, Locale.getDefault()
            );
            view.render(modelMap, request, response);

            return response.getContentAsString();
        }
        catch(Exception e) {
            throw new IOException(e);
        }
    }
}


class RendererRequest extends MockHttpServletRequest {

    private ServletContext servletContext;

    public RendererRequest(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String path) {
        /* 需要透過真實的 RequestDispatcher 進行 Render */
        return servletContext.getRequestDispatcher(path);
    }
}


class RendererResponse extends MockHttpServletResponse {

    private StringWriter writer = new StringWriter();

    @Override
    public PrintWriter getWriter() throws UnsupportedEncodingException {
        /* 用 StringWriter 作為輸出的容器 */
        return new PrintWriter(writer);
    }

    @Override
    public boolean isCommitted() {
        /* true 是為了讓 View 可以採用 include 方式 Render 到 Response */
        return true;
    }

    @Override
    public String getContentAsString() throws UnsupportedEncodingException {
        /* 取得 Render 後的內容 */
        return writer.getBuffer().toString();
    }
}


配置 spring.xml

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

<bean id="jspRenderer" class="com.orion.webmvc.util.JspRenderer">
    <property name="viewResolver" ref="viewResolver"/>
</bean>


使用範例

//@Autowired
//private JspRenderer jspRenderer;

Map<String,Object> jspMap = new HashMap<String,Object>();
jspMap.put("jspMessage", "中文訊息測試");
jspMap.put("costMessage", 4567.89);

String jspOutput = jspRenderer.render("/mailer/test", jspMap);
System.out.println(jspOutput);


參考自:Render and capture the output of a JSP as a String | Technological Oddity
2015-02-10 15:52

JAX-WS RI + Spring 4 自動 Exporting Service

Spring 4 的部分我就不多說了,請自行配置 jar 檔,這裡我用 jaxws-ri-2.2.10.zipjaxws-spring-1.9.jar 進行配置。


AccountService.java
package test.jaxws.webservice;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public interface AccountService {

    @WebMethod
    void insertAccount(String acc);

    @WebMethod
    String[] getAccounts(String role);
}
先來定義一個 interface,這主要是給 Client 用的。


AccountServiceImpl.java
package test.jaxws.webservice;

import javax.jws.WebMethod;
import javax.jws.WebService;
import org.springframework.stereotype.Component;

@Component
@WebService(serviceName="AccountService")
public class AccountServiceImpl implements AccountService {

    @Override
    @WebMethod
    public void insertAccount(String acc) {
    }

    @Override
    @WebMethod
    public String[] getAccounts(String role) {
        return new String[] {"tom", "jax"};
    }
}
接者是 Web Service 的主體,@Component 是為了讓 Spring component-scan 自動建立 Bean,@WebService 則是用定義要 Exporting 到 JAX-WS 的 serviceName,由於是透過 Spring 去管理 bean 所以可以用 @Autowired 引用其他資源。


JaxWsRtServletServiceExporter.java
package test.jaxws.webservice;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.jws.WebService;
import javax.xml.ws.WebServiceProvider;

import org.jvnet.jax_ws_commons.spring.SpringService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.CannotLoadBeanClassException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import com.sun.xml.ws.transport.http.servlet.SpringBinding;

public class JaxWsRtServletServiceExporter implements BeanFactoryAware, InitializingBean {

    private ListableBeanFactory beanFactory;
    private String basePath;

    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (!(beanFactory instanceof ListableBeanFactory)) {
            throw new IllegalStateException(getClass().getSimpleName() + " requires a ListableBeanFactory");
        }
        this.beanFactory = (ListableBeanFactory) beanFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.beanFactory instanceof SingletonBeanRegistry == false) { return; }

        SpringService service = new SpringService();

        Set<String> beanNames = new LinkedHashSet<String>(this.beanFactory.getBeanDefinitionCount());
        beanNames.addAll(Arrays.asList(this.beanFactory.getBeanDefinitionNames()));

        if (this.beanFactory instanceof ConfigurableBeanFactory) {
            beanNames.addAll(Arrays.asList(((ConfigurableBeanFactory) this.beanFactory).getSingletonNames()));
        }

        SingletonBeanRegistry factory = (SingletonBeanRegistry)this.beanFactory;

        for (String beanName : beanNames) {
            try {
                Class<?> type = this.beanFactory.getType(beanName);
                if (type == null || type.isInterface()) { continue; }

                WebService wsAnnotation = type.getAnnotation(WebService.class);
                WebServiceProvider wsProviderAnnotation = type.getAnnotation(WebServiceProvider.class);
                if (wsAnnotation == null && wsProviderAnnotation == null) { continue; }


                String serviceName;
                if (wsAnnotation != null) {
                    serviceName = wsAnnotation.serviceName();
                } else {
                    serviceName = wsProviderAnnotation.serviceName();
                }

                service.setImpl(type);
                service.setBean(this.beanFactory.getBean(beanName));

                SpringBinding springBinding = new SpringBinding();
                springBinding.setBeanName(serviceName + "Binding");
                springBinding.setUrl(this.basePath + "/" + serviceName);
                springBinding.setService(service.getObject());

                factory.registerSingleton(serviceName + "Binding", springBinding);
            }
            catch (CannotLoadBeanClassException ex) {
                // ignore beans where the class is not resolvable
            }
        }
    }
}
為了可以自動 Exporting Service 到 JaxWs RI Servlet 需要建立一個 Exporter,這裡將具有 @WebService 或 @WebServiceProvider 的 Bean 包裝成 SpringBinding 再註冊到 Spring BeanFactory 裡,這樣 WSSpringServlet 就會取到這邊註冊 SpringBinding Bean 進行 Exporting。


web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns="http://java.sun.com/xml/ns/javaee" 
 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
 version="3.0"
>
 <display-name>test-jaxws-rt</display-name>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/jaxws.xml</param-value>
 </context-param>

 <context-param>
     <param-name>webAppRootKey</param-name>
     <param-value>test-jaxws-rt.root</param-value>
   </context-param> 
 
 <servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/dispatcher.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>

    <servlet>
        <servlet-name>jaxws-servlet</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class>
  <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>jaxws-servlet</servlet-name>
        <url-pattern>/service/*</url-pattern>
    </servlet-mapping>
</web-app>
在 web.xml 同時註冊兩個 servlet,分別是 Spring MVC 的 DispatcherServlet 跟 JAX-WS 的 WSSpringServlet,這邊主要是透過 ContextLoaderListener 去啟用全域的 ApplicationContext 去管理所有的 Bean。


jaxws.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd"
>
    <context:component-scan base-package="test.jaxws.webservice" />

    <bean id="jaxWsRtServletServiceExporter" class="test.jaxws.webservice.JaxWsRtServletServiceExporter">
        <property name="basePath" value="/service"/>
    </bean>
</beans>
jaxws.xml 是給 ContextLoaderListener 的設定,這裡用 component-scan 去找出 test.jaxws.webservice 下有 @Component 的 Bean,以及用 JaxWsRtServletServiceExporter 去轉換所有的 @WebService 到 SpringBinding。


dispatcher.xml
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd"
>
    <context:component-scan base-package="test.jaxws.controller" />

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

    <bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean" scope="request">
        <property name="serviceInterface" value="test.jaxws.webservice.AccountService"/>
        <property name="wsdlDocumentUrl" value="http://localhost:8090/test-jaxws-rt/service/AccountService?wsdl"/>
        <property name="namespaceUri" value="http://webservice.jaxws.test/"/>
        <property name="serviceName" value="AccountService"/>
        <property name="portName" value="AccountServiceImplPort"/>
    </bean>
</beans>
在這裡用 JaxWsPortProxyFactoryBean 去建立 WebService Client,因為是同一個 Web Content 所以用 scope="request" 延遲建立。


HomeController.java
package test.jaxws.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.ModelAndView;

import test.jaxws.webservice.AccountService;

@Controller
public class HomeController {

    @Autowired
    private WebApplicationContext appContent;

    @RequestMapping("/")
    public ModelAndView index() {
        AccountService service = appContent.getBean("accountWebService", AccountService.class);
        String[] users = service.getAccounts("user");

        ModelAndView model = new ModelAndView("index");
        model.addObject("users", users);

        return model;
    }
}
接下來就是連接的 Client,這邊用 WebApplicationContext 延遲取得 AccountService,在 http://localhost:8080/test-jaxws-rt/service/AccountService 就可以看到 service 的主頁面。


參考資料:
JAX-WS + Spring integration example
22. Remoting and web services using Spring
2015-02-06 13:01

[轉載] spring 的配置文件中 mvc:view-controller path 使用方法

1、重定向
<mvc:view-controller path="/" view-name="redirect:/admin/index"/>
即如果当前路径是 / 则重定向到 /admin/index


2、view name
<mvc:view-controller path="/" view-name=admin/index"/>
如果当前路径是 / 则交给相应的视图解析器直接解析为视图


<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="2">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="contentType" value="text/html"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
则得到的视图时 /WEB-INF/jsp/admin/index.jsp


不想进 controller,可以在 spring-mvc.xml 中配置静态访问路径
<!-- 访问静态资源文件  -->
<mvc:resources mapping="/images/**" location="/images/" cache-period="31556926"/>

像这样,jsp 文件若放在静态路径 /images 下,可以直接访问,而不经过 controller。
2015-02-06 12:48

[轉載] Spring Bean Scope

轉載自:Spring Bean Scope 學習

在 Spring 中定義一個 Bean 時,可以針對其 scope 加以設定。在新版的 Spring 中,共有五種不同的 scope 可以設定,分別為:
singleton
在 Spring IoC Container,該 bean 只會有單一實例(a single instance),此為 Spring 預設值
prototype
在 Spring IoC Container 中,該 bean 可以有多個實例(any number of object instances)
request
在每一次的 HTTP Request,spring container 會根據 loginAction bean 的定義來建立一個全新的 instance,而且僅在目前的 request 中有效,所以可以放心的去更改 instance 的內部狀態,請求結束,request scope 的 bean instance 會被 destroy
session
針對某個 HTTP Session,spring container 會根據 userPreference bean 的定義來建立一個全新的 instance,同樣的,和 request scope 一樣,可以放心的去更改 instance 內部狀態。
global-session
僅在 portlet 為基礎的 Web 應用下有作用。Porlet 的規範中定義了 global session 的概念。


在定義 scope 時,會在 bean 中定義如下:
<bean id="helloWorld" class="HelloWorld" scope="singleton">

當 scope 設定為 singleton 時,整個 container 只會有一個 instance,所以下方的 obj 和 obj1 都會是同一個 instance。
ApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.setMessage("hello");
obj.getMessage();
HelloWorld obj1 = (HelloWorld) context.getBean("helloWorld");
obj1.getMessage();


<bean id="helloWorld" class="HelloWorld" scope="prototype">

如果設定為 prototype 時,obj 和 obj1 將會是不同的 instances,可以分別進行操作。在這裡,singleton 的單一 instance 是用 id 來進行識別,不同 id、相同 class 時,還是可以有多個 instances(getBean傳入不同的id即可)。


request、session 和 global-session 三種 scope 只能在 web 環境中使用,如果在非 web 環境設定,會出現【No Scope registered for scope 'request'】的錯誤。