SpringMVC
1、SpringMVC常用注解
https://blog.csdn.net/lipinganq/article/details/79155072
https://blog.csdn.net/javazejian/article/details/54561302
用于依赖注入:@Autowired、@Resource、@Value
用于Bean定义:@Component、@Controller、@Service、@Repository、@Scope、@RequestMapping(@GetMapping、@PostMapping....)、@ResponseBody
@Controller、@Service、@Repository三者与@Component等效。
1、@Component
@Component
是所有受Spring 管理组件的通用形式,@Component注解可以放在类的头上,@Component不推荐使用。
2、@Controller
@Controller对应表现层的Bean,也就是Action,例如:
1 @Controller 2 @Scope("prototype") 3 public class UserAction extends BaseAction<User>{ 4 …… 5 }
使用@Controller注解标识UserAction之后,就表示要把UserAction交给Spring容器管理,在Spring容器中会存在一个名字为"userAction"的action,这个名字是根据UserAction类名来取的。注意:如果@Controller不指定其value【@Controller】,则默认的bean名字为这个类的类名首字母小写,如果指定value【@Controller(value="UserAction")】或者【@Controller("UserAction")】,则使用value作为bean的名字。
这里的UserAction还使用了@Scope注解,@Scope("prototype")表示将Action的范围声明为原型,可以利用容器的scope="prototype"来保证每一个请求有一个单独的Action来处理,避免struts中Action的线程安全问题。spring 默认scope是单例模式(scope="singleton"),这样只会创建一个Action对象,每次访问都是同一Action对象,数据不安全,struts2 是要求每次次访问都对应不同的Action,scope="prototype" 可以保证当有请求的时候都创建一个Action对象
3、@ Service
@Service对应的是业务层Bean,例如:
1 @Service("userService") 2 public class UserServiceImpl implements UserService { 3 ……… 4 }
@Service("userService")注解是告诉Spring,当Spring要创建UserServiceImpl的的实例时,bean的名字必须叫做"userService",这样当Action需要使用UserServiceImpl的的实例时,就可以由Spring创建好的"userService",然后注入给Action:在Action只需要声明一个名字叫“userService”的变量来接收由Spring注入的"userService"即可,具体代码如下:
1 // 注入userService 2 @Resource(name = "userService") 3 private UserService userService;
注意:在Action声明的“userService”变量的类型必须是“UserServiceImpl”或者是其父类“UserService”,否则由于类型不一致而无法注入,由于Action中的声明的“userService”变量使用了@Resource注解去标注,并且指明了其name = "userService",这就等于告诉Spring,说我Action要实例化一个“userService”,你Spring快点帮我实例化好,然后给我,当Spring看到userService变量上的@Resource的注解时,根据其指明的name属性可以知道,Action中需要用到一个UserServiceImpl的实例,此时Spring就会把自己创建好的名字叫做"userService"的UserServiceImpl的实例注入给Action中的“userService”变量,帮助Action完成userService的实例化,这样在Action中就不用通过“UserService userService = new UserServiceImpl();”这种最原始的方式去实例化userService了。如果没有Spring,那么当Action需要使用UserServiceImpl时,必须通过“UserService userService = new UserServiceImpl();”主动去创建实例对象,但使用了Spring之后,Action要使用UserServiceImpl时,就不用主动去创建UserServiceImpl的实例了,创建UserServiceImpl实例已经交给Spring来做了,Spring把创建好的UserServiceImpl实例给Action,Action拿到就可以直接用了。Action由原来的主动创建UserServiceImpl实例后就可以马上使用,变成了被动等待由Spring创建好UserServiceImpl实例之后再注入给Action,Action才能够使用。这说明Action对“UserServiceImpl”类的“控制权”已经被“反转”了,原来主动权在自己手上,自己要使用“UserServiceImpl”类的实例,自己主动去new一个出来马上就可以使用了,但现在自己不能主动去new“UserServiceImpl”类的实例,new“UserServiceImpl”类的实例的权力已经被Spring拿走了,只有Spring才能够new“UserServiceImpl”类的实例,而Action只能等Spring创建好“UserServiceImpl”类的实例后,再“恳求”Spring把创建好的“UserServiceImpl”类的实例给他,这样他才能够使用“UserServiceImpl”,这就是Spring核心思想“控制反转”,也叫“依赖注入”,“依赖注入”也很好理解,Action需要使用UserServiceImpl干活,那么就是对UserServiceImpl产生了依赖,Spring把Acion需要依赖的UserServiceImpl注入(也就是“给”)给Action,这就是所谓的“依赖注入”。对Action而言,Action依赖什么东西,就请求Spring注入给他,对Spring而言,Action需要什么,Spring就主动注入给他。
4、@ Repository
@Repository对应数据访问层Bean ,例如:
1 @Repository(value="userDao") 2 public class UserDaoImpl extends BaseDaoImpl<User> { 3 ……… 4 }
@Repository(value="userDao")注解是告诉Spring,让Spring创建一个名字叫“userDao”的UserDaoImpl实例。
当Service需要使用Spring创建的名字叫“userDao”的UserDaoImpl实例时,就可以使用@Resource(name = "userDao")注解告诉Spring,Spring把创建好的userDao注入给Service即可。
1 // 注入userDao,从数据库中根据用户Id取出指定用户时需要用到 2 @Resource(name = "userDao") 3 private BaseDao<User> userDao;
@Scope
标记在类和方法,标记上述Spring Bean的作用域:singleton、prototype、request、session、application
- singleton:单例,无论getBean多少次得到的都是同一个实例
- prototype:每次getBean都创建一个新的实例
- request:每个http请求创建一个实例,该实例仅在该请求内有效且该请求过程中用同一个实例,不同request创建的实例互不干扰;实例随请求结束而销毁
- session:没当创建一个会话时创建一个实例,实例随着会话的结束而销毁
@Service、@Repository、@Component等默认即此
Java Singleton与Spring Singleton的区别:Java singleton class is per classloader and Spring’s singleton is per application context. 更多可参考https://javabeat.net/spring-singleton-java-singleton/
接收请求参数和页面传参
转自:http://blog.csdn.net/z69183787/article/details/41653875
2、接收请求请求
(1)使用HttpServletRequest获取,如request.getParameter("name")
(2)@RequestParam("pass")String password,或@Param("pass")String password。表单参数也可以用这种方式获取,Spring会自动将表单参数注入到方法参数,和表单的name属性保持一致。
(3)自动注入Bean属性
3、向页面传参
(1)使用HttpServletRequest 和 Session 然后setAttribute(),就和Servlet中一样
(2)使用ModelAndView对象
(3)使用ModelMap对象
(4)使用@ModelAttribute注解
4、异常捕获处理
@ExceptionHandler:捕获特定异常进行处理,仅对当前Controller有效
@ControllerAdvice:修饰类,捕获全局各个Controller抛出的异常。ControllerAdvice注解只拦截Controller不会拦截Interceptor的异常。
示例:
@Controller @RequestMapping("/exception") @ControllerAdvice public class ExceptionController { @ExceptionHandler({ ArithmeticException.class }) // 单用@ExceptionHandler时只对当前controller有效即只捕获当前Controller抛出的异常,结合@ControllerAdvice可对全局有效。 // @ResponseStatus(value = HttpStatus.NOT_FOUND) @ResponseBody public String handleArithmeticException(Exception e) { e.printStackTrace(); return "some arighmetric error"; } @RequestMapping(value = "e/{id}", method = { RequestMethod.GET }) @ResponseBody public String testExceptionHandle(@PathVariable(value = "id") Integer id) { System.out.println(10 / id); return id.toString(); } } @Controller class TTT { @RequestMapping(value = "e/{id}", method = { RequestMethod.GET }) @ResponseBody public String testExceptionHandle(@PathVariable(value = "id") Integer id) { System.out.println(10 / id); return id.toString(); } }
该示例中,若没有@ControllerAdvice注解,则分别访问 http://localhost:8081/exception/e/0 、 http://localhost:8081/e/0 前者会被@ExceptionController指定的方法捕获处理、后者则不会被捕获。若加上@ControllerAdvice注解则访问两个路径都会被处理。
另外注意,类需要在能被spring扫描到的包下。
5、request body传数组
public Integer deleteAdminAnStus(@RequestBody List<String> studentIdList, HttpServletRequest request) {} //前端请求时直接在body传类似如下格式的数据即可: ["a","b"]
6、页面重定向
假设当前在 /api/v1,则
response.sendRedirect("/test"); 将导向 /test
response.sendRedirect("test"); 将导向 /api/test
7、手动返回错误信息
response.sendError(400, "some error");
8、返回数据的统一包装
1 @ControllerAdvice // 拦截所有Controller的处理结果 2 public class ControllerResponseWrapper implements ResponseBodyAdvice<Object> { 3 private static final Logger logger = LoggerFactory.getLogger(ControllerResponseWrapper.class); 4 5 private final Set<MediaType> jsonMediaType = new HashSet<>( 6 Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON_UTF8)); 7 8 // 对于哪些请求要执行beforeBodyWrite,返回true执行,返回false不执行 9 @Override 10 public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) { 11 return true; 12 } 13 14 @Override 15 public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType mediaType, 16 Class<? extends HttpMessageConverter<?>> converterType, ServerHttpRequest request, 17 ServerHttpResponse response) { 18 19 // 不是需要验证的路径,不处理 20 // String reqURLPath = request.getURI().getPath(); 21 // if (!reqURLPath.startsWith(WebSecurityConfig.AUTH_PATH_PREFIX)) {//发送500时reqURL被重定向到/error了,所以此法不可行 22 // } 23 // 类型 不属于 需要处理的包头的时候,不处理 24 if (!jsonMediaType.contains(mediaType)) { 25 } 26 // 当类型 是属于需要处理的时候 并且 obj不是ReturnMsg的时候 进行格式化处理 27 else if (obj == null || !(obj instanceof ReturnMsg)) { 28 if (isExceptionOccur(obj)) {// 1.说明请求失败,返回了默认错误处理DefaultHandlerExceptionResolver的结果 29 Map<String, Object> res = (Map<String, Object>) obj; 30 int status = (int) res.get("status"); 31 String errorMsg = (String) res.get("error") + ": " + res.get("message"); 32 String path = (String) res.get("path"); 33 34 obj = ReturnMsg.fail(status, errorMsg, path, request.getMethodValue()); 35 36 } else {// 2.说明请求成功 37 ReturnMsg newRes = ReturnMsg.success(obj, request.getURI().getPath(), request.getMethodValue()); 38 if (obj instanceof String) {// 对于String类型默认会用org.springframework.http.converter.StringHttpMessageConverter处理,所以ReturnMsg类型obj会出错 39 try { 40 obj = new ObjectMapper().writeValueAsString(newRes); 41 } catch (JsonProcessingException e) { 42 // TODO Auto-generated catch block 43 e.printStackTrace(); 44 } 45 } else { 46 obj = newRes; 47 } 48 } 49 } 50 // 返回结果已经是包装格式 51 else { 52 } 53 54 logger.info("Req from {}: {}", request.getRemoteAddress(), obj); 55 return obj; 56 } 57 58 /** 根据返回结果判断是否是发ere生异常返回的,正常结果也可能是这个格式,所以检查得严格! */ 59 private static boolean isExceptionOccur(Object obj) { 60 /** 61 * 默认的Spring异常处理DefaultHandlerExceptionResolver返回格式为:<br> 62 * { <br> 63 * "timestamp":"2018-06-19T06:19:57.757+0000",<br> 64 * "status":400,<br> 65 * "error":"Bad Request",<br> 66 * "message":"Failed to convert value of type 'java.lang.String' to required type 'java.lang.Integer'",<br> 67 * "path":"/api/v1/student/recent_experiment"<br> 68 * } 69 */ 70 71 // 不是Map则肯定不是异常 72 if ((obj == null) || !(obj instanceof Map<?, ?>)) { 73 return false; 74 } 75 76 // 不符合异常的格式,则不是异常。由于正常返回数据也有可能这几个字段,所以检查要严格点 77 @SuppressWarnings("unchecked") 78 Map<String, Object> mapObj = (Map<String, Object>) obj; 79 if (mapObj.get("path") == null && mapObj.get("timestamp") == null || mapObj.get("status") == null) { 80 return false;// 发生异常时 error 和 message 有可能为null 81 } 82 // 发生异常时status肯定不是200 83 if ((Integer) (mapObj.get("status")) == 200) { 84 return false; 85 } 86 return true; 87 } 88 }
通过实现ResponseBodyAdvice接口的beforeBodyWrite方法来完成,在方法里包装Controller返回数据srcRes得到 自定义统一数据格式ApiResMsg的数据finalRes 后返回。(注:只能捕获到Controller类里方法返回的结果)
Controller方法返回的srcRes为String类型时是种特殊情况,需特殊处理:
beforeBodyWrite方法返回的数据将会被converterType所表示的MessageConverter处理后输出给前端,在SpringMVC中定义了9个MessageConverter来处理返回数据。其中:
org.springframework.http.converter.json.MappingJackson2HttpMessageConverter:可以处理任意类型数据:若是基本数据类型则原样输出、对于Map、Java Bean则会转成JSON输出(对于Map输出所有键值对、Java Bean输出所有getXxx方法的xxx即输出的参数名为xxx而不是field名故可以输出Bean里不存在的属性名)
class org.springframework.http.converter.StringHttpMessageConverter:只能处理String类型的数据,若输入非String类型显然会报错。
对于一个Controller中的方法来说,其返回值srcRes会被哪种MessageConverter处理取决于srcRes的类型:
对于@RestController(@Controller+@ResponseBody)下的方法来说,通常(除了String类型)其返回的content-type=application/json,beforeBodyWrite返回值finalRes将被converterType=MappingJackson2HttpMessageConverter处理
然而当srcRes为String类型时是特例,其content-type=text/plain, converterType=StringHttpMessageConverter(猜测不与上述一样的原因是SpringMVC中返回String类型的srcRes可以代表一个view?)
可见,srcRes是String时converterType为StringHttpMessageConverter,若直接在beforeBodyWrite里将其包装为ApiResMsg类型的finalRes,则会报错。解决:此时可以在beforeBodyWrite里进一步将finalRes转为String类型,然而仍存在问题,因为srcRes为null时无法知道srcRes的类型,终极解法:如示例代码所示,根据converterType确定是否将finalRes转为String类型。
相关见:https://my.oschina.net/u/1757225/blog/1543715
9、@Value注入静态域
默认@Value是注入到实例域的,要注入到静态域,可以通过如下方法:
@Service //或者@Compoent、@Controller等,以便使@Value生效 class GlobalValue { public static String hostname; @Value("${sensestudy.redis.host}") public void setMyHost(String h) { hostname = h; } }
10、上传文件并附带额外参数
其实就是借助form表单来实现。
后端代码:
@PostMapping(value = "/api/v1/admin/students") @PreAuthorize(("hasRole('ROLE_ADMIN')")) public List<String> addAdminStudents(@RequestParam UserEntity userEntity, @RequestParam("stuFile") List<MultipartFile> files, @RequestParam("isOverride") boolean isOverride, HttpServletRequest request) throws Exception { // List<MultipartFile> files = ((MultipartHttpServletRequest) request).getFiles("stuFile"); String adminId = controllerUtils.getUserIdFromJwt(request); List<StudentBeanForAddAccount> stuBeanList = resolveStudentAccountFile(files); return studentService.addStudentAccounts(adminId, stuBeanList, isOverride); }
前端逻辑:构造个form表单放所需参数(包括stuFile、isOverride)提交即可;若有很多个非文件域参数,则后端可以声明个@RequestParam UserEntity userEntity来接收这些非文件域参数。
进阶:有时候额外参数可能是个JSON,怎么办?
可以将JSON转成String然后作为form表单的一个字段;
或者将JSON里的各字段拿出来分别作为form表单的一个字段传输,但若JSON有多层则此法行不通了。
另外须注意:form表单只能用POST方法
11、@Autoweird、@Resource、@Inject都可以用于依赖注入,其区别:
https://www.sourceallies.com/2011/08/spring-injection-with-resource-and-autowired/#more-2350
‘@Autowired’ and ‘@Inject’ annotation behave identically. Both of these annotations use the ‘AutowiredAnnotationBeanPostProcessor’ to inject dependencies. ‘@Autowired’ and ‘@Inject’ can be used interchangeable to inject Spring beans. However the ‘@Resource’ annotation uses the ‘CommonAnnotationBeanPostProcessor’ to inject dependencies. Even though they use different post processor classes they all behave nearly identically.
12、事务@Transational
Java Throwable分为Error和Exception,Exception分为Unchecked Exception(包括RuntimeException类及其子类)和Checked Exception(包括Exception类自身)。
Spring的AOP即声明式事务管理@Transactional默认是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚,对checked Exception即Exception可try{}捕获的不会回滚。 因此若在API中实现自定义异常则最好继承RuntimeException以在出错时能进行回滚。若想针对Checked Exception进行事务回滚,可在@Transactional 注解里使用 rollbackFor 属性明确指定异常,如: @Transactional(rollbackFor = Exception.class)
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为只有在使用基于接口的代理时它才会生效。
@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
可参考:https://www.jianshu.com/p/380a9d980ca5
13、代码中添加静态资源
在 Spring MVC 中,资源的查找、处理使用的是责任链设计模式(Filter Chain):
其思路为如果当前 resolver 找不到资源,则转交给下一个 resolver 处理。 当前 resolver 找到资源则立即返回给上级 resovler(如果存在),此时上级 resolver 又可以选择对资源进一步处理或再次返回给它的上级(如果存在)。
配置方法为重写 WebMvcConfigurerAdapter 类的 addResourceHandlers:
@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/webjars/**") .addResourceLocations( "classpath:/META-INF/resources/webjars/"); }
这段代码实际上是添加了一个 PathResourceResolver来完成对资源的查找,该 resolver 的作用是将 url 为 /webjars/** 的请求映射到 classpath:/META-INF/resources/webjars/。
比如请求 http://localhost:8080/webjars/jquery/3.1.0/jquery.js 时, Spring MVC 会查找路径为 classpath:/META-INF/resources/webjars/jquery/3.1.0/jquery.js 的资源文件。
使用了 @EnableWebMvc 注解后 WebMvcAutoConfiguration 提供的默认配置会失效,必须提供全部配置。想要使用默认配置,无需使用 @EnaleWebMvc 注解。
14、过滤器(Filter)与拦截器(Interceptor)
区别:
Filter 接口定义在 javax.servlet 包中;HandlerInterceptor接口定义在org.springframework.web.servlet 包中
Filter在 Servlet 规范中定义,依赖于Servlet容器,被Servlet容器(如Tomcat)调用;Interceptor不依赖于Servlet容器,是Spring框架的一个组件,归Spring IoC容器管理调用。因此可以通过注入等方式来获取其他Bean的实例,使用更方便。
Filter是基于函数回调的,而Interceptor则是基于动态代理(Java反射)的。
Filter只能在请求的前后使用,而Interceptor可以详细到每个方法(即handler)
Filter对几乎所有的请求起作用(init、doFilter、destroy),而Interceptor只能对handler请求起作用(preHandle、postHandle、afterCompletion)
Interceptor可以访问handler的上下文,值栈里的对象,而Filter不能。
注:SpringBoot(SpringMVC)里的Handler特指@Controller注解的类里每个处理HTTP请求的public method
执行顺序:过滤前-拦截前-handler执行-拦截后-过滤后
示例:
//这里通过如下两个注解使过滤器生效。也可以不用注解,通过FilterRegistrationBean添加过滤器 @Component @WebFilter(urlPatterns = "/Blogs", filterName = "blosTest") class TestFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.err.println("filter .. init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; System.out.println("filter 请求前"); filterChain.doFilter(request, response); System.out.println("filter 请求后"); } @Override public void destroy() { System.err.println("filter .. destroy"); } }
1 import javax.servlet.http.HttpServletRequest; 2 import javax.servlet.http.HttpServletResponse; 3 4 import org.springframework.context.annotation.Configuration; 5 import org.springframework.web.servlet.HandlerInterceptor; 6 import org.springframework.web.servlet.ModelAndView; 7 import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 8 import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 10 @Configuration 11 public class LoginInterceptorConfigurer implements WebMvcConfigurer { 12 @Override 13 public void addInterceptors(InterceptorRegistry registry) { 14 // 多个拦截器组成一个拦截器链 15 // addPathPatterns 用于添加拦截规则 16 // excludePathPatterns 用户排除拦截 17 registry.addInterceptor(new MyInterceptor1()).addPathPatterns("/**"); 18 registry.addInterceptor(new MyInterceptor2()).addPathPatterns("/**"); 19 } 20 } 21 22 class MyInterceptor1 implements HandlerInterceptor { 23 24 @Override 25 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 26 throws Exception { 27 System.out.println(">>>MyInterceptor1>>>>>>>在请求处理之前进行调用(Controller方法调用之前)"); 28 29 return true;// 只有返回true才会继续向下执行,返回false取消当前请求 30 } 31 32 @Override 33 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, 34 ModelAndView modelAndView) throws Exception { 35 System.out.println(">>>MyInterceptor1>>>>>>>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)"); 36 } 37 38 @Override 39 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 40 throws Exception { 41 System.out.println(">>>MyInterceptor1>>>>>>>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)"); 42 } 43 44 } 45 46 class MyInterceptor2 implements HandlerInterceptor { 47 48 @Override 49 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 50 throws Exception { 51 System.out.println(">>>MyInterceptor2>>>>>>>在请求处理之前进行调用(Controller方法调用之前)"); 52 53 return true;// 只有返回true才会继续向下执行,返回false取消当前请求 54 } 55 56 @Override 57 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, 58 ModelAndView modelAndView) throws Exception { 59 System.out.println(">>>MyInterceptor2>>>>>>>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后)"); 60 } 61 62 @Override 63 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 64 throws Exception { 65 System.out.println(">>>MyInterceptor2>>>>>>>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)"); 66 } 67 68 } 69 70 71 72 //输出如下 73 74 >>>MyInterceptor1>>>>>>>在请求处理之前进行调用(Controller方法调用之前) 75 >>>MyInterceptor2>>>>>>>在请求处理之前进行调用(Controller方法调用之前) 76 2018-12-07 17:43:48.043 [http-nio-8080-exec-1] INFO c.s.s.r.ControllerResponseWrapper - Req from /0:0:0:0:0:0:0:1:34712: { errorCode:1000, errorMsg:ok, path:/api/v1/admin_super/courses, method:GET, timestamp:2018-12-07 17:43:48 } 77 >>>MyInterceptor2>>>>>>>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后) 78 >>>MyInterceptor1>>>>>>>请求处理之后进行调用,但是在视图被渲染之前(Controller方法调用之后) 79 >>>MyInterceptor2>>>>>>>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作) 80 >>>MyInterceptor1>>>>>>>在整个请求结束之后被调用,也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
参考资料:
https://segmentfault.com/a/1190000012072060
http://einverne.github.io/post/2017/08/spring-interceptor-vs-filter.html
过滤器触发两次
将Filter定义成一个Bean并通手动注册到SpringSecurity时( .addFilterBefore(myFilter, UsernamePasswordAuthenticationFilter.class) //这里 myFilter通过 @Autowired MyFilter myFilter; 定义 ),此时如果调用一个handler一次则会触发两次该Filter。
原因:SpringBoot对于任何一个bean都会自动注册,加上我们手动注册的,这样该Filter就被注册了两次。解决:
法1:不让Filter成为一个Bean:若该Filter里面没有依赖需要自动注入的Bean,则可以不将该Filter定义为Bean,这样往SpringSecurity注册的filter 通过new Filter()产生而不是Autowired,从而不会注册两次。
法2:禁止SpringBoot自动注册该Bean,可以在该Filter里加上如下代码:
@Bean public FilterRegistrationBean registration(MyFilter filter) {// 本filter将手动注册到SpringSecurity。但SpringBoot会自动注册任何一个bean组件,这样导致注册两次从而每次调用都会触发两次,故通过此来让SpringBoot别自动注册此filter FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setEnabled(false); return registration; }
法3:继承OncePerRequestFilter来实现Filter,这样虽然注册了两次,但可以保证只执行一次。implements Filter 和 extends OncePerRequestFilter两种写法的示例分别如下:
1 @Component 2 @WebFilter(urlPatterns = "/Blogs", filterName = "blosTest") 3 class TestFilter1 extends OncePerRequestFilter { 4 5 @Autowired 6 CustomerCourseService customerCourseService; 7 8 @Override 9 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, 10 FilterChain filterChain) throws ServletException, IOException { 11 // you business 12 // customerCourseService.getByCustomerId(""); 13 } 14 15 @Override 16 public void destroy() { 17 System.err.println("filter .. destroy"); 18 } 19 } 20 21 @Component 22 @WebFilter(urlPatterns = "/Blogs", filterName = "blosTest") 23 class TestFilter2 implements Filter { 24 25 @Autowired 26 CustomerCourseService customerCourseService; 27 28 @Override 29 public void init(FilterConfig filterConfig) throws ServletException { 30 31 } 32 33 @Override 34 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 35 throws IOException, ServletException { 36 // you business 37 // customerCourseService.getByCustomerId(""); 38 39 } 40 41 @Override 42 public void destroy() { 43 44 } 45 }
SpringBoot基础
配置文件语法
配置文件可以用yml或properties, 若是yml,则键与值间的冒号后需要有空格!,如 :
sensestudy: redis: host: 172.20.6.88 port: 6379
配置文件优先级
代码中的初始配置 < 配置文件中的配置 < 操作系统环境变量 < 命令行启动参数中的配置
若一个模块依赖另一个模块,则被依赖module里配置文件中的配置项 < 当前项目配置文件中的配置项
Spring Security
hasRole、hasAnyRole
log配置
SpringBoot默认采用logback日志框架,可以配置采用其他框架。
可以直接在application.yml配置(日志级别、输出文件等),如:
logging:
level:
root: info
file: ./logs/log.log #输出位置
#config: classpath:logback-spring.xml
但是此对于定期输出一个文件等较复杂的配置无能为力,可以借助日志配置文件:
如上述配置中指定了配置文件,在项目resources文件夹下。也可以不指定,只要配置文件名按默认风格命名即可。配置示例:
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true" scan="false" scanPeriod="30 seconds"> <contextName>sensestudy</contextName> <!-- 文件输出路径 --> <springProperty scope="context" name="LOG_FILE_PATH" source="logging.path" /> <!-- <property name="LOG_FILE_PATH" value="/sensestudy_logs/javaserver_logs" /> --> <!-- 日志级别 --> <springProperty scope="context" name="LOG_LEVEL" source="logging.level.root" /> <!-- <property name="LOG_LEVEL" value="info" /> --> <!-- 输出格式 --> <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符 --> <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" /> <appender name="CONSOLE_APPENDER" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <Pattern>${LOG_PATTERN}</Pattern> </encoder> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>TRACE</level> </filter> </appender> <appender name="FILE_APPENDER" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <OnMismatch>DENY</OnMismatch> <OnMatch>ACCEPT</OnMatch> </filter> --> <file>${LOG_FILE_PATH}/sensestudyserver.log</file> <encoder> <pattern>${LOG_PATTERN}</pattern> </encoder> <append>true</append> <!-- <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>warn</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_FILE_PATH}/sensestudyserver.%d{yyyy-MM-dd}.%i.log.zip </fileNamePattern> <!-- keep 30 days' files --> <!-- <maxHistory>30</maxHistory> --> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>20MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!-- 超出删除老文件 --> <totalSizeCap>20GB</totalSizeCap> </rollingPolicy> </appender> <!-- root是默认的logger,输出到console --> <root level="${LOG_LEVEL}"> <appender-ref ref="CONSOLE_APPENDER" /> </root> <!-- 另外专门定义一个logger用来输出埋点信息到特定文件 --> <logger name="fileLogger" additivity="false" level="${LOG_LEVEL}"> <appender-ref ref="FILE_APPENDER" /> <appender-ref ref="CONSOLE_APPENDER" /><!-- 同时也输出到console --> </logger> </configuration>
配置文件中的配置优先级优于application.yml,在配置文件指定日志输出位置后,application.yml中的file(输出位置)就失效了。
更多可参考:SpringBoot logback日志配置
SpringBoot 静态资源
Spring Boot 默认“约定”从资源目录的这些子目录读取静态资源:
- src/main/resources/META-INF/resources
- src/main/resources/static (推荐)
- src/main/resources/public
自定义handler参数转换器
如将 int 请求参数转为对应的枚举值:
// add request parameter converters to current controller's handler @InitBinder public void initBinder(WebDataBinder dataBinder) { // DevelopStateEnum converter dataBinder.registerCustomEditor(DevelopStateEnum.class, new PropertyEditorSupport() { @Override public void setAsText(String text) throws IllegalArgumentException { Integer devState = Integer.parseInt(text); setValue(DevelopStateEnum.myValueOf(devState)); } }); }
上述方法位于一个Controller内,只对当前Controller有效,若要对全局有效,可以借助@ControllerAdvice放在@ControllerAdvice所在的类中。
此外,进行该配置GET或POST中的请求参数均生效。
参考资料:https://stackoverflow.com/questions/4617099/spring-3-0-mvc-binding-enums-case-sensitive