這是關(guān)于使用Spring MVC創(chuàng)建Web API的另一個教程。這并不是一個非常精細的教程,而僅僅是一個演習(xí)(攻略)。本教程旨在創(chuàng)建一個提供服務(wù)器端API的應(yīng)用,并且使用Mongo作為它的數(shù)據(jù)庫,使用Spring Security作為安全框架。
準(zhǔn)備開始——POM
由于我是一個maven腦殘粉,所以這個項目還是基于maven的?,F(xiàn)在Spring 4.0 RC2已經(jīng)發(fā)布了,所以我決定使用最新的依賴管理工具。本文使用的pom.xml如下:使用這個配置創(chuàng)建Spring MVC應(yīng)用確實非常簡單。這里面比較新鮮的東西就是dependencyManagement 元素。詳解猛戳這兒:http:///blog/2013/12/03/spring-framework-4-0-rc2-available
配置
這個應(yīng)用可以使用JavaConfig完成配置。我把它切分為下面幾個部分:
ServicesConfig(服務(wù)配置)
無需掃描組件,配置真的非常簡單:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Configuration
public class ServicesConfig {
@Autowired
private AccountRepository accountRepository;
@Bean
public UserService userService() {
return new UserService(accountRepository);
}
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
|
PersistenceConfig(持久層配置)
我們想要一個配置了所有可用倉庫的MONGODB配置。在這個簡單的應(yīng)用中我們只用了一個倉庫,所以配置也非常的簡單:
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 | @Configuration
class PersistenceConfig {
@Bean
public AccountRepository accountRepository() throws UnknownHostException {
return new MongoAccountRepository(mongoTemplate());
}
@Bean
public MongoDbFactory mongoDbFactory() throws UnknownHostException {
return new SimpleMongoDbFactory( new Mongo(), "r" );
}
@Bean
public MongoTemplate mongoTemplate() throws UnknownHostException {
MongoTemplate template = new MongoTemplate(mongoDbFactory(), mongoConverter());
return template;
}
@Bean
public MongoTypeMapper mongoTypeMapper() {
return new DefaultMongoTypeMapper( null );
}
@Bean
public MongoMappingContext mongoMappingContext() {
return new MongoMappingContext();
}
@Bean
public MappingMongoConverter mongoConverter() throws UnknownHostException {
MappingMongoConverter converter = new MappingMongoConverter(mongoDbFactory(), mongoMappingContext());
converter.setTypeMapper(mongoTypeMapper());
return converter;
}
}
|
SecurityConfig(安全配置)
理論上,Spring Security 3.2完全可以使用JavaConfig。但對于我這也僅僅是一個理論,所以這里還是選擇xml配置的方式:
1 2 3 | @Configuration
@ImportResource ( "classpath:spring-security-context.xml" )
public class SecurityConfig {}
|
使用這個xml就讓API能使用基本的安全機制了。
WebAppInitializer(初始化)
我們不想使用web.xml,所以使用下面的代碼配置整個應(yīng)用:
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 | @Order ( 2 )
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected String[] getServletMappings() {
return new String[]{ "/" };
}
@Override
protected Class[] getRootConfigClasses() {
return new Class[] {ServicesConfig. class , PersistenceConfig. class , SecurityConfig. class };
}
@Override
protected Class[] getServletConfigClasses() {
return new Class[] {WebMvcConfig. class };
}
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding( "UTF-8" );
characterEncodingFilter.setForceEncoding( true );
return new Filter[] {characterEncodingFilter};
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setInitParameter( "spring.profiles.active" , "default" );
}
}
|
WebAppSecurityInitializer (安全配置初始化)
相對于Spring3,可以使用下面這種更加新穎的特性來完成配置:
1 2 | @Order ( 1 )
public class WebAppSecurityInitializer extends AbstractSecurityWebApplicationInitializer {}
|
WebMvcConfig (Mvc配置)
調(diào)度控制器配置。這個也非常簡單,僅僅包含了構(gòu)建一個簡單API的最重要配置:
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 | @Configuration
@ComponentScan (basePackages = { "pl.codeleak.r" }, includeFilters = { @Filter (value = Controller. class )})
public class WebMvcConfig extends WebMvcConfigurationSupport {
private static final String MESSAGE_SOURCE = "/WEB-INF/i18n/messages" ;
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping requestMappingHandlerMapping = super .requestMappingHandlerMapping();
requestMappingHandlerMapping.setUseSuffixPatternMatch( false );
requestMappingHandlerMapping.setUseTrailingSlashMatch( false );
return requestMappingHandlerMapping;
}
@Bean (name = "messageSource" )
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasename(MESSAGE_SOURCE);
messageSource.setCacheSeconds( 5 );
return messageSource;
}
@Override
public Validator getValidator() {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.setValidationMessageSource(messageSource());
return validator;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
|
這就是需要的配置,非常簡單吧!
IndexController (INDEX控制器)
為了驗證這個配置是正確的,我創(chuàng)建了一個IndexController。功能非常簡單,只是簡單地返回“Hello World”,示例代碼如下:
1 2 3 4 5 6 7 8 9 | @Controller
@RequestMapping ( "/" )
public class IndexController {
@RequestMapping
@ResponseBody
public String index() {
return "This is an API endpoint." ;
}
}
|
如果運行一下這個應(yīng)用,就能夠在瀏覽器中看到返回的“Hello World”文本。
構(gòu)建API
UserService
為了完成Spring安全框架配置,還需要完成另一個部分:實現(xiàn)之前創(chuàng)建的UserService。
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 | public class UserService implements UserDetailsService {
private AccountRepository accountRepository;
public UserService(AccountRepository accountRepository) {
this .accountRepository = accountRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountRepository.findByEmail(username);
if (account == null ) {
throw new UsernameNotFoundException( "user not found" );
}
return createUser(account);
}
public void signin(Account account) {
SecurityContextHolder.getContext().setAuthentication(authenticate(account));
}
private Authentication authenticate(Account account) {
return new UsernamePasswordAuthenticationToken(createUser(account), null , Collections.singleton(createAuthority(account)));
}
private User createUser(Account account) {
return new User(account.getEmail(), account.getPassword(), Collections.singleton(createAuthority(account)));
}
private GrantedAuthority createAuthority(Account account) {
return new SimpleGrantedAuthority(account.getRole());
}
}
|
構(gòu)建一個API節(jié)點需要處理三個方法:獲取當(dāng)前登陸用戶、獲取所有用戶(可能不是太安全)、創(chuàng)建一個新賬戶。那么我們就按照這個步驟來進行吧。
Account
Account 將會是我們的第一個Mongo文檔。同樣也是非常簡單:
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 | @SuppressWarnings ( "serial" )
@Document
public class Account implements java.io.Serializable {
@Id
private String objectId;
@Email
@Indexed (unique = true )
private String email;
@JsonIgnore
@NotBlank
private String password;
private String role = "ROLE_USER" ;
private Account() {
}
public Account(String email, String password, String role) {
this .email = email;
this .password = password;
this .role = role;
}
// getters and setters
}
|
Repository
先創(chuàng)建一個接口:
1 2 3 4 5 6 7 | public interface AccountRepository {
Account save(Account account);
List findAll();
Account findByEmail(String email);
}
|
接下來創(chuàng)建它的Mongo實現(xiàn):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | public class MongoAccountRepository implements AccountRepository {
private MongoTemplate mongoTemplate;
public MongoAccountRepository(MongoTemplate mongoTemplate) {
this .mongoTemplate = mongoTemplate;
}
@Override
public Account save(Account account) {
mongoTemplate.save(account);
return account;
}
@Override
public List findAll() {
return mongoTemplate.findAll(Account. class );
}
@Override
public Account findByEmail(String email) {
return mongoTemplate.findOne(Query.query(Criteria.where( "email" ).is(email)), Account. class );
}
}
|
API控制器
功能快要完成了。我們需要將內(nèi)容提供給用戶,所以需要創(chuàng)建自己的節(jié)點:
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 39 40 41 42 43 44 | @Controller
@RequestMapping ( "api/account" )
class AccountController {
private AccountRepository accountRepository;
@Autowired
public AccountController(AccountRepository accountRepository) {
this .accountRepository = accountRepository;
}
@RequestMapping (value = "current" , method = RequestMethod.GET)
@ResponseStatus (value = HttpStatus.OK)
@ResponseBody
@PreAuthorize (value = "isAuthenticated()" )
public Account current(Principal principal) {
Assert.notNull(principal);
return accountRepository.findByEmail(principal.getName());
}
@RequestMapping (method = RequestMethod.GET)
@ResponseStatus (value = HttpStatus.OK)
@ResponseBody
@PreAuthorize (value = "isAuthenticated()" )
public Accounts list() {
List accounts = accountRepository.findAll();
return new Accounts(accounts);
}
@RequestMapping (method = RequestMethod.POST)
@ResponseStatus (value = HttpStatus.CREATED)
@ResponseBody
@PreAuthorize (value = "permitAll()" )
public Account create( @Valid Account account) {
accountRepository.save(account);
return account;
}
private class Accounts extends ArrayList {
public Accounts(List accounts) {
super (accounts);
}
}
}
|
我希望你能明白:因為需要直接連接數(shù)據(jù)庫,所以沒有對密碼進行編碼。如果你比較在意這些小細節(jié),那么可以稍后修改。目前這種方式是OK的。
完成
最后我考慮到還需要一個錯誤處理器,這樣用戶就可以看到JSON格式的錯誤信息而不是HTML。使用Spring Mvc以及@ControllerAdvice很容易實現(xiàn)這一點:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @ControllerAdvice
public class ErrorHandler {
@ExceptionHandler (value = Exception. class )
@ResponseStatus (HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorResponse errorResponse(Exception exception) {
return new ErrorResponse(exception.getMessage());
}
}
public class ErrorResponse {
private String message;
public ErrorResponse(String message) {
this .message = message;
}
public String getMessage() {
return message;
}
}
|
如果你想了解關(guān)于Spring4 中@ControllerAdvice的用法,請點擊以下鏈接。
測試一下這個app
作為一個單元測試極客,本來應(yīng)該先創(chuàng)建一個單元測試。但是……這次哥想用用新的工具:Postman(Chrome 插件),所以請往下看我是怎么做的:
獲取所有account(非授權(quán))
提交account(無需授權(quán))
獲取所有account(已授權(quán))
獲取當(dāng)前account(已授權(quán))
結(jié)束語
以上就是所有內(nèi)容,希望你能像我一樣喜歡這種創(chuàng)建項目的方式。創(chuàng)建這個項目以及寫這篇文章總共花了我大概三個鐘頭。其中絕大多數(shù)時間是配置安全框架(我希望它在Java中能夠更加徹底),以及編寫這篇攻略。
原文鏈接: javacodegeeks 翻譯: ImportNew.com - 鄔柏 譯文鏈接: http://www./7903.html [ 轉(zhuǎn)載請保留原文出處、譯者和譯文鏈接。]
|