Using Scoped Beans

Bean的Scope

Spring支持的Scope有: * singleton: 默认scope
* prototype
* thread
* request
* session
* globalSession
* application

在定义Bean的地方添加org.springframework.context.annotation.Scopeannotation,为定义的Bean指定不同的Scope。该annotation可以用在type-level和method-level上。
* type-level: 该类型的所有的Bean都是指定的Scope
* method-level: 当前方法所定义的Bean是指定的Scope

Scope Annotation的属性:
* value: 指定Bean的scope类型
* proxyModel: 指定是否需要proxy以及proxy的机制。ScopedProxyModel.TARGET_CLASS用于指定该Bean所对应的类型没有接口,需要默认值是NO

Example of session scope

配置session scope的Bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 package com.apress.prospringmvc.bookstore.web.config;
  //Other imports omitted
  import org.springframework.context.annotation.Scope;
  import org.springframework.context.annotation.ScopedProxyMode;
  import com.apress.prospringmvc.bookstore.domain.Cart;
  @Configuration
  @EnableWebMvc
  @ComponentScan(basePackages =      { "com.apress.prospringmvc.bookstore.web" })
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
//Other methods omitted
@Bean
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
  
public Cart cart() {
        return new Cart();
    }
}

使用SessionStatus管理session scope bean的状态

SessionStatus实例
1
2
3
4
5
6
7
8
9
10
11
12
 @RequestMapping(method = RequestMethod.POST, params = "order")      
 public String checkout(SessionStatus status,@Validated @ModelAttribute Order order,
                           BindingResult errors) {
        if (errors.hasErrors()) {
            return "cart/checkout";
        } else {
            this.bookstoreService.store(order);
            status.setComplete(); //remove order from session
            this.cart.clear(); // clear the cart
            return "redirect:/index.htm";
      }
}
Comments

Internationalization in Spring @MVC

Spring中i18n的组件

  1. org.springframework.context.MessageSource基于message code和Locale转换成相应的message
  2. org.springframework.web.servlet.LocaleResolver从session或者cookie里取的locale
  3. org.springframework.web.servlet.i18n.LocaleChangeInterceptor改变locale

MessageSource

MessageSource的实现

Spring在org.springframework.context.support包下提供了两个MessageSource的实现:
1. ResourceBundleMessageSource:使用JVM提供的ResourceBundle实现,只能在classpath里加载资源
2. ReloadableResourceBundleMessageSource:和ResourceBundleMessageSource非常类似,但是提供了自动重新加载缓存能力,以及可以从文件系统读取资源文件

配置MessageSource

配置MessageSource
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.apress.prospringmvc.bookstore.web.config;
import org.springframework.context.support.ReloadableResourceBundleMessageSource; // Other imports omitted
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.apress.prospringmvc.bookstore.web" })
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource;
        messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:/messages");
        messageSource.setUseCodeAsDefaultMessage(true);
        return messageSource;
} }
  • 配置一个name是messageSource的Bean
  • 该配置会从classpath加载所有的messages.properties和messages_[locale].properties文件

LocaleResolver

LocaleResolver用来判断使用什么Locale,Spring在org.springframework.web.servlet.i18n包里具有如下实现: * FixedLocaleResolver:固定Locale,不支持改变locale * SessionLocaleResolver * AcceptHeaderLocaleResolver:从Http头的accept里取的locale,一般和用户的操作系统一样,因此也不支持改变locale。是默认LocaleResolver * CookieLocaleResolver

LocaleChangeInterceptor

LocaleChangeInterceptor检查当前的request中是否有locale的参数,如果有,interceptor会使用配置的LocaleResolver去改变当前用户的locale。参数的名子(locale)是可配置的。

配置Spring的i18n

Spring的i18n配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.apress.prospringmvc.bookstore.web.config;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
// Other imports omitted
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.apress.prospringmvc.bookstore.web" })
public class WebMvcContextConfiguration extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
    @Bean
    public HandlerInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor localeChangeInterceptor;
        localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("lang");
        return localeChangeInterceptor;
}
    @Bean
    public LocaleResolver localeResolver() {
        return new CookieLocaleResolver();
    }
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource;
        messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasename("classpath:/messages");
        messageSource.setUseCodeAsDefaultMessage(true);
        return messageSource;
} }

注意:
* 23行的代码localeChangeInterceptor.setParamName(“lang”)用来设置request中的参数名
* 一般把LocaleChangeInterceptor配置为第一个interceptor,这样即使出现什么错误,任然能够改变用户的language


本文是基于Pro Spring MVC - With Web Flow的笔记,非原创

Comments

Validating Model Attributes in Spring @MVC

Spring的Validator接口

Spring的Validation主要接口是:org.springframework.validation.Validator

Validator接口
1
2
3
4
5
6
package org.springframework.validation;

public interface Validator {
    boolean supports(Class<?> clazz);
    void validate(Object target, Errors errors);
}
  • supports方法: 用来判断该validator是否可以验证对象。如果supports返回true,框架会调用validate方法去验证。
  • validate方法: 实现验证 在Spring @MVC里有两种方式触发验证:
    1. 把Validator注入到Controller,我们自己手工调validate方法。
    2. 在方法上添加javax.validation.Valid或者org.springframework.validation.annotation.Validatedannotation。Spring的annotation要比javax的强大些,可以指定hints和validation groups(如果和JSR-303validator联合使用)

需要注意的是,validation结果要是有错误的话,validator会返回message code,需要配置MessageSource来让错误消息显示的更有意义些。


实现一个Validator

需求

1
2
3
4
实现一个验证Account的Validator
username, password和email必填
email是正确的email地址
地址,城市和国家是必填的
AccountValidator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.apress.prospringmvc.bookstore.validation;

import java.util.regex.Pattern;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
import com.apress.prospringmvc.bookstore.domain.Account;

public class AccountValidator implements Validator {
    private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@"
                                              +"[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
    @Override
    public boolean supports(Class<?> clazz) {
        return (Account.class).isAssignableFrom(clazz);
    }
    @Override
    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmpty(errors, "username",
                                  "required", new Object[] {"Username"});
        ValidationUtils.rejectIfEmpty(errors, "password",
              "required", new Object[] {"Password"});
        ValidationUtils.rejectIfEmpty(errors, "emailAddress",
                                  "required", new Object[] {"Email Address"});
        ValidationUtils.rejectIfEmpty(errors, "address.street",
                                  "required", new Object[] {"Street"});
        ValidationUtils.rejectIfEmpty(errors, "address.city",
                                  "required", new Object[] {"City"});
        ValidationUtils.rejectIfEmpty(errors, "address.country",
                                  "required", new Object[] {"Country"});
        if (!errors.hasFieldErrors("emailAddress")) {
            Account account = (Account) target;
            String email = account.getEmailAddress();
            if (!emai.matches(EMAIL_PATTERN)) {
                errors.rejectValue("emailAddress", "invalid");
            }
      }
  }
}
  • 第14行的isAssignableFrom方法用于判断类Class1和另一个类Class2是否相同或是另一个类的超类或接口
  • ValidationUtils.rejectIfEmpty方法用来判断对象的属性是否为空,还有ValidationUtils.rejectIfEmptyOrWhiteSpace方法。但是判断属性是否为空还可以在org.springframework.web.bind.WebDataBinder里加以验证
  • errors.hasFieldErrors(“emailAddress”) 方法用来判断emailAddress是否有错误
  • errors.rejectValue用于手动往errors里添加一个错误
  • 这里的required都是Error Message的Key,需要Spring的框架处理
  • address.street是嵌套属性

配置并使用自定义Validator

AccountValidator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.apress.prospringmvc.bookstore.web.controller;
import com.apress.prospringmvc.bookstore.domain.AccountValidator;
import javax.validation.Valid;
// Other imports omitted
@Controller
@RequestMapping("/customer/register")
public class RegistrationController {
    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.setDisallowedFields("id");
        binder.setValidator(new AccountValidator());
}
@RequestMapping(method = { RequestMethod.POST, RequestMethod.PUT })
public String handleRegistration(@Valid @ModelAttribute Account account, BindingResult result) {
        if (result.hasErrors()) {
            return "customer/register";
        }
        this.accountService.save(account);
        return "redirect:/customer/account/" + account.getId();
    }
    // Other methods omitted
}
  • binder.setValidator(new AccountValidator())在Controller中将我们自定义的Validator注册一下
  • @Valid @ModelAttribute Account account绑定到Account上,这样当页面提交之后就会触发验证
  • BindingResult result在binding和validation中的错误信息都会放到BindingResult里去
  • 使用result.hasErrors()判断是否有错误(包括binding和validation)

使用JSR-303 Validation

JSR-303提供了一种方便的方式来进行validation,不需要我们自己去实现接口,也不需要在init-binder方法中注册,只需要在要验证的类上加上响应的annotation就可以了。

AccountValidator
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.apress.prospringmvc.bookstore.domain;

import java.util.Date;
import java.util.List;
import javax.validation.Valid;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;

public class Account {
    private Long id;
    private String firstName;
    private String lastName;
    private Date dateOfBirth;
    @Embedded
    @Valid
    private Address address = new Address();
    @NotEmpty
    @Email
    private String emailAddress;
    @NotEmpty
    private String username;
    @NotEmpty
    private String password;
    // getters and setters omitted
}
  • @Embedded表示嵌套对象
  • @NotEmpty验证不能为空
  • @Email验证Email地址的合法性
    这样看起来JSR-303简单很多

本文是基于Pro Spring MVC - With Web Flow的笔记,非原创

Comments