spring boot开发笔记

只愿长相守 提交于 2020-11-13 14:01:54
单元测试

/**
 * 用户查询单测
 *
 */
@Test
public void whenQuerySuccess() throws Exception {
    String result = mockMvc.perform(
            get("/user")
                    .param("username", "jojo")
                    .param("age", "18")
                    .param("ageTo", "60")
                    .param("xxx", "yyy")
                    // .param("size", "15")
                    // .param("page", "3")
                    // .param("sort", "age,desc")
                    .contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.length()").value(3)) // 结果期望
            .andReturn().getResponse().getContentAsString();
    System.out.println(result);
}
@GetMapping
@JsonView(User.UserSimpleView.class)
@ApiOperation(value = "用户查询服务")
public List<User> query(UserQueryCondition condition,
      @PageableDefault(page = 2, size = 17, sort = "username,asc") Pageable pageable) {

   System.out.println(ReflectionToStringBuilder.toString(condition, ToStringStyle.MULTI_LINE_STYLE));
   System.out.println(pageable.getPageSize());
   System.out.println(pageable.getPageNumber());
   System.out.println(pageable.getSort());

   List<User> users = new ArrayList<>();
   users.add(new User());
   users.add(new User());
   users.add(new User());
   return users;
}

 

/**
     * 用户详情
     *
     * @param id 用户id
     *           :\d+ 只能接受数字
     */
    @GetMapping("/{id:\\d+}")
    @JsonView(User.UserDetailView.class)
    public User getInfo(@ApiParam("用户id") @PathVariable String id) {
//    throw new RuntimeException("user not exist");
        System.out.println("进入getInfo服务");
        User user = new User();
        user.setUsername("tom");
        return user;
    }
@Test
public void whenGetInfoFail() throws Exception {
    mockMvc.perform(get("/user/a")
            .contentType(MediaType.APPLICATION_JSON_UTF8))
            .andExpect(status().is4xxClientError());
}

 

@GetMapping("/{id:\\d+}") 与
@RequestMapping(value = "/{id:\\d+}",method = RequestMethod.GET) 等价

 

@JsonView使用步骤

  • 使用接口声明多个视图
// 用户简单视图
public interface UserSimpleView {};

// 用户详细视图
public interface UserDetailView extends UserSimpleView {};
  • 在值对象的get方法上指定视图
说明:在UserDetailView显示密码

@JsonView(UserSimpleView.class)
public String getUsername() {
   return username;
}

@JsonView(UserDetailView.class)
public String getPassword() {
   return password;
}
  • 在controller方法上指定视图
@GetMapping
@JsonView(User.UserSimpleView.class)

或者

@JsonView(User.UserDetailView.class)

 

常用验证注解

@NotBlank(message = "密码不能为空")
public User create(@Valid @RequestBody User user, BindingResult errors) {
    if (errors.hasErrors()) {
        errors.getAllErrors().forEach(error -> System.out.println(error.getDefaultMessage()));
    }

 

spring boot错误处理方式

org.springframework.boot.autoconfigure.web.BasicErrorControlle 

根据请求头中,选择返回形式

例如

@RequestMapping(produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request,
      HttpServletResponse response) {
   HttpStatus status = getStatus(request);
   Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
         request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
   response.setStatus(status.value());
   ModelAndView modelAndView = resolveErrorView(request, response, status, model);
   return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);
}

@RequestMapping
@ResponseBody
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
   Map<String, Object> body = getErrorAttributes(request,
         isIncludeStackTrace(request, MediaType.ALL));
   HttpStatus status = getStatus(request);
   return new ResponseEntity<Map<String, Object>>(body, status);
}

 

自定义404和500页面

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>404</title>
</head>
<body>
   您所访问的页面不存在
</body>
</html>

 

过滤器

/**
 *
 */
package com.imooc.web.filter;

import javax.servlet.*;
import java.io.IOException;
import java.util.Date;

/**
 * @author zhailiang
 */
//@Component
public class TimeFilter implements Filter {

    /* (non-Javadoc)
     * @see javax.servlet.Filter#destroy()
     */
    @Override
    public void destroy() {
        System.out.println("time filter destroy");
    }

    /* (non-Javadoc)
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("time filter start");
        long start = new Date().getTime();
        chain.doFilter(request, response);
        System.out.println("time filter 耗时:" + (new Date().getTime() - start));
        System.out.println("time filter finish");
    }

    /* (non-Javadoc)
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        System.out.println("time filter init");
    }

}

控制台

 

引入第三方过滤器

/**
 * 
 */
package com.imooc.web.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import com.imooc.web.filter.TimeFilter;
import com.imooc.web.interceptor.TimeInterceptor;

/**
 * @author zhailiang
 *
 */
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
   
   @SuppressWarnings("unused")
   @Autowired
   private TimeInterceptor timeInterceptor;
   
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
//    registry.addInterceptor(timeInterceptor);
   }
   
// @Bean
   public FilterRegistrationBean timeFilter() {
      
      FilterRegistrationBean registrationBean = new FilterRegistrationBean();
      
      TimeFilter timeFilter = new TimeFilter();
      registrationBean.setFilter(timeFilter);
      
      List<String> urls = new ArrayList<>();
      urls.add("/*");
      registrationBean.setUrlPatterns(urls);
      
      return registrationBean;
      
   }

}

 

拦截器

/**
 * 
 */
package com.imooc.web.interceptor;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

/**
 * @author zhailiang
 *
 */
@Component
public class TimeInterceptor implements HandlerInterceptor {

   /* (non-Javadoc)
    * @see org.springframework.web.servlet.HandlerInterceptor#preHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object)
    */
   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {
      System.out.println("preHandle");
      
      System.out.println(((HandlerMethod)handler).getBean().getClass().getName());
      System.out.println(((HandlerMethod)handler).getMethod().getName());
      
      request.setAttribute("startTime", new Date().getTime());
      return true;
   }

   /* (non-Javadoc)
    * @see org.springframework.web.servlet.HandlerInterceptor#postHandle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, org.springframework.web.servlet.ModelAndView)
    */
   @Override
   public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
         ModelAndView modelAndView) throws Exception {
      System.out.println("postHandle");
      Long start = (Long) request.getAttribute("startTime");
      System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));

   }

   /* (non-Javadoc)
    * @see org.springframework.web.servlet.HandlerInterceptor#afterCompletion(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
    */
   @Override
   public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
         throws Exception {
      System.out.println("afterCompletion");
      Long start = (Long) request.getAttribute("startTime");
      System.out.println("time interceptor 耗时:"+ (new Date().getTime() - start));
      System.out.println("ex is "+ex);

   }

}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
   
   @SuppressWarnings("unused")
   @Autowired
   private TimeInterceptor timeInterceptor;
   
   @Override
   public void addInterceptors(InterceptorRegistry registry) {
      registry.addInterceptor(timeInterceptor);
   }

总结:拦截器会拦截所有控制层接口,包括spring的

------------------------------------------------------------------

org.springframework.web.servlet.DispatcherServlet

org.springframework.web.servlet.DispatcherServlet#doService

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
   return;
}

// Actually invoke the handler. 真正调用处理器 (方法处理器在下面 handle()mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
   return;
}

拦截器缺陷:无法获取请求参数等信息,可以使用spring切片解决。

切片

 

/**
 * 
 */
package com.imooc.web.aspect;

import java.util.Date;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @author zhailiang
 *
 */
@Aspect
@Component
public class TimeAspect {
 
   @Around("execution(* com.imooc.web.controller.UserController.*(..))")
   public Object handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable {
      
      System.out.println("time aspect start");
      
      Object[] args = pjp.getArgs();
      for (Object arg : args) {
         System.out.println("arg is "+arg);
      }
      
      long start = new Date().getTime();
      
      Object object = pjp.proceed(); // 获取返回参数,但是拿不到request信息
      
      System.out.println("time aspect 耗时:"+ (new Date().getTime() - start));
      
      System.out.println("time aspect end");
      
      return object;
   }

}

总图

 

https://www.merriam-官网

官网:

webster.com/dictionary/wirework

模拟接口,给前端访问,前后端并行开发。

/**
 *
 */
package com.imooc.wiremock;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

/**
 *
 */
public class MockServer {

      public static void main(String[] args) throws IOException {
        configureFor(8062);
        removeAllMappings();

        mock("/order/1", "01");
        mock("/order/2", "02");
    }

    /**
     * @param url  文件url
     * @param file 文件名称
     */
    private static void mock(String url, String file) throws IOException {
        ClassPathResource resource = new ClassPathResource("mock/response/" + file + ".txt");
        String content = StringUtils.join(FileUtils.readLines(resource.getFile(), "UTF-8").toArray(), "\n");
        stubFor(get(urlPathEqualTo(url)).willReturn(aResponse().withBody(content).withStatus(200)));
    }

}

 

讲解上图过程:

地址栏输入一个请求:

http://localhost:9999/user

 org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke

if (fi.getRequest() != null) {
   fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}

InterceptorStatusToken token = super.beforeInvocation(fi);

try {
   fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
   super.finallyInvocation(token);
}

super.afterInvocation(token, null);

异常捕获

org.springframework.security.web.access.ExceptionTranslationFilter#doFilter

catch (Exception ex) {
   // Try to extract a SpringSecurityException from the stacktrace
   Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
   RuntimeException ase = (AuthenticationException) throwableAnalyzer
         .getFirstThrowableOfType(AuthenticationException.class, causeChain);

   if (ase == null) {
      ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
            AccessDeniedException.class, causeChain);
   }

   if (ase != null) {
      handleSpringSecurityException(request, response, chain, ase);
   }

异常处理会重定向到一个登陆页面

点击登录,到

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication

public Authentication attemptAuthentication(HttpServletRequest request,
      HttpServletResponse response) throws AuthenticationException {
   if (postOnly && !request.getMethod().equals("POST")) {
      throw new AuthenticationServiceException(
            "Authentication method not supported: " + request.getMethod());
   }

   String username = obtainUsername(request);
   String password = obtainPassword(request);

   if (username == null) {
      username = "";
   }

   if (password == null) {
      password = "";
   }

   username = username.trim();

   UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
         username, password);

   // Allow subclasses to set the "details" property
   setDetails(request, authRequest);

   return this.getAuthenticationManager().authenticate(authRequest);
}

账户密码校验ok,又会到org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke

这次对地址栏请求接口进行处理

 

public void invoke(FilterInvocation fi) throws IOException, ServletException {
   if ((fi.getRequest() != null)
         && (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
         && observeOncePerRequest) {
      // filter already applied to this request and user wants us to observe
      // once-per-request handling, so don't re-do security checking
      fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
   }
   else {
      // first time this request being called, so perform security checking
      if (fi.getRequest() != null) {
         fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
      }

      InterceptorStatusToken token = super.beforeInvocation(fi);

      try {
         fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
      }
      finally {
         super.finallyInvocation(token);
      }

      super.afterInvocation(token, null);
   }
}

doFilter()则进入后台控制层接口/user接口,并返回数据。

 

org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#obtainUsername

org.springframework.security.web.util.ThrowableAnalyzer#determineCauseChain

 

根据用户名查询

org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername

public interface UserDetailsService {
   // ~ Methods
   // ========================================================================================================

   /**
    * Locates the user based on the username. In the actual implementation, the search
    * may possibly be case sensitive, or case insensitive depending on how the
    * implementation instance is configured. In this case, the <code>UserDetails</code>
    * object that comes back may have a username that is of a different case than what
    * was actually requested..
    *
    * @param username the username identifying the user whose data is required.
    *
    * @return a fully populated user record (never <code>null</code>)
    *
    * @throws UsernameNotFoundException if the user could not be found or the user has no
    * GrantedAuthority
    */
   UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

 

用户逻辑校验

public SocialUser(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
   super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}

public SocialUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
   super(username, password, authorities);
}
new SocialUser(userId, password,
      true, true, true, true,
      AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));

 

密码加密


package org.springframework.security.crypto.password;

public interface PasswordEncoder {

   String encode(CharSequence rawPassword);

   boolean matches(CharSequence rawPassword, String encodedPassword);

}

 

实际

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}
@Autowired
private PasswordEncoder passwordEncoder;
String password = passwordEncoder.encode("123456"); 
logger.info("数据库密码是:"+password);   // 密文 每次不一样 会自动加盐 增加安全性

记住我

源码追踪

正向记住:

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication

org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#successfulAuthentication

protected void successfulAuthentication(HttpServletRequest request,
      HttpServletResponse response, FilterChain chain, Authentication authResult)
      throws IOException, ServletException {

   if (logger.isDebugEnabled()) {
      logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
            + authResult);
   }

   SecurityContextHolder.getContext().setAuthentication(authResult);

   rememberMeServices.loginSuccess(request, response, authResult);

   // Fire event
   if (this.eventPublisher != null) {
      eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
            authResult, this.getClass()));
   }

   successHandler.onAuthenticationSuccess(request, response, authResult);
}

org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices#onLoginSuccess

protected void onLoginSuccess(HttpServletRequest request,
      HttpServletResponse response, Authentication successfulAuthentication) {
   String username = successfulAuthentication.getName();

   logger.debug("Creating new persistent login for user " + username);

   PersistentRememberMeToken persistentToken = new PersistentRememberMeToken(
         username, generateSeriesData(), generateTokenData(), new Date());
   try {
      tokenRepository.createNewToken(persistentToken); // 创建新token
      addCookie(persistentToken, request, response);  // 写入浏览器
   }
   catch (Exception e) {
      logger.error("Failed to save persistent token ", e);
   }
}

 

校验登陆

org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter#doFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {
   HttpServletRequest request = (HttpServletRequest) req;
   HttpServletResponse response = (HttpServletResponse) res;

   if (SecurityContextHolder.getContext().getAuthentication() == null) {
      Authentication rememberMeAuth = rememberMeServices.autoLogin(request, // 调用自动登陆
            response);

      if (rememberMeAuth != null) {
         // Attempt authenticaton via AuthenticationManager
         try {
            rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);

            // Store to SecurityContextHolder
            SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

            onSuccessfulAuthentication(request, response, rememberMeAuth);

            if (logger.isDebugEnabled()) {
               logger.debug("SecurityContextHolder populated with remember-me token: '"
                     + SecurityContextHolder.getContext().getAuthentication()
                     + "'");
            }

            // Fire event
            if (this.eventPublisher != null) {
               eventPublisher
                     .publishEvent(new InteractiveAuthenticationSuccessEvent(
                           SecurityContextHolder.getContext()
                                 .getAuthentication(), this.getClass()));
            }

            if (successHandler != null) {
               successHandler.onAuthenticationSuccess(request, response,
                     rememberMeAuth);

               return;
            }

         }
         catch (AuthenticationException authenticationException) {
            if (logger.isDebugEnabled()) {
               logger.debug(
                     "SecurityContextHolder not populated with remember-me token, as "
                           + "AuthenticationManager rejected Authentication returned by RememberMeServices: '"
                           + rememberMeAuth
                           + "'; invalidating remember-me token",
                     authenticationException);
            }

            rememberMeServices.loginFail(request, response);

            onUnsuccessfulAuthentication(request, response,
                  authenticationException);
         }
      }

      chain.doFilter(request, response);
   }
   else {
      if (logger.isDebugEnabled()) {
         logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
               + SecurityContextHolder.getContext().getAuthentication() + "'");
      }

      chain.doFilter(request, response);
   }
}

org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices#processAutoLoginCookie

protected UserDetails processAutoLoginCookie(String[] cookieTokens,
      HttpServletRequest request, HttpServletResponse response) {

   if (cookieTokens.length != 2) {
      throw new InvalidCookieException("Cookie token did not contain " + 2
            + " tokens, but contained '" + Arrays.asList(cookieTokens) + "'");
   }

   final String presentedSeries = cookieTokens[0];
   final String presentedToken = cookieTokens[1];

   PersistentRememberMeToken token = tokenRepository
         .getTokenForSeries(presentedSeries); // 从数据库拿token

   if (token == null) {
      // No series match, so we can't authenticate using this cookie
      throw new RememberMeAuthenticationException(
            "No persistent token found for series id: " + presentedSeries);
   }
if (token == null) {
      // No series match, so we can't authenticate using this cookie
      throw new RememberMeAuthenticationException(
            "No persistent token found for series id: " + presentedSeries);
   }

   // We have a match for this user/series combination
   if (!presentedToken.equals(token.getTokenValue())) {
      // Token doesn't match series value. Delete all logins for this user and throw
      // an exception to warn them.
      tokenRepository.removeUserTokens(token.getUsername());

      throw new CookieTheftException(
            messages.getMessage(
                  "PersistentTokenBasedRememberMeServices.cookieStolen",
                  "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
   }

   if (token.getDate().getTime() + getTokenValiditySeconds() * 1000L < System
         .currentTimeMillis()) {
      throw new RememberMeAuthenticationException("Remember-me login has expired");
   }

   // Token also matches, so login is valid. Update the token value, keeping the
   // *same* series number.
   if (logger.isDebugEnabled()) {
      logger.debug("Refreshing persistent login token for user '"
            + token.getUsername() + "', series '" + token.getSeries() + "'");
   }

   PersistentRememberMeToken newToken = new PersistentRememberMeToken(
         token.getUsername(), token.getSeries(), generateTokenData(), new Date());

   try {
      tokenRepository.updateToken(newToken.getSeries(), newToken.getTokenValue(),
            newToken.getDate());
      addCookie(newToken, request, response);
   }
   catch (Exception e) {
      logger.error("Failed to update token: ", e);
      throw new RememberMeAuthenticationException(
            "Autologin failed due to data access problem");
   }

   return getUserDetailsService().loadUserByUsername(token.getUsername()); // 获取用户信息
}

登陆了

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!