2018年8月26日 星期日

Spring Filter - RESTFUL Log

最直接的方式就在每個method內直接加入restful log

但這樣就太囉嗦

所以改用filter來處理這件事情

但由於request body只能被讀取一次的特性

所以filter要針對request動點手腳

@WebFilter(
    filterName="testFilter",
    urlPatterns={"/testRest"}
)
public class SpringRequestBodyFilter extends OncePerRequestFilter{
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(request);
        System.out.println("body: " + requestWrapper.getBody());

        filterChain.doFilter(requestWrapper, response);
    }
}

public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper{
    private final String body;

    public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        try(BufferedReader bufferedReader = request.getReader()){
            body = request.getReader().lines().collect(Collectors.joining());
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new DelegatingServletInputStream(new ByteArrayInputStream(body.getBytes()));
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String getBody() {
        return this.body;
    }
}
第10行將HttpServletRequest包裝成BodyReaderHttpServletRequestWrapper傳給filter

BodyReaderHttpServletRequestWrapper的功能就是先讀取request body並存到body欄位中
由於前面提到過request body只能讀取一次的特性
所以要重新建立inputStream和reader
若有用到inputStream或reader時才不會出現Stream closed的錯誤

第29行這邊我用org.springframework.mock.web.DelegatingServletInputStream
這個是用spring test的(要自己實作ServletInputStream也可以,但已有寫好的沒有不拿來用的理由)

完整程式可參考https://github.com/softmenlouis/springBoot-filter.git

Spring Filter

1.使用Java自己本身的Filter
@SpringBootApplication
@ServletComponentScan("test.annotation.filter")
public class SptringAnnotationFilterApplication {
    public static void main(String[] args) {
        SpringApplication.run(SptringAnnotationFilterApplication.class, args);
    }
}


@WebFilter(
    filterName="testFilter",
    urlPatterns={"/testRest"}
)
public class SpringAnnotationFilter implements Filter{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        // do something

        filterChain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}
SptringAnnotationFilterApplication
1. 設定@ServletComponentScan(filter的package)

SpringAnnotationFilter
1. implements javax.servlet.Filter
2. 設定@WebFilter(urlPatterns是設定哪些url要filter)
3. 改寫doFilter

這樣filter就可以生效

註:
若有多個filter時,在這邊使用Spring的@Order是沒有用的
真的要使用order的話可以參考下面第二種作法or使用package name去控制filter順序

2.使用Spring的FilterRegistrationBean
@SpringBootApplication
public class SpringBeanConfigFilterApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBeanConfigFilterApplication.class, args);
    }
}

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean<Filter> registFilter() {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new SpringBeanConfigFilter());
        registration.addUrlPatterns("/testRest");
        registration.setOrder(2);
        registration.setName("testFilter");
        return registration;
    }

    @Bean
    public FilterRegistrationBean<Filter> registFilter2() {
        FilterRegistrationBean<Filter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new SpringBeanConfigFilter2());
        registration.addUrlPatterns("/testRest");
        registration.setOrder(1);
        registration.setName("testFilter2");
        return registration;
    }
}

public class SpringBeanConfigFilter implements Filter{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        // do something

        filterChain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

public class SpringBeanConfigFilter2 implements Filter{
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
            throws IOException, ServletException {
        // do something

        filterChain.doFilter(request, response);
    }

    @Override
    public void destroy() {

    }
}

使用Spring bean的方式去做設定
註:此設定的方式可以設定filter order

完整程式可參考https://github.com/softmenlouis/springBoot-filter.git

2018年8月8日 星期三

Multiple MongoDB connectors

pom.xml配置
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.0.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
    <version>2.0.4.RELEASE</version>
</dependency>

Application設定
@SpringBootApplication(
    exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}
)
public class TestApplication {
    public static void main(String[] args) {
        SpringApplication.run(TestApplication.class, args);
    }
}
最重要的就是要設定 exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}

application.properties設定
mongodb.connect1.uri=mongodb://自己的連線設定1
mongodb.connect1.database=自己的database1

mongodb.connect2.uri=mongodb://自己的連線設定2
mongodb.connect2.database=自己的database2

Configurer設定
@Configuration
public class TestConfigurer{
    @Bean
    @ConfigurationProperties(prefix = "mongodb.connect1")
    public MongoProperties getConnectionSetting1() {
        return new MongoProperties();
    }

    @Bean
    @ConfigurationProperties(prefix = "mongodb.connect2")
    public MongoProperties getConnectionSetting2() {
        return new MongoProperties();
    }

    @Bean(name="Template1")
    public MongoTemplate mongoTemplate1(){
        MongoProperties p = getConnectionSetting1();
        
        return genMongoTemplateWithMongoProperties(p);
    }

    @Bean(name="Template2")
    public MongoTemplate mongoTemplate2(){
        MongoProperties p = getConnectionSetting2();

        return genMongoTemplateWithMongoProperties(p);
    }

    private MongoTemplate genMongoTemplateWithMongoProperties(MongoProperties p){
        MongoClientURI uri = new MongoClientURI(p.getUri());
        MongoClient client = new MongoClient(uri);

        SimpleMongoDbFactory f = new SimpleMongoDbFactory(client, p.getDatabase());
        return new MongoTemplate(f);
    }
}

Dao使用Template
@Repository
public class TestDao {
    @Autowired
    @Qualifier("Template1")
    private MongoTemplate mongoTemplate1;

    @Autowired
    @Qualifier("Template2")
    private MongoTemplate mongoTemplate2;
}

eclipse plus properties edit

eclipse properties編輯小工具 http://propedit.sourceforge.jp/index_en.html