如果进入SpringMVC的规则为/时,Spring Boot的默认静态资源的路径为:
spring.resources.static-locations=classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
也就是说,在默认的Spring MVC进入规则下,classpath下的META-INF/resources目录、resources目录、static目录和public目录中的静态资料是可以直接通过 "xxx.com/静态资源" 的方式访问到的。如:
如果进入SpringMVC的规则为*.xxx时(如:*.html),则上述目录下的静态资源将无法直接访问,需要将静态资源放置到webapp下的static目录中即可通过 " xxx.com/static/静态资源 " 访问。此外,默认不配置SpringMVC的规则下也可以如此访问,也就是说这种访问静态资源的方式是通用的。如图所示:
增加一个拦截器,需要通过继承WebMvcConfigurerAdapter然后重写父类中的方法进行扩展。
@Configuration public class MySpringMvcConfig extends WebMvcConfigurerAdapter { @Override public void addInterceptors(InterceptorRegistry registry) { HandlerInterceptor handlerInterceptor = new HandlerInterceptor() { @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { System.out.println("自定义拦截器。。。"); return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } }; // 添加拦截器并设置拦截规则 registry.addInterceptor(handlerInterceptor).addPathPatterns("/**"); } }
自定义消息转化器有两种实现方式,一种是@Bean方式,另一种是自定义拦截器。
只需要在@Configuration的类中添加消息转化器的@bean加入到Spring容器,就会被Spring Boot自动加入到容器中。
// spring boot默认就有消息转化器,其编码格式为utf-8 @Bean public StringHttpMessageConverter stringHttpMessageConverter() { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8")); return stringHttpMessageConverter; }
WebMvcConfigurerAdapter的功能很强大,除了可以配置拦截器外,还可以配置消息转换器。
@Configuration public class MySpringMvcConfig extends WebMvcConfigurerAdapter { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter(Charset.forName("UTF-8")); converters.add(stringHttpMessageConverter); } }
@Configuration @PropertySource(value = { "classpath:jdbc.properties", "classpath:base.properties" }, ignoreResourceNotFound = true) public class 任意类 { }
Druid提供了一个高效、功能强大、可扩展性好的数据库连接池,常用于替换DBCP和C3P0。但在Spring Boot上配置比较“恶心”,这里就简单的贴出个人参照网上的配置代码,不必深究。
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.11</version> </dependency>
项目中一般会创建一个jdbc.properties文件来记录数据库的连接信息。
#MySQL jdbc.url=jdbc:mysql://127.0.0.1:3306/dbxxx?useUnicode=true&characterEncoding=utf-8 jdbc.username=root jdbc.password=123456 #Druid jdbc.initialSize=0 jdbc.minIdle=0 jdbc.maxActive=150
建议将配置Druid数据源的操作放在@SpringBootApplication注解的类中。
@SpringBootApplication @Configuration @PropertySource(value = {"classpath:jdbc.properties"}) public class MyWebApplication{ @Value("${jdbc.url}") public String jdbcUrl; @Value("${jdbc.username}") public String jdbcUsername; @Value("${jdbc.password}") public String jdbcPassword; @Value("${jdbc.initialSize}") public int jdbcInitialSize; @Value("${jdbc.minIdle}") public int jdbcMinIdle; @Value("${jdbc.maxActive}") public int jdbcMaxActive; @Bean public ServletRegistrationBean druidServletRegistrationBean() { ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(); servletRegistrationBean.setServlet(new StatViewServlet()); servletRegistrationBean.addUrlMappings("/druid/*"); return servletRegistrationBean; } @Bean public FilterRegistrationBean duridFilterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new WebStatFilter()); HashMap<String, String> initParams = new HashMap<>(); // 设置忽略请求 initParams.put("exclusions", "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"); filterRegistrationBean.setInitParameters(initParams); filterRegistrationBean.addUrlPatterns("/*"); return filterRegistrationBean; } @Bean(initMethod = "init", destroyMethod = "close") public DruidDataSource druidDataSource() { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setUrl(jdbcUrl); druidDataSource.setUsername(jdbcUsername); druidDataSource.setPassword(jdbcPassword); druidDataSource.setInitialSize(jdbcInitialSize); druidDataSource.setMinIdle(jdbcMinIdle); druidDataSource.setMaxActive(jdbcMaxActive); return druidDataSource; } }
之后就可以通过@AutoWried注解得到数据源(druidDataSource)了。
Jpa在使用上跟Hibernate很像,因为它们之前的关系非同一般,有兴趣可以看看 《Hibernate与Jpa的关系,终于弄懂》 这篇文章。Spring Boot对jpa的支持也是很好的,配置起来非常简单。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1/dbgirl username: root password: 123456 jpa: hibernate: ddl-auto: update #第一次加载hibernate时根据model类会自动建立起表的结构(前提是先建立好数据库),以后加载hibernate时根据 model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等 应用第一次运行起来后才会。 show-sql: true
Mybatis和Spring Boot的整合有两种方式:
第一种:使用mybatis官方提供的Spring Boot整合包实现,地址: spring-boot-starter
第二种:使用mybatis-spring整合的方式,也就是我们传统的方式
这里推荐并使用第二种方式,因为可以很方便的控制Mybatis的各种配置。这里假设你已经配置过数据源了(数据源可以是druid、dbcp、c3p0...)。
首先,需要在pom.xml文件中引用mybatis依赖
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.2.2</version> </dependency>
然后,创建一个Mybatis的配置类:
@Configuration public class MybatisConfig { @Autowired DruidDataSource druidDataSource; @Bean @ConditionalOnMissingBean// 当Spring容器中没有SqlSessionFactoryBean时才创建 public SqlSessionFactoryBean sqlSessionFactoryBean() { SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean(); // 设置数据源 sqlSessionFactory.setDataSource(druidDataSource); // 设置别名扫描包 sqlSessionFactory.setTypeAliasesPackage("com.lqr.demo3.bean"); // 设置Mybatis的配置文件位置 PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); Resource mybatisConfigXml = resolver.getResource("classpath:mybatis-config.xml"); sqlSessionFactory.setConfigLocation(mybatisConfigXml); return sqlSessionFactory; } }
最后,创建Mapper接口的扫描类MapperScannerConfig:
@Configuration @AutoConfigureAfter(MybatisConfig.class)// Mybatis的扫描配置必须在SqlSessionFactory被创建之后 public class MapperScanConfig { @Bean public MapperScannerConfigurer mapperScannerConfigurer() { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.lqr.demo3.mapper"); return mapperScannerConfigurer; } }
Spring Boot中推荐使用@Transactional注解来申明事务。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency>
当引入jdbc依赖之后,Spring Boot会自动默认分别注入DataSourceTransactionManager或JpaTransactionManager,所以我们不需要任何额外配置就可以用@Transactional注解进行事务的使用。
@Service @Transactional public class GirlService { @Transactional public void insertGirl() { Girl girlA = new Girl(); girlA.setCupSize("A"); girlA.setAge(18); girlRespository.save(girlA); } }
@Transactional不仅可以注解在方法,也可以注解在类上。当注解在类上时,意味着此类所有public方法都会开启事务。如果类级别和方法级别同时使用了@Transactional注解,则使用在类级别的注解会重载方法级别的注解。
Spring boot默认内嵌的tomcat是不支持jsp页面的,如果项目中使用到了jsp,需要导入如下依赖才能正常访问。
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency>
这部分可能跟Spring Boot的关系并不是很大(或者说,并非Spring Boot特有),但很值得我们在编码方面作为参考。
现在的Web项目已经越来越趋向于将后端与前端分别开发了,如果贵公司的项目就是使用json来进行前后端交互,且使用Spring MVC来开发的话,就一定会用到下面这2个注解:
@Controller @ResponseBody public class GirlController { ... }
而在Spring MVC4之后,我们可以使用@RestController 注解来开发基于Spring MVC4的REST风格的JSON服务。
通俗的说就是@RestController = @Controller + @ResponseBody。
@Controller和@RestController的区别:
如果只是使用@RestController注解Controller,则Controller中的方法无法返回jsp页面,配置的视图解析器InternalResourceViewResolver不起作用,返回的内容就是Return 里的内容。
例如:本来应该到success.jsp页面的,则其显示success.
Spring4.3中引进了{@GetMapping、@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping},来帮助简化常用的HTTP方法的映射,并更好地表达被注解方法的语义。
@GetMapping => @RequestMapping(value = "/xxx",method = RequestMethod.GET) @PostMapping => @RequestMapping(value = "/xxx",method = RequestMethod.POST) @PutMapping => @RequestMapping(value = "/xxx",method = RequestMethod.PUT) @DeleteMapping => @RequestMapping(value = "/xxx",method = RequestMethod.DELETE) ...
Web开发中,对从前端传过来的数据做数据校验已经是家常便饭的事了,但如果校验的数据很多,那么,一方面在开发上就需要做很多if判断,另一方面则是代码上显得不再简洁。其实,使用@Valid + BindingResult就可以优雅地解决这样的问题。使用示例如下:
首先,使用一个Java Bean来接收前端提交的数据。在这个Java Bean之前加上@Valid,在这个Java Bean之后加上BindingResult(BindingResult参数必须紧跟在@Valid参数之后。)
/** * 添加一个女生 */ @PostMapping(value = "/girls") public Result<Girl> girlAdd(@Valid Girl girl, BindingResult bindingResult) { if (bindingResult.hasErrors()) { return ResultUtils.error(-1, bindingResult.getFieldError().getDefaultMessage()); } return ResultUtils.success(girlRespository.save(girl)); }
然后,需要在@Valid注解的Java Bean中,使用各种"规则注解"对其中的属性进行校验规则的设定。示例如下:
public class Girl { private Integer id; @NotBlank(message = "这个字段必传") private String cupSize; @Min(value = 18, message = "未成年少女禁止入内") private Integer age; @NotNull(message = "金额必传") private Double money; ... }
示例中,"规则注解"的message值可以在Controller中通过bindingResult.getFieldError().getDefaultMessage()获取。
这类用于数据校验的注解还有很多,有兴趣的可以好好研究一下:
注解 | 类型 | 说明 |
---|---|---|
@AssertFalse | Boolean,boolean | 验证注解的元素值是false |
@AssertTrue | Boolean,boolean | 验证注解的元素值是true |
@NotNull | 任意类型 | 验证注解的元素值不是null |
@Null | 任意类型 | 验证注解的元素值是null |
@Min(value=值) | BigDecimal,BigInteger, byte,short, int, long,等任何Number或CharSequence(存储的是数字)子类型 | 验证注解的元素值大于等于@Min指定的value值 |
@Max(value=值) | 和@Min要求一样 | 验证注解的元素值小于等于@Max指定的value值 |
@DecimalMin(value=值) | 和@Min要求一样 | 验证注解的元素值大于等于@ DecimalMin指定的value值 |
@DecimalMax(value=值) | 和@Min要求一样 | 验证注解的元素值小于等于@ DecimalMax指定的value值 |
@Digits(integer=整数位数, fraction=小数位数) | 和@Min要求一样 | 验证注解的元素值的整数位数和小数位数上限 |
@Size(min=下限, max=上限) | 字符串、Collection、Map、数组等 | 验证注解的元素值的在min和max(包含)指定区间之内,如字符长度、集合大小 |
@Past | java.util.Date,java.util.Calendar;Joda Time类库的日期类型 | 验证注解的元素值(日期类型)比当前时间早 |
@Future | 与@Past要求一样 | 验证注解的元素值(日期类型)比当前时间晚 |
@NotBlank | CharSequence子类型 | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的首位空格 |
@Length(min=下限, max=上限) | CharSequence子类型 | 验证注解的元素值长度在min和max区间内 |
@NotEmpty | CharSequence子类型、Collection、Map、数组 | 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0) |
@Range(min=最小值, max=最大值) | BigDecimal,BigInteger,CharSequence, byte, short, int, long等原子类型和包装类型 | 验证注解的元素值在最小值和最大值之间 |
@Email(regexp=正则表达式,flag=标志的模式) | CharSequence子类型(如String) | 验证注解的元素值是Email,也可以通过regexp和flag指定自定义的email格式 |
@Pattern(regexp=正则表达式,flag=标志的模式) | String,任何CharSequence的子类型 | 验证注解的元素值与指定的正则表达式匹配 |
@Valid | 任何非原子类型 | 指定递归验证关联的对象;如用户对象中有个地址对象属性,如果想在验证用户对象时一起验证地址对象的话,在地址对象上加@Valid注解即可级联验证 |
AOP是Spring中一大核心,若在SpringBoot中要使用AOP只需两步:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
@Aspect @Component public class HttpAspect { private final static Logger logger = LoggerFactory.getLogger(HttpAspect.class); @Pointcut("execution(public * com.lqr.controller.GirlController.*(..))") public void log() { } @Before("log()") public void deBefore(JoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); // url logger.info("url={}", request.getRequestURL()); // method logger.info("method={}", request.getMethod()); // ip logger.info("ip={}", request.getRemoteAddr()); // 类方法 logger.info("class_method={}", joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); // 参数 logger.info("args={}", joinPoint.getArgs()); } @After("log()") public void doAfter() { logger.info("doAfter..."); } @AfterReturning(returning = "object", pointcut = "log()") public void doAfterReturning(Object object) { if (object != null) logger.info("response={}", object.toString()); } }
这里之所以继承RuntimeException,是为了方便事务回滚。而自定义异常的好处在于:一方面可以使代码语义化,另一方面使得我们编码更加方便。
public class GirlException extends RuntimeException { private Integer code; public GirlException(ResultEnum resultEnum) { super(resultEnum.getMsg()); this.code = resultEnum.getCode(); } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } }
使用全局异常捕获器,一方面可以捕获到整个项目中的Exception及其子类(包含RuntimeException等),另一方面可以对异常进行统一处理并返回统一的数据格式,为前端提供友好的数据交互。
@ControllerAdvice public class ExceptionHandle { private final Logger logger = LoggerFactory.getLogger(ExceptionHandle.class); @ExceptionHandler(value = Exception.class) @ResponseBody public Result handle(Exception e) { if (e instanceof GirlException) { GirlException girlException = (GirlException) e; return ResultUtils.error(girlException.getCode(), girlException.getMessage()); } else { logger.error("【系统异常】{}", e); return ResultUtils.error(-1, "未知错误"); } } }
Web开发中,没有热部署怎么能行呢?所以,下面就介绍下Spring Boot的热部署配置。
<dependencies> ... <!--spring boot 热部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> </dependencies> <build> <plugins> <!--spring boot 热部署--> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <fork>true</fork> </configuration> </plugin> </plugins> ... </build>
打开Idea的Settings界面,找到"Build,Execution,Depolyment",在Compiler中将"Build project automatically"打钩。
使用快捷键 "ctrl+shift+alt+/" 打开pom.xml的维护(Maintenance)菜单,找到登记(registry)项,单击打开。
在登记(registry)中找到"compiler.automake.allow.when.app.running"这一项打钩,关闭。最后重启项目即可!!!
尽管Spring Boot项目会内置一个tomcat,仅只需通过一个简单的指令便可启动项目,但在生产环境下,我们还是习惯将项目发布到第三外的servlet容器中,下面将介绍如果将一个Spring Boot项目团部署到第三方tomcat中运行。
spring-boot-starter-tomcat是Spring Boot默认就会配置的,即上面说到的内嵌tomcat,将其设置为provided是在打包时会将该包(依赖)排除,因为要放到独立的tomcat中运行,Spring Boot内嵌的Tomcat是不需要用到的。
<!--spring boot tomcat(默认可以不用配置,但当需要把当前web应用布置到外部servlet容器时就需要配置,并将scope配置为provided)--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>
需要继承SpringBootServletInitializer,并重写configure()方法,将Spring Boot的入口类设置进去。
// 若要部署到外部servlet容器,需要继承SpringBootServletInitializer并重写configure() @SpringBootApplication @Configuration public class MyWebApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { // 设置启动类,用于独立tomcat运行的入口 return builder.sources(MyWebApplication.class); } }