SpringBoot 学习笔记_整合 Web 开发(二)
声明:
本次学习参考 《SpringBoot + Vue 开发实战》 · 王松(著) 一书。
本文的目的是记录我学习的过程和遇到的一些问题以及解决办法,其内容主要来源于原书。
如有侵权,请联系我删除
文章目录
SpringBoot 整合 Web 开发
CORS 支持
CORS (Cross-Origin Resource Sharing) 是一种跨域资源共享技术标准,其目的就是为了解决前端跨域请求。在 Java EE 中,关于前端跨域请求最常见的解决方案是
JSONP
,但是,JSONP
最大的缺陷是只支持GET
请求。而CORS
支持多种 HTTP 请求方法。
跨域有两个地方可以配置:
-
请求方法配置
@PostMapping("/") @CrossOrigin(value = "https://localhost:8080", maxAge = 1800, allowedHeaders = "*") public String addBook(String name){ return "receive" + name; } @DeleteMapping("/{id}") @CrossOrigin(value = "https://localhost:8080", maxAge = 1800, allowedHeaders = "*") public String deleteBookById(@PathVariable Long id){ return String.valueOf(id); }
@CrossOrigin
中的value
表示支持的域,这里表示https://localhost:8080
域的请求支持跨域maxAge
表示探测请求的有效期。在请求执行过程中会先发送探测请求,探测请求不是每次都发送,可以配置一个周期,过了有效期再次发送,默认 1800 秒allowedHeaders
表示允许的请求头,*
表示所有请求头都被允许
-
全局配置
@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/book/**") .allowedHeaders("*") .allowedMethods("*") .maxAge(1800) .allowedOrigins("https://localhost:8080"); }
配置类和 XML 配置
SpringBoot 推荐使用 Java 来完成相关配置工作,这些配置类需要添加
@Configuration
注解,@ComponentScan
注解会扫描所有 Spring 组件,也包括@Configuration
。@ComponentScan
注解在项目入口类的@SpringBootApplication
注解中已经提供,因此在实际项目中只需要按需提供相关配置类即可。
注册拦截器
SpringMVC 中提供了
AOP
风格的拦截器,拥有更加精细的拦截处理能力。 SpringBoot 的拦截器注册更加方便。
- 创建拦截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("MyInterceptor >>>>> preHandler");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("MyInterceptor >>>>> postHandler");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("MyInterceptor >>>>> afterCompletion");
}
}
按照 preHandle - Controller - postHandle - afterCompletion 的顺序依次执行, 当且仅当 preHandler 返回 true 时,后面的才会继续
当拦截器链内有多个拦截器时,postHandler 在拦截器链内所有拦截器返回成功时才会调用,而 afterCompletion 只有 preHandler 返回 true 才调用
- 配置拦截器
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
/**
* 拦截器配置
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**") //拦截路径
.excludePathPatterns("/hello"); //排除路径
}
}
启动系统任务
有一些需要在系统启动时执行的任务,如配置文件加载、数据库初始化等。
在不使用 SpringBoot 的情况下,这些问题一般在 Listener 中解决。
SpringBoot 对此提供了两种方案
CommandLineRunner
和ApplicationRunner
,二者主要区别在参数不同。
-
CommandLineRunner
SpringBoot 会在启动时按照
@Order
的顺序遍历所有的CommandLineRunner
的实现类并调用其中的run
方法,run
方法的参数是系统启动时传入的参数@Component @Order(1) public class MyCommandLineRunner implements CommandLineRunner { @Override public void run(String... args) throws Exception { System.out.println("Runner >>> " + Arrays.toString(args)); } }
-
ApplicationRunner
整合 Servlet、Filter 和 Listener
-
Servlet
@WebServlet("/myServlet") public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { super.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("name" + req.getParameter("name")); super.doPost(req, resp); } }
-
Filter
@WebFilter("/*") public class MyFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { System.out.println("MyFilter >>>>> init"); } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("MyFilter >>>>> doFilter"); } @Override public void destroy() { System.out.println("MyFilter >>>>> destroy"); } }
-
Listener
@WebListener public class MyListener implements ServletRequestListener { @Override public void requestDestroyed(ServletRequestEvent sre) { System.out.println("MyListener >>>>> requestDestroyed"); } @Override public void requestInitialized(ServletRequestEvent sre) { System.out.println("MyListener >>>>> requestInitialized"); } }
接下来,在项目入口类添加 @ServletComponentScan
注解,实现对 Servlet
、Filter
和 Listener
的扫描。
@SpringBootApplication
@ServletComponentScan
public class Chapter012Application {
public static void main(String[] args) {
SpringApplication.run(Chapter012Application.class, args);
}
}
启动项目,访问 https://localhost:8080/myServlet?name=Ambrose
测试
路径映射
一般情况下,我们都是通过控制器访问页面,有时候,有些页面不需要加载数据,只是完成简单的跳转,对于这种页面,就可以直接配置路径映射,提高访问速度
@Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
/* 这行代码其实就相当于在控制器中对 login 页面 和 /login 映射,提升了访问速度,但无法进行数据处理 */
registry.addViewController("/login").setViewName("login");
}
}
配置 AOP
面向切面编程(Aspect-Oriented Programming, AOP),是一种通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP 技术中,有一些常见的概念需要了解。
- Joinpoint(连接点):类里面可以被增强的方法即为连接点。例如,想修改哪个方法,那么该方法就是一个连接点。
- Pointcut(切入点):对
Joinpoint
进行拦截的定义即为切入点。例如,拦截所有insert
开始的方法。,这个定义就是切入点。 - Advice(通知):拦截到
Joinpoint
之后要做的操作就是通知。通知分为前置通知、后置通知、异常通知、最终通知和环绕通知。 - Aspect(切面):
Pointcut
和Advice
的结合。 - Target(目标对象):要增强的类称为
Target
。
SpringBoot 在 Spring 的基础上对 AOP 的配置提供了自动化配置解决方案 spring-boot-starter-aop
。
- 引入
spring-boot-starter-aop
依赖
<!-- 引入 AOP 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 创建
UserService
类
@Service
public class UserService {
public String getUserById(Integer id){
System.out.println("get...");
return "user";
}
public void deleteUserById(Integer id){
System.out.println("delete...");
}
}
- 创建切面
/**
* AOP 切面
*/
// @Aspect 注解表明这是一个切面类
@Aspect
public class LogAspect {
// @Pointcut 注解定义切入点定义
// execution 中的第一个 * 表示方法返回任意值
// execution 中的第二个 * 表示 org.sang.aop.service 包下的任意类
// execution 中的第三个 * 表示类中的任意方法
// 括号中的两个点表示地方法参数任意。
// 即,这里描述的切入点为 service 包下所有类中的所有方法
@Pointcut("execution(* org.sang.aop.service.*.*(..))")
public void pc1(){
}
// @Before 表示这是一个前置通知,该方法在目标方法执行前执行。通过 JoinPoint 参数可以获取目标方法名、修饰符等
@Before(value = "pc1()")
public void before(JoinPoint jp){
//这里是 org.aspectj.lang.JoinPoint
//还有一个 org.aopalliance.intercept.Joinpoint
//不要用错了~
String name = jp.getSignature().getName();
System.out.println(name + "方法开始执行...");
}
// @After 表示这是一个后置通知,该方法在目标方法执行后执行。
@After(value = "pc1()")
public void after(JoinPoint jp){
String name = jp.getSignature().getName();
System.out.println(name + "方法执行结束...");
}
// @AfterReturning 表示这是一个返回通知,在该方法中可以获取目标方法的返回值。 returning 参数指返回值的变量名
@AfterReturning(value = "pc1()", returning = "result")
public void afterReturning(JoinPoint jp, Object result){
String name = jp.getSignature().getName();
System.out.println(name + "方法返回值为:" + result);
}
// @AfterThrowing 表示这是一个异常通知,即当目标方法发生异常时,该方法被调用。
@AfterThrowing(value = "pc1()", throwing = "e")
public void afterThrowing(JoinPoint jp, Exception e){
String name = jp.getSignature().getName();
System.out.println(name + "方法发生了异常:" + e.getMessage());
}
// @Around 表示这是一个环绕通知。环绕通知是所有通知中功能最强大的,可以实现所有通知的功能。
// 目标方法进入环绕通知后,可以调用 ProceedingJoinPoint 对象的 proceed 方法使目标继续执行;
// 开发者可以在这里修改目标方法的执行参数、返回值等,并可以在此处理目标方法的异常。
@Around("pc1()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
return pjp.proceed();
}
}
- 创建接口调用
UserService
中的方法测试。
package org.sang.controller;
import org.sang.bean.User;
import org.sang.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Date;
@Controller
public class UserController {
/* AOP 测试:调用 UserService 的两个方法,即可以看到 LogAspect 中的代码动态嵌入目标方法中的执行 */
@Autowired
UserService userService;
@GetMapping("/getUserById")
public String getUserById(Integer id){
return userService.getUserById(id);
}
@GetMapping("/deleteUserById")
public void deleteUserBuId(Integer id){
userService.deleteUserById(id);
}
}
自定义首页
SpringBoot 启动后会先去静态资源路径下查找
index.html
作为首页文件,如果未找到,则会取查找动态的index.html
作为首页文件。
-
如果使用静态首页,只需要在
resources/static
目录下新建index.html
文件即可; -
如果使用动态首页,需要在
resources/templates
目录下新建index.html
文件,并在Controller
中返回逻辑视图名称。@RequestMapping("/index") public String Index(){ return "index"; }