SpringBoot Web开发整合(下)
本篇是Web开发整合的下篇内容,具体包括、启动系统任务、整合Servlet、Filter和Listener路径映射、配置AOP、自定义欢迎页、自定义favicon和除去某个自动配置等相关知识。
启动系统任务
众所周知,有一些特殊的任务需要在系统启动时执行,如配置文件加载、数据库初始化等操作。如果没有使用SpringBoot,这些问题可以在Listener中得到解决。SpringBoot对此提供了两种方式:CommandLineRunner
和ApplicationRunner
。这两种基本上差不多,只是使用时的参数不同而已。
CommandLineRunner
SpringBoot项目在启动时会遍历所有CommandLineRunner
的实现类,并调用其中的run方法,如果整个系统中存在多个CommandLineRunner
的实现类,那么可以使用@Order
注解来对这些实现类的调用顺序进行排序。
查看CommandLineRunner
源码可知为一个函数式接口,其中只包含一个run方法:
1 | @FunctionalInterface |
新建一个SpringBoot的Web项目(我依旧使用前面corsspringboot
项目),然后新建一个command包,里面新建两个CommandLineRunner
的实现类MyCommandLineRunner1
和MyCommandLineRunner2
,里面的代码为:
1 | @Component |
解释一下上述代码的含义:自定义两个MyCommandLineRunner
类,实现CommandLineRunner
接口,并重写其中的run方法,请注意需要使用@Component
注解将自定义的类注入到Spring容器中。@Order
注解用来描述CommandLineRunner
对象的执行顺序,数字越小越先执行。其中的run方法是调用的核心逻辑,参数是系统启动时传入的参数,也就是入口类中main方法的参数。(在调用SpringApplication.run(CorsspringbootApplication.class, args)
这个方法时,才被传入SpringBoot项目中。)
那么问题来了如何在系统启动时,传入参数呢?很简单,按照图示操作即可:
然后启动项目,可以发现控制台就会输出以下信息:
1 | This is the Runner1>>>>>[西游记, 吴承恩, 红楼梦, 曹雪芹] |
请注意多个参数之间是以空格分隔的,这一点尤其要引起重视。
ApplicationRunner
前面也说过ApplicationRunner
和CommandLineRunner
基本一致,区别主要体现在run方法的参数上。
SpringBoot项目在启动时会遍历所有ApplicationRunner
的实现类,并调用其中的run方法,如果整个系统中存在多个ApplicationRunner
的实现类,那么可以使用@Order
注解来对这些实现类的调用顺序进行排序。
查看ApplicationRunner
源码可知它也是一个函数式接口,其中也只是包含一个run方法,但是参数是ApplicationArguments
类型,这一点与CommandLineRunner
不同的:
1 | @FunctionalInterface |
新建一个SpringBoot的Web项目(我依旧使用前面corsspringboot
项目),然后新建一个application包,里面新建两个ApplicationRunner
的实现类MyApplicationRunner1
和MyApplicationRunner2
。现在问题在于传入的参数是ApplicationArguments
类型,查看一下它的源码:
1 | public interface ApplicationArguments { |
一共有5个方法,这个具体怎样使用也不太清楚,老规矩找一下它的实现类,发现了一个DefaultApplicationArguments
类,它这里面就实现了全部方法。所以可以借鉴其中的代码来书写MyApplicationRunner1
和MyApplicationRunner2
这两个类:
1 | @Component |
解释一下上述代码的含义:自定义两个MyApplicationRunner
类,实现ApplicationRunner
接口,并重写其中的run方法,请注意需要使用@Component
注解将自定义的类注入到Spring容器中。@Order
注解用来描述MyApplicationRunner
对象的执行顺序,数字越小越先执行。
不同于CommandLineRunner
中run方法的String数组参数,这里的run方法的参数是一个ApplicationArguments
对象,如果你想从ApplicationArguments
对象中获取入口类main方法中接收的参数,那么需要调用ApplicationArguments
对象的getNonOptionArgs
。当然如果你想获取项目启动命令行中参数的key,那么需要调用ApplicationArguments
对象的getOptionNames
方法。将本项目打包成一个jar包,然后使用java -jar xxx.jar -name=Envy
命令来启动该项目,此时getOptionNames
方法获取到的就是name,而getOptionValues
方法则是获取到相应的value。
接下来使用mvn package
命令来打包项目,然后找到打包生成的jar同级目录下,执行下面的命令来启动项目:
1 | java -jar corsspringboot-0.0.1-SNAPSHOT.jar --name=Envy --age=24 西游记 吴承恩 红楼梦 曹雪芹 |
解释一下上面代码的含义:--name=Envy --age=24
都属于getOptionNames/getOptionValues
的范围。而后面的“西游记 吴承恩 红楼梦 曹雪芹”可以通过getNonOptionArgs
方法来获取,获取到的是一个数组,相当于前面运行时配置的ProgramArguments
。
整合Servlet、Filter和Listener路径映射
一般来说,当你使用Spring、SpringMVC等这些框架以后,基本上就不会再使用Servlet、Filter以及Listener,但是当你整合一些第三方框架的时候,可能不得不使用Servlet,这种例子太多了,像报表插件、统计插件、登录插件等等,这是就需要使用Servlet了。SpringBoot对于整合这些基本的Web组件也提供了很好的支持。
还是以前面的corsspringboot
项目为例进行说明。新建一个union包,接着在里面自定义三个类:MyServlet、MyFilter和MyListener类,分别去继承/实现Servlet(一般用HttpServlet
)、Filter以及Listener(一般用ServletRequestListener
)类/接口,并重写相应的方法:
1 | @WebServlet("/myname") |
简单解释一下上述代码的含义:定义了三个基本的组件,分别使用@WebServlet
、@WebFilter
、@WebListener
三个注解进行标记。注意一下这里的Listener是以ServletRequestListener
,但是对于其他的Listener
,如HttpSessionListener
、ServletContextListener
等也是支持的。
请注意由于这里的Servlet、Filter和Listener不是,因此仅仅使用@SpringBootApplication
注解是无法完成扫描的,需要使用@ServletComponentScan
注解才行,这一点需要注意,这个注解需要添加到项目的入口类上。
然后启动项目,在浏览器地址栏中输入http://localhost:8080/myname?name=envy
(如果不行就使用postman进行测试,注意需要将前面拦截器的代码注释掉,否则请求过不来),就可以看到控制台已经输出相关日志信息:
路径映射
一般情况下, 使用了页面模板后,用户需要通过控制器才能访问页面。有一些页面需要在控制器中加载数据,然后渲染才能显示出来。还有一些页面在控制器中不需要加载数据,只是完成简单的跳转,对于这种页面可以直接配置路径映射,以提高访问速度。举个例子来说,现在有两个使用Thymeleaf
作为模板引擎时的页面login.html
和index.html
,这两个页面不需要加载任何数据,所以最好的解决办法就是不放入控制器中 ,直接在MVC配置中重写addViewControllers
方法,用来配置映射关系。
在config包内新建一个MyControllerWebMcvConfig
类,实现WebMvcConfigurer
接口,并重写addViewControllers
方法,又看到了WebMvcConfigurer
接口,这个接口非常重要,里面实现了很多方法,如果你想自定义配置,最简单的方式就是实现这个接口,然后实现它对应的方法即可。MyControllerWebMcvConfig
类中的代码为:
1 | @Configuration |
这样你不需要在controller中定义相应的访问路由,只需要在resources/templates目录下新建对应的html页面,然后就可以使用诸如http://localhost:8080/index
等地址来访问页面。
配置AOP
AOP简介
在介绍AOP(Aspect Oriented pragramming)面向切面编程之前,先来考虑这么一个场景:公司有一个人力资源管理系统目前已经上线,但是系统运行不稳定时快时慢,为了检测出到底是哪个环节出问题了,开发人员想要监控每一个方法的执行时间,再根据这些执行时间来判断问题所在。当问题解决后,需要将这些监控移除掉,以避免影响正常的系统运行。如果手动修改系统中成千上万个方法,那么工作量未免太大,且这些监控方法之后还要移除,显然是一个非明智之举。
我们希望有这么一种技术,能够在系统运行过程中动态地添加代码,这样问题就迎刃而解了。这种在系统运行时动态添加代码的方式称为面向切面编程(AOP)。Spring框架对AOP提供了很好的支持,AOP 中有一些常见的概念需要事先了解一下。我在《Spring学习(3):Spring AOP》和《Spring学习(4):基于AspectJ的AOP开发》这两篇文章中对AOP进行了介绍和使用,有兴趣的可以去看一下。不过这里还是简单提一下相关的概念。
Joinpoint(连接点):类里面可以被增强的方法就是连接点,Spring只支持方法增强。如想修改某个方法的功能,那么该方法就是一个连接点。Pointcut(切入点):对Joinpoint(连接点)进行拦截的定义即为切入点。如拦截所有以insert开始的方法,这个定义即为切入点。Advice(通知/增强):拦截到Joinpoint(连接点)之后所要做的事情就是通知。如打印日志监控就分为:前置通知、后置通知、异常通知、最终通知和环绕通知。Aspect(切面):其实就是Pointcut(切入点)和Advice(通知/增强)的结合。Target(目标对象):指需要增强的类。接下来介绍如何在SpringBoot中实现AOP。
SpringBoot支持
SpringBoot在Spring的基础上对AOP的配置提供了自动化配置解决方案spring-boot-starter-aop
,极大的方便了开发者在SpringBoot中使用AOP。第一步,使用spring Initializr
构建工具构建一个SpringBoot的Web应用,名称为aopspringboot
,然后添加spring-boot-starter-web
和spring-boot-starter-aop
依赖:
1 | <dependency> |
第二步,新建service包,并在里面新建UserService类,里面的代码为:
1 | @Service |
第三步,新建一个aspect包,并在里面新建一个LogAspect类,用于记录日志信息:
1 | @Component |
解释一下上述代码的含义:首先使用@Component
注解表明让Spring容器来管理它,@Aspect
注解表示这是一个切面类。test方法使用了@Pointcut
注解,说明这是一个切入点定义。execution中需要书写拦截规则,第一个表示方法任意返回值,第二个表示service包下的任意类,第三个*表示任意类中的任意方法,括号中的两个点表示方法参数任意,也就是说这里描述的切入点为com.envy.aopspringboot.service
包下的所有类的所有方法。请注意JoinPoint
选择使用org.aspectj.lang.JoinPoint;
包内的,不要弄错。
before方法上使用了@Before
注解,表示这是一个前置通知,该方法在目标方法执行前执行,通过JoinPoint参数可以获取目标方法的方法名、修饰符等信息。
after方法上使用了@After
注解,表示这是一个后置通知,该方法在目标方法执行之后执行,通过JoinPoint参数可以获取目标方法的方法名、修饰符等信息。
afterReturning方法上使用了@AfterReturning
注解,表示这是一个返回通知,在该方法中可以获取目标方法的返回值。@AfterReturning
注解的returning参数是指返回值的变量名,对应方法的参数。请注意在方法参数中定义了result的类型为Object,表示目标方法的返回值可以是任意类型,若result参数的类型为Long,则该方法只能处理目标方法返回值为Long的情况。
afterThrowing方法上使用了@AfterThrowing
注解,表示这是一个异常通知,即只有当目标方法发生异常时,该方法才会被调用,异常类型为Exception,表示所有的异常都会进入该方法中执行,若异常类型为ArithmeticException
,则表示只有目标方法抛出的ArithmeticException
异常才会进入该方法中处理。
around方法上使用了@Around
注解,表示这是一个环绕通知。环绕通知是所有通知中功能最为强大的通知,可以实现前置通知、后置通知、异常通知以及返回通知的功能。目标方法进入环绕通知后,通过调用ProceedingJoinPoint
对象proceed方法使目标方法继续执行,开发者可以在此修改目标方法的执行参数、返回值等,且可以在此处理目标方法的异常。
第四步,配置完上述信息后,接下来新建一个controller包,在里面新建一个UserController类,分别调用UserService中定义的两个方法:
1 | @RestController |
然后运行项目,访问这两个链接,看控制台的输出信息:
1 | 【前置通知】getUserById 方法开始执行... |
可以看到在LogAspect类中定义的信息已经动态嵌入到目标方法中,且按照既定的顺序执行了。
其他配置
接下来介绍一些非常有意思的配置,当然受目前知识的影响,后续会补充其他内容。
自定义欢迎页
通过前面的介绍已经知道SpringBoot项目启动后,首先会去静态资源路径(resources/static)目录下查找index.html文件作为首页文件,如果找不到则会去查找动态的index文件作为首页文件。
如果想使用静态的index.html页面作为项目首页那么只需在静态资源路径(resources/static)目录下创建index.html文件即可;如果想使用动态页面作为项目首页,则需要在动态资源路径(resources/templates)目录下创建index.html文件(使用Thymeleaf作为模板)或者index.ftlh(使用FreeMaker模板),然后在Controller中返回逻辑视图即可:
1 | @RequestMapping("/index") |
最后启动项目,在浏览器地址栏中输入http://localhost:8080/
即可看到项目首页的内容了:
自定义favicon
favicon是浏览器选项卡左上角的图标,可以放在静态资源路径下或者类路径下,但是静态资源路径下的favicon优先级高于类路径下的favicon。
可以使用一些将png或者jpg转为favicon的网站,这里推荐一个图片转换,图片转换成功后需要将名称修改为favicon.ico
,然后复制到静态资源路径(resources/static)目录下即可:
最后启动项目,在浏览器地址栏中输入http://localhost:8080/
即可在浏览器选项卡左上角看到自定义的favicon:
除去某个自动配置
SpringBoot中提供了大量的自动化配置类,如前面提到的ErrorMvcAutoConfiguration
、WebMvcAutoConfiguration
、ThymeleafAutoConfiguration
、FreeMakerAutoConfiguration
和MutilpartAutoConfiguration
等,这些自动化配置可以减少相应操作的配置,达到开箱即用的效果。在SpringBoot的入口类上有一个@SpringBootApplication
注解,前面多次提到它是一个组合注解,由@SpringBootConfiguration
、@EnableAutoConfiguration
和@ComponentScan
三个注解组成,其中的@EnableAutoConfiguration
注解开启自动化配置,相关的自动化配置类就会被使用。如果开发者不想使用某个自动化配置,可以按照如下方式除去相关配置即可:
1 | @SpringBootApplication |
但实际上按照上面的写法可能会抛错误:
点开可以发现具体错误信息为:
1 | Spring Boot Application in default package less… (Ctrl+F1) |
原因在于这里定义的OtherApplication
类是直接在项目目录下,应该将其放在项目目录下的包目录:
当然如果你不想移动位置,也是可以的,既然@SpringbootApplication
注解失效了,那可以使用它三个注解来代替即可:
1 | @SpringBootConfiguration |
这样也没有问题。在@EnableAutoConfiguration
注解中使用exclude属性除去了Error的自动化配置类,这时如果在resources/static/error
目录下创建了4xx.html、5xx.html文件,那么当访问出错时就不会自动跳转到该处。由于@EnableAutoConfiguration
注解的exclude属性值是一个数组,因此有多个要排除的自动化配置类时只需要继续添加即可。
当然除了这种配置方式外,开发者还可以在application.properties
配置文件中进行设置:
1 | # 除去ErrorMVCAutoConfiguration |
这样也是可以的,当然还是需要结合实际情况来进行选择。
Web开发整合小结
此处利用了三篇文章来介绍SpringBoot整合Web开发时的一些常见的、有用配置。如果开发者之前使用过SpringMVC,就会发现这些大部分是SpringMVC的功能,只是在SpringBoot中做了自动化配置,少部分是SpringBoot自身提供的功能,像CommandLineRunner
。这里三篇文章的学习其实完全不够,后续会针对具体的情况再进一步学习。