springboot学习(三)整合web开发

整合web开发

静态资源

静态资源默认在 resources/static 目录,Spring Boot 中默认情况下,一共有5个位置可以放静态资源,五个路径分别是如下5个:

  1. classpath:/META-INF/resources/

  2. classpath:/resources/

  3. classpath:/static/

  4. classpath:/public/

  5. /

前四个目录分别对应了resources目录下不同的目录,第5个 / 表示 webapp 目录中的静态资源也不被拦截。

@ControllerAdvice

@ControllerAdvice 可以实现三个功能:

  1. 全局异常处理

  2. 全局数据绑定

  3. 全局数据预处理

  • 全局异常处理

使用 @ControllerAdvice 实现全局异常处理,只需要定义类,添加该注解即可定义方式如下:

@ControllerAdvice
public class MyGlobalExceptionHandler {
   @ExceptionHandler(Exception.class)
   public ModelAndView customException(Exception e) {
       ModelAndView mv = new ModelAndView();
       mv.addObject("message", e.getMessage());
       mv.setViewName("myerror");
       return mv;
  }
}

@ExceptionHandler 注解用来指明异常的处理类型,即如果这里指定为 NullpointerException,则数组越界异常就不会进到这个方法中来。

如果程序有异常抛出,且抛出的异常和@ExceptionHandler指明的异常类型相同,则@ExceptionHandler注解的方法捕获并处理这个异常。

  • 全局数据绑定

全局数据绑定功能可以用来做一些数据初始化操作,我们可以将一些公共的数据定义在添加了 @ControllerAdvice 注解的类中,这样,在每一个 Controller 的接口中,就都能够访问导致这些数据

//使用@ModelAttribute定义全局数据,定义完成后,在任何一个Controller 的接口中,都可以获取到这里定义的数据
@ControllerAdvice
public class MyGlobalExceptionHandler {
   @ModelAttribute(name = "model")
   public Map<String,Object> map(){
       HashMap<String,Object> map=new HashMap<>();
       map.put("age",48);
       map.put("gender","male");
       return map;
  }
}
//controller
@RestController
public class UserController {
   @GetMapping("/map")
   public String map(Model model){
       Map<String,Object> map=model.asMap();
       return map.toString();
  }
}
//接口访问结果 {model={gender=male, age=48}}
  • 全局数据预处理

当有两个实体类的属性相同时,在接口传递这两个接口的属性,因为有相同属性会使前端无法区分, 解决步骤:

  • 在接口中给实体参数取别名

    @PostMapping("/book")
   public String addBook(@ModelAttribute("a")Author author, @ModelAttribute("b")Book book){
       return author.toString()+":"+book.toString();
  }
  • @ControllerAdvice 标记的类中进行请求数据预处理

@ControllerAdvice
public class MyGlobalExceptionHandler {
//@InitBinder("b") 注解表示该方法用来处理和Book和相关的参数,在方法中,给参数添加一个 b 前缀,即请求参数要有b前缀    
   @InitBinder("a")
   public void a(WebDataBinder binder){
       binder.setFieldDefaultPrefix("a.");
  }
   @InitBinder("b")
   public void b(WebDataBinder binder){
       binder.setFieldDefaultPrefix("b.");
  }
}

请求接口时,使用a.xx 和b.xx来区分

异常处理方案

  • 静态异常页面

自定义静态异常页面,又分为两种,第一种 是使用 HTTP 响应码来命名页面,例如 404.html、405.html、500.html ....,另一种就是直接定义一个 4xx.html,表示400-499 的状态都显示这个异常页面,5xx.html 表示 500-599 的状态显示这个异常页面,默认存放在 classpath:/static/error/ 路径下

  • 动态的异常页

动态的异常页面定义方式和静态的基本 一致,可以采用的页面模板有 jsp、freemarker、thymeleaf。动态异常页面,也支持 404.html 或者 4xx.html,默认存放在 classpath:/templates/error/路径下

注意,动态页面模板,不需要开发者自己去定义控制器,直接定义异常页面即可 ,Spring Boot 中自带的异常处理器会自动查找到异常页面

  • 自定义异常数据

继承 org.springframework.boot.web.servlet.error.DefaultErrorAttributes ,重写 getErrorAttributes方法,并将这个类注册为bean

定义系统启动任务

项目启动阶段要做一些数据初始化操作,这些操作有一个共同的特点,只在项目启动时进行,以后都不再执行,这里,容易想到web基础中的三大组件( Servlet、Filter、Listener )之一 Listener ,这种情况下,一般定义一个 ServletContextListener,然后就可以监听到项目启动和销毁,进而做出相应的数据初始化和销毁操作。Spring Boot 中针对系统启动任务提供了两种解决方案,分别是 CommandLineRunner 和 ApplicationRunner

  • CommandLineRunner

@Component
@Order(100)
public class MyCommandLineRunner implements CommandLineRunner {
   @Override
   public void run(String... args) throws Exception {
       System.out.println("系统正在启动中....................");
       System.out.println(Arrays.toString(args));
  }
}
  1. 实现CommandLineRunner接口和run方法,并注册为一个bean

  2. 添加 @Order注解,表示这个启动任务的执行优先级,因为在一个项目中,启动任务可能有多个,所以需要有一个排序。

  3. run 方法中,写启动任务的核心逻辑,当项目启动时,run方法会被自动执行。run方法的参数来自于项目的启动参数,即项目入口类中,main方法的参数会被传到这里

  4. 传入项目启动参数有两种方式:

    1. 在IDEA的启动项目处 edit configurations,设置 program arguments

    2. 使用命令 Java -jar启动项目时传入参数

  • ApplicationRunner

ApplicationRunner 和 CommandLineRunner 功能一致,用法也基本一致,唯一的区别主要体现在对参数的处理上,ApplicationRunner 可以接收更多类型的参数(ApplicationRunner 除了可以接收 CommandLineRunner 的参数之外,还可以接收 key/value形式的参数)。

ApplicationRunner 的run方法参数ApplicationArguments 说明:

  1. args.getNonOptionArgs();可以用来获取命令行中的无key参数(和CommandLineRunner一样)。

  2. args.getOptionNames();可以用来获取所有key/value形式的参数的key。

  3. args.getOptionValues(key));可以根据key获取key/value 形式的参数的value。

  4. args.getSourceArgs(); 则表示获取命令行中的所有参数。

传入参数方式也和 CommandLineRunner 相同,传递键值对时使用 --key=value

定时任务

spring boot(spring+springMVC)实现定时任务有两种方案,一种是使用 Spring 自带的定时任务处理器 @Scheduled 注解,另一种就是使用第三方框架 Quartz

  • @Scheduled:只适合处理简单的计划任务,不能处理分布式计划任务。优势:是spring框架提供的计划任务,开发简单,执行效率比较高。且在计划任务数量太多的时候,可能出现阻塞,崩溃,延迟启动等问题。

    • 在spring boot的启动类上添加 @EnableScheduling 注解,开启定时任务

    • 使用 @Scheduled 添加一个方法上,表示一个定时任务,这个类注册为bean

    @Component
    public class MyScheduled {
       @Scheduled(fixedRate = 3000)
       public void print(){
           System.out.println("定时任务,没3s执行一次");
      }
    }

@Scheduled的参数解析:

  1. fixedRate 表示任务执行之间的时间间隔,具体是指两次任务的开始时间间隔,即第二次任务开始时,第一次任务可能还没结束。单位ms

  2. fixedDelay 表示任务执行之间的时间间隔,具体是指本次任务结束到下次任务开始之间的时间间隔。单位ms

  3. initialDelay 表示首次任务启动的延迟时间。

  4. cron,支持cron表达式

  • Quartz:Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。

一般在项目中,除非定时任务涉及到的业务实在是太简单,使用 @Scheduled 注解来解决定时任务,否则大部分情况可能都是使用 Quartz 来做定时任务。

在开发Quartz相关应用时,只要定义了Job(任务),Trigger(触发器)和Scheduler(调度器),即可实现一个定时调度能力。其中Scheduler是Quartz中的核心,Scheduler负责管理Quartz应用运行时环境,Scheduler不是靠自己完成所有的工作,是根据Trigger的触发标准,调用Job中的任务执行逻辑,来完成完整的定时任务调度首先添加jar包依赖

<dependency>
   <groupId>org.quartz-scheduler</groupId>
   <artifactId>quartz</artifactId>
   <version>2.3.1</version>
</dependency>
<!-- scheduled所属资源为spring-context-support,在Spring中对Quartz的支持,是集成在spring-context-support包中。org.springframework.scheduling.quartz -->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-tx</artifactId>
</dependency>
  1. 在spring boot的启动类上添加 @EnableScheduling 注解,开启定时任务

  2. 定义JOB任务以及JOB任务调用的模拟业务对象

@Component
public class Hello {
   public void say(){
       System.out.println("定时任务执行......");
  }
}
//job任务
public class MyJob extends QuartzJobBean {
   @Autowired
   private Hello hello;
   @Override
   protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
       System.out.println("定时任务"+new Date());
       hello.say();
  }
}
  1. 创建Trigger以及JobDetail对象,并用Schedule配置定时任务

@Configuration
public class QuartzConfig {
   //创建JobDetail
   @Bean
   public JobDetailFactoryBean initJobDetailFactoryBean(){
       JobDetailFactoryBean factoryBean=new JobDetailFactoryBean();
       //设置job提供类
       factoryBean.setJobClass(MyJob.class);
       return factoryBean;
  }
   //创建Trigger,可用SimpleTrigger 或 CronTrigger
   @Bean
   public SimpleTriggerFactoryBean initSimpleTriggerFactoryBean(){
       SimpleTriggerFactoryBean factoryBean=new SimpleTriggerFactoryBean();
       factoryBean.setRepeatInterval(3000);
       factoryBean.setJobDetail(initJobDetailFactoryBean().getObject());
       return factoryBean;
  }
   //创建Schedule
   @Bean
   public SchedulerFactoryBean initSchedulerFactoryBean(MyJobFactory myJobFactory){
       SchedulerFactoryBean factoryBean=new SchedulerFactoryBean();
       factoryBean.setTriggers(initSimpleTriggerFactoryBean().getObject());
//为Scheduler设置JobDetail的工厂。可以覆盖掉SpringBoot提供的默认工厂,保证JobDetail中的自动装配有效,否则job类的自动装配失效,会报空指针异常
       factoryBean.setJobFactory(myJobFactory);
       return factoryBean;
  }
}
  1. 重写JobFactory

@Component
public class MyJobFactory extends AdaptableJobFactory {
   /**
    * AutowireCapableBeanFactory : 简单理解为Spring容器,是Spring容器Context的一个Bean对象管理工程。
    * 可以实现自动装配逻辑,和对象创建逻辑。
    * 是SpringIoC容器的一个重要组成部件。
    */
   @Autowired
   private AutowireCapableBeanFactory autowireCapableBeanFactory;
   @Override
   protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
       // 通过父类型中的方法,创建JobDetail对象。
       Object obj = super.createJobInstance(bundle);
       // 将JobDetail对象加入到Spring容器中,让Spring容器管理,并实现自动装配逻辑。
       this.autowireCapableBeanFactory.autowireBean(obj);
       return obj;
  }
}

Swagger

自动生成接口文档,地址为 http://localhost:8080/swagger-ui.html,首先添加swagger2的依赖

<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>2.9.2</version>
</dependency>
<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>2.9.2</version>
</dependency>
  • Swagger2配置

Swagger2的配置也是比较容易的,只需要开发者自己提供一个Docket的Bean即可,@EnableSwagger2注解启用Swagger2

@Configuration
@EnableSwagger2
public class SwaggerConfig {
   @Bean
   public Docket createRestApi(){
       return new Docket(DocumentationType.SWAGGER_2)
              .pathMapping("/")
              .select()
              .apis(RequestHandlerSelectors.basePackage("com.hjy.controller"))
              .build().apiInfo(new ApiInfoBuilder()
                      .title("SpringBoot整合Swagger")
                      .description("SpringBoot整合Swagger,详细信息......")
                      .version("9.0")
                      .contact(new Contact("名字123","blog.csdn.net","aaa@gmail.com"))
                      .license("The Apache License")
                      .licenseUrl("http://www.baidu.com")
                      .build());
  }
}
  • 创建controller和实体类

@RestController
@Api(tags = "book相关接口")
@RequestMapping("/book")
public class BookController {

   @PostMapping("/")
   @ApiOperation("添加书")
   public Book addBook(@RequestBody Book book){
       return book;
  }

   @GetMapping("/{id}")
   @ApiOperation("根据id查询书")
   @ApiImplicitParam(name = "id",value = "book.id",defaultValue = "12",required = true)
   public Book getBook(@PathVariable("id") String id){
       Book book=new Book();
       book.setId(id);
       book.setName("unsigned");
       return book;
  }
}

注解解析:

  1. @Api注解用来标记当前Controller的功能。

  2. @ApiOperation注解用来标记一个方法的作用。

  3. @ApiImplicitParam注解用来描述一个参数,可以配置参数的中文含义,也可以给参数设置默认值,这样在接口测试的时候可以避免手动输入。

  4. 如果有多个参数,则需要使用多个@ApiImplicitParam注解来描述,多个@ApiImplicitParam注解需要放在一个@ApiImplicitParams注解中。

  5. 需要注意的是,@ApiImplicitParam注解中虽然可以指定参数是必填的,但是却不能代替@RequestParam(required = true),前者的必填只是在Swagger2框架内必填,抛弃了Swagger2,这个限制就没用了,所以假如开发者需要指定一个参数必填,@RequestParam(required = true)注解还是不能省略。

  6. 如果参数是一个对象,对于参数的描述也可以放在实体类中。例如下面一段代码:

@Data
@Component
@ApiModel
public class Book {
   @ApiModelProperty(value = "book.id")
   private String id;
   @ApiModelProperty(value = "book.name")
   private String name;
}
  • 在Security中的配置

如果我们的Spring Boot项目中集成了Spring Security,那么如果不做额外配置,Swagger2文档可能会被拦截,此时只需要在Spring Security的配置类中重写configure方法,添加如下过滤即可:

@Override
public void configure(WebSecurity web) throws Exception{
web.ignoring().
   antMatchers("/swagger-ui.html").
   antMatchers("/v2/**").
   antMatchers("/swagger-resources/**");
}

 

posted @ 2020-12-20 18:23  hjy1995  阅读(137)  评论(0编辑  收藏  举报