在前面我们已经学习过Srping MVC框架,我们需要配置web.xml、spring mvc配置文件,tomcat,是不是感觉配置较为繁琐。那我们今天不妨来试试使用Spring Boot,Spring Boot让我们的Spring应用变的更轻量化。比如:你可以仅仅依靠一个Java类来运行一个Spring引用。你也可以打包你的应用为jar并通过使用java -jar来运行你的Spring Web应用。

一 Spring Boot简介

1、Spring Boot特点

  • 开箱即用,提供各种默认配置来简化项目配置;
  • 内嵌式容器简化Web项目;
  • 没有冗余代码生成和XML配置的要求;

2、Spring Boot和Spring MVC区别

Spring Boot 是一个快速开发的框架,能够快速的整合第三方常用框架(Maven继承方式),简化XML配置,全部采用注解形式,Spring Boot项目中没有web.xml,内置Http服务器(Tomcat、Jetty),默认嵌入Tomcat服务器,最终是以Java应用程序运行。Spring Boot的Web组件默认集成的是Spring MVC框架,Spring MVC是控制层。

注意:Spring Boot使用注解方式启动Spring MVC,详情参考博客:Spring MVC -- 基于注解的控制器

3、Spring Boot和Spring Cloud区别

Spring Cloud依赖Spring Boot组件,使用Spring Boot编写Http协议接口,Spring Cloud是一套目前完整的微服务框架,功能非常强大。注册中心、客户端调用工具、服务治理(负载均衡、断路器、分布式配置中心、网关、服务链路、消息总线等)。

二、创建第一个Spring Boot项目

环境要求:Java1.8及以上、Spring Framework 4.1.5及以上。

开发工具:IDEA 2019。

1、新建项目

点击File->New Project->选择Maven,点击Next,填写如下信息:

2、pom文件引入依赖

<!--  spring-boot-starter-parent  整合第三方常用框架依赖信息(各种引来信息)-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
</parent> <!-- spring-boot-starter-web 是Spring Boot整合Spring MVC Web -->
<!-- 相当于把第三方常用Maven依赖信息,在parent项目中封装好了,使用Spring Boot提供依赖信息关联整合的Jar包 -->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!-- 为什么不需要版本号,在parent里面已经封装好了版本号 -->
</dependency>
</dependencies>

spring-boot-starter-parent作用:在pom.xml中引入spring-boot-starter-parent,spring官方的解释叫什么stater poms,它可以提供dependency management,也就是说依赖管理,引入以后在申明其它dependency的时候就不需要version了;
spring-boot-starter-web作用:Spring Web 核心组件,整合了Spring MVC所用到的jar,主要包括spring-aop、spring-beans、spring-context、spring-core、spring-web、spring-webmvc;

3、简单demo

创建一个包 com.goldwind.member.controller,新增MemberController.java代码:

package com.goldwind.member.controller;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; /**
* @Author: zy
* @Description: Spring Boot2.0第一个案例
* @Date: 2020-2-2
*/
@RestController
@EnableAutoConfiguration
public class MemberController {
//@RestController注解表示该类中所有方法返回json格式 等价于@Controller+@ReponseBody
//Spring Boot启动原理:Spring MVC注解方式启动,不需要web.xml文件 默认内置tomcat服务器
@RequestMapping("/memberIndex")
public String memberIndex(){
return "Spring Boot2.0全新版本教程!";
} public static void main(String[] args){
//整个程序入口 启动Spring Boot项目
SpringApplication.run(MemberController.class,args);
}
}

在上面代码中,我们使用到了如下注解:

  • @RestController:表示修饰该Controller所有的方法返回JSON格式,直接可以编写Restful接口;

  • @EnableAutoConfiguration注解:作用在于让 Spring Boot 根据应用所声明的依赖来对 Spring 框架进行自动配置,这个注解告诉Spring Boot根据pom中添加的jar依赖猜测你想如何配置Spring。由于spring-boot-starter-web添加了Tomcat和Spring MVC,所以auto-configuration将假定你正在开发一个web应用并相应地对Spring进行设置。

启动主程序,打开浏览器访问http://localhost:8080//memberIndex,可以看到页面输出Spring Boot2.0全新版本教程!

注意:每个请求处理方法可以有多个不同类型的参数,以及一个多种类型的返回结果,具体可以参考 编写请求处理方法。以memberIndex()函数为例,返回值为String类型、由于使用了@RestController注解,表示返回的是json字符串,如果将注解修改为@Controller,则返回的是代表逻辑视图名的String。

4、其它启动方式

小节3中的启动方式,默认只扫描当前包,如果存在多个控制器,是无法扫描到的,我们可以通过@ComponentScan实现,修改MemberController.java文件如下:

package com.goldwind.member.controller;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; /**
* @Author: zy
* @Description: Spring Boot2.0第一个案例
* @Date: 2020-2-2
*/
@EnableAutoConfiguration
@RestController
public class MemberController {
//@RestController注解表示该类中所有方法返回json格式 等价于@Controller+@ReponseBody
//Spring Boot启动原理:Spring MVC注解方式启动,不需要web.xml文件 默认内置tomcat服务器
@RequestMapping("/memberIndex")
public String memberIndex(){
return "Spring Boot2.0全新版本教程!";
}
}

创建启动文件App.java:

package com.goldwind.member.controller;

import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan; /**
* @Author: zy
* @Description: 启动代码
* @Date: 2020-2-2
*/
@ComponentScan("com.goldwind.member.controller")
public class App {
public static void main(String[] args){
//整个程序入口 启动Spring Boot项目
SpringApplication.run(MemberController.class,args);
}
}

5、SpringBootApplication注解使用

如果我们此时再创建一个包 com.goldwind.order.controller,新增OrderController.java代码:

package com.goldwind.order.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; /**
* @Author: zy
* @Description:
* @Date: 2020-2-2
*/
@RestController
public class OrderController {
@RequestMapping("/orderIndex")
public String orderIndex(){
return "orderIndex";
}
}

此时需要修改扫包范围:

@ComponentScan(basePackages = {"com.goldwind.member.controller","com.goldwind.order.controller"})

当包很多的时候,这种写法无疑是麻烦的,此时我们可以使用@SpringBootApplication。

@SpringBootApplication 被 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解所修饰,换言之 Springboot 提供了统一的注解来替代以上三个注解。

扫包范围:在启动类上加上@SpringBootApplication注解,当前包下或者子包下所有的类都可以扫到。

由于App.Java文件位于包 com.goldwind.member.controller,因此使用@SpringBootApplication注解无法扫描到包 com.goldwind.order.controller,需要将App.java文件移动到包 com.goldwind中,并修改其代码如下:

package com.goldwind;

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;/**
* @Author: zy
* @Description: 启动代码
* @Date: 2020-2-2
*/ @SpringBootApplication
public class App {
public static void main(String[] args){
//整个程序入口 启动Spring Boot项目
SpringApplication.run(App.class,args);
}
}

此时,整个项目结构如下:

三、Web开发

这里主要介绍一下在Spring Boot中如何访问静态页面,以及如何动态渲染Web页面,然而随着前后端分离技术的成熟,因此在Spring Boot中渲染Web页面基本不会再使用了。

1、静态资源访问

在我们开发Web应用的时候,需要引入大量的js、css、图片等静态资源。

Spring Boot默认提供静态资源目录位置需置于classpath下,目录名需符合如下规则:

  • /static
  • /public
  • /resources
  • /META-INF/resources

举例:我们可以在src/main/resources/目录下创建static,在该位置放置一个图片文件。启动程序后,尝试访问http://localhost:8080/cjk.jpg。如能显示图片,配置成功。

2、渲染Web页面

在之前的示例中,我们都是通过@RestController来处理请求,所以返回的内容为json对象,那么如果需要渲染html页面的时候,要如何实现呢?

在动态HTML实现上Spring Boot仍然可以完美胜任,并且提供了多种模板引擎的默认配置支持,所以在推荐的模板殷勤下,我们可以很快的上手开发动态网站。

Spring Boot提供了默认配置的模板引擎主要有以下几种:

  • Thymeleaf
  • FreeMarker
  • Velocity
  • Groovy
  • Mustache

Spring Boot建议使用这些模板引擎,避免使用JSP,若一定要使用JSP将无法实现Spring Boot的多种特性,具体可见后文:支持JSP的配置;
当你使用上述模板引擎中的任何一个,它们默认的模板配置路径为:src/main/resources/templates。当然也可以修改这个路径,具体如何修改,可在后续各模板引擎的配置属性中查询并修改。

3、使用Freemarker模板引擎渲染Web视图

pom文件引入依赖包:

<!--   引入freemarker的依赖包   -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

在src/main/resources/创建一个templates文件夹,新建index.ftl文件:

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title></title>
</head>
<body>
${name}
</body>
</html>

创建包com.goldwind.template.controller,新建FTLIndexController.java:

package com.goldwind.template.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import java.util.Map; /**
* @Author: zy
* @Description: 使用Freemarker模板引擎渲染web视图
* @Date: 2020-2-3
*/
@Controller
public class FTLIndexController {
@RequestMapping("/index")
public String index(Map<String, Object> map) {
map.put("name","美丽的天使...");
return "index";
}
}

在src/main/resources下新建freemarker配置文件application.properties:

## Freemarker 配置
##模版存放路径(默认为 classpath:/templates/)
spring.freemarker.template-loader-path=classpath:/templates/
##是否生成缓存,生成环境建议开启(默认为true)
spring.freemarker.cache=false
##编码
spring.freemarker.charset=UTF-8
spring.freemarker.check-template-location=true
##content-type类型(默认为test/html)
spring.freemarker.content-type=text/html
## 设定所有request的属性在merge到模板的时候,是否要都添加到model中(默认为false)
spring.freemarker.expose-request-attributes=false
##设定所有HttpSession的属性在merge到模板的时候,是否要都添加到model中.(默认为false)
spring.freemarker.expose-session-attributes=false
##RequestContext属性的名称(默认为-)
spring.freemarker.request-context-attribute=request
##模板后缀(默认为.ftl)
spring.freemarker.suffix=.ftl

最终项目结构如下:

运行程序,访问http://localhost:8080/index

4、使用jsp渲染Web视图

Spring Boot官方不推荐的jsp,因此这里就不过多介绍如何在Spring Boot中使用jsp,具体可以查找相关博客。

其中有两点需要注意:

  • 创建Spring Boot整合JSP,一定要为war类型项目,否则会找不到页面.;
  • 不要把JSP页面存放在resources/jsp下,因为该路径下不能被访问到;

5、全局捕获异常

创建包com.goldwind.error.controller,新建ErrorController.java文件:

package com.goldwind.error.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; /**
* @Author: zy
* @Description: 全局捕获异常
* @Date: 2012-2-3
*/
@RestController
public class ErrorController {
@RequestMapping("/getUser")
public String getUser(@RequestParam int i){
int j = 1/i;
return "success" + j;
}
}

当我们访问http://localhost:8080/getUser?i=1,返回结果如下:

当我们访问http://localhost:8080/getUser?i=0,将会抛出异常:

针对这种情况,我们可以捕获异常,但是当有很多路由处理函数时,一一捕获效率很低。此时我们可以采用全局捕获异常、使用AOP技术,采用异常通知。

在包com.goldwind.error下,新建GlobalExceptionHandler.java,代码如下:

package com.goldwind.error;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap;
import java.util.Map; /**
* @Author: zy
* @Description: 全局捕获异常:使用AOP技术,采用异常通知
* 1、捕获返回json格式
* 2、捕获返回页面
* @Date: 2020-2-3
*/
@ControllerAdvice(basePackages = "com.goldwind")
public class GlobalExceptionHandler {
//@ResponseBody:返回json格式
//ModelAndView:返回视图页面
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Map<String,Object> errorResult(){
//实际开发中 将错误记录在日志中
Map<String,Object> errorResultMap = new HashMap<>();
errorResultMap.put("errorCode","500");
errorResultMap.put("errorMsg","系统错误");
return errorResultMap;
}
}

再次访问http://localhost:8080/getUser?i=0,返回信息如下:

在上面代码中,我们使用到了如下注解:

  • @ExceptionHandler:表示拦截异常;
  • @ControllerAdvice 是 controller 的一个辅助类,最常用的就是作为全局异常处理的切面类,@ControllerAdvice 可以指定扫描范围,@ControllerAdvice 约定了几种可行的返回值:
  1. 如果是直接返回 model 类的话,需要使用 @ResponseBody 进行 json 转换;
  2. 返回 String,表示跳到某个 view;
  3. 返回 ModelAndView;

四、日志管理

1、使用log4j记录日志

在src/main/resources下新建log4j.properties:

#注意优先级  DEBUG < INFO < WARN < ERROR < FATAL
#log4j.rootLogger = [ level ] , appenderName1, appenderName2, …
#level: 比如在这里设定了INFO级别,则应用程序中所有DEBUG级别的日志信息将不会被打印出来
#appenderName:就是指定日志信息要输出到哪里。可以同时指定多个输出目的地,用逗号隔开。
log4j.rootLogger=info,ERROR,INFO,CONSOLE,DEBUG #控制台输出
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.Target=System.out
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t] [%c] [%p] - %m%n #输出INFO级别以上的日志到文件
log4j.logger.INFO=info
log4j.appender.INFO=org.apache.log4j.DailyRollingFileAppender
log4j.appender.INFO.layout=org.apache.log4j.PatternLayout
log4j.appender.INFO.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t] [%c] [%p] - %m%n
log4j.appender.INFO.datePattern='.'yyyy-MM-dd
log4j.appender.INFO.Threshold =info
log4j.appender.INFO.append=true
log4j.appender.INFO.File=./logs/info/info.log #输出ERROR级别以上的日志到文件
log4j.logger.ERROR=error
log4j.appender.ERROR=org.apache.log4j.DailyRollingFileAppender
log4j.appender.ERROR.layout=org.apache.log4j.PatternLayout
log4j.appender.ERROR.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t] [%c] [%p] - %m%n
log4j.appender.ERROR.datePattern='.'yyyy-MM-dd
log4j.appender.ERROR.Threshold =error
log4j.appender.ERROR.append=true
log4j.appender.ERROR.File=./logs/error/error.log #输出DEBUG级别以上的日志到文件
log4j.logger.DEBUG=DEBUG
log4j.appender.DEBUG=org.apache.log4j.DailyRollingFileAppender
log4j.appender.DEBUG.layout=org.apache.log4j.PatternLayout
log4j.appender.DEBUG.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%t] [%c] [%p] - %m%n
log4j.appender.DEBUG.datePattern='.'yyyy-MM-dd
log4j.appender.DEBUG.Threshold = DEBUG
log4j.appender.DEBUG.append=true
log4j.appender.DEBUG.File=./logs/debug/debug.log

添加pom依赖:

        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<!-- 排除自带的logback依赖 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>

修改GlobalExceptionHandler.java:

package com.goldwind.error;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap;
import java.util.Map; /**
* @Author: zy
* @Description: 全局捕获异常:使用AOP技术,采用异常通知
* 1、捕获返回json格式
* 2、捕获返回页面
* @Date: 2020-2-3
*/
@ControllerAdvice(basePackages = "com.goldwind")
public class GlobalExceptionHandler { private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class); //@ResponseBody:返回json格式
//ModelAndView:返回视图页面
@ExceptionHandler(RuntimeException.class)
@ResponseBody
public Map<String,Object> errorResult(){
//实际开发中 将错误记录在日志中
Map<String,Object> errorResultMap = new HashMap<>();
errorResultMap.put("errorCode","500");
errorResultMap.put("errorMsg","系统错误"); //打印错误消息
logger.error("服务器内部错误"); return errorResultMap;
}
}

再次访问http://localhost:8080/getUser?i=0,将会输出error信息,该信息日志会输出到当前项目./logs./error/error.log文件中:

2020-02-03 18:22 [http-nio-8080-exec-1] [com.goldwind.error.GlobalExceptionHandler] [ERROR] - 服务器内部错误

上面这种方法需要在需要打印日志的类中,创建一个日志写入器对象,然后在每个类中输出日志,那有没有一种统一处理日志的方式,当然有,下面我们将会介绍。

2、使用AOP统一处理Web请求日志

到目前位置我们的项目结构是如下所示:

为了使结构更加清晰明了,我们将所有控制器合并,结构如下:

要想使用AOP统一处理Web请求日志,首先需要添加aop依赖:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

创建包com.goldwind.aop,新建WebLogAspect.java:

package com.goldwind.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration; /**
* @Author: zy
* @Description: 使用AOP统一处理Web请求日志
* @Date: 2020-2-4
*/
@Aspect
@Component
public class WebLogAspect {
private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class); // 拦截包com.goldwind.controller下所有类中的所有方法
@Pointcut("execution(public * com.goldwind.controller.*.*(..))")
public void webLog() {
} /**
* 使用AOP前置通知拦截请求参数信息
* @param joinPoint
* @throws Throwable
*/
@Before("webLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容 一般最多记录半年,然后进行数据迁移、云备份
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest(); // 记录下请求内容
logger.info("URL : " + request.getRequestURL().toString());
logger.info("HTTP_METHOD : " + request.getMethod());
logger.info("IP : " + request.getRemoteAddr());
Enumeration<String> enu = request.getParameterNames();
while (enu.hasMoreElements()) {
String name = (String) enu.nextElement();
logger.info("name:{},value:{}", name, request.getParameter(name));
}
} /**
* 后置通知
* @param ret
* @throws Throwable
*/
@AfterReturning(returning = "ret", pointcut = "webLog()")
public void doAfterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
logger.info("RESPONSE : " + ret);
}
}

运行程序,访问http://localhost:8080/getUser?i=1,将会打印日志信息:

2020-02-04 09:22:48 [http-nio-8080-exec-4] [com.goldwind.aop.WebLogAspect] [INFO] - URL : http://localhost:8080/getUser
2020-02-04 09:22:48 [http-nio-8080-exec-4] [com.goldwind.aop.WebLogAspect] [INFO] - HTTP_METHOD : GET
2020-02-04 09:22:48 [http-nio-8080-exec-4] [com.goldwind.aop.WebLogAspect] [INFO] - IP : 0:0:0:0:0:0:0:1
2020-02-04 09:22:48 [http-nio-8080-exec-4] [com.goldwind.aop.WebLogAspect] [INFO] - name:i,value:1
2020-02-04 09:22:48 [http-nio-8080-exec-4] [com.goldwind.aop.WebLogAspect] [INFO] - RESPONSE : success1

五、Spring Boot中使用lombok

1、lombok的简单使用

Lombok能以简单的注解形式来简化java代码,提高开发人员的开发效率。例如开发中经常需要写的javabean,都需要花时间去添加相应的getter/setter,也许还要去写构造器、equals等方法,而且需要维护,当属性多时会出现大量的getter/setter方法,这些显得很冗长也没有太多技术含量,一旦修改属性,就容易出现忘记修改对应方法的失误。

Lombok能通过注解的方式,在编译时自动为属性生成构造器、getter/setter、equals、hashcode、toString方法。出现的神奇就是在源码中没有getter和setter方法,但是在编译生成的字节码文件中有getter和setter方法。这样就省去了手动重建这些代码的麻烦,使代码看起来更简洁些。接下来我们来分析Lombok中注解的具体用法。

  • @NonNull : 让你不在担忧并且爱上NullPointerException;
  • @CleanUp : 自动资源管理:不用再在finally中添加资源的close方法;
  • @Setter/@Getter : 自动生成set和get方法;
  • @ToString : 自动生成toString方法;
  • @EqualsAndHashcode : 从对象的字段中生成hashCode和equals的实现;
  • @NoArgsConstructor/@RequiredArgsConstructor/@AllArgsConstructor : 自动生成构造方法;
  • @Data : 自动生成set/get方法,toString方法,equals方法,hashCode方法,不带参数的构造方法;
  • @Value : 用于注解final类;
  • @Builder : 产生复杂的构建器api类;
  • @SneakyThrows : 异常处理(谨慎使用);
  • @Synchronized : 同步方法安全的转化;
  • @Getter(lazy=true) :;
  • @Log : 支持各种logger对象,使用时用对应的注解,如:@Slf4j;其内部具体实现和4.1节中介绍的一样,配置文件是log4j.properties;

下面将会演示下面我们主要演示一个@Getter、@Setter的使用,更多使用可以参考博客:Lombok介绍、使用方法和总结

首先安装lombok插件,点击File->Setting->Plugins->搜索Lombok:

Lombok的使用跟引用jar包一样,可以在官网(https://projectlombok.org/download)下载jar包,也可以使用maven添加依赖:

<!--  lombok使用     -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
<scope>provided</scope>
</dependency>

创建包com.goldwind.entity,新建UserEntity.java:

package com.goldwind.entity;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; /**
* @Author: zy
* @Description: User实体类 lombok使用
* lombok底层使用字节码技术 ASM 修改字节码文件,生成get和set方法
* @Date: 2020-2-4
*/
@Slf4j
public class UserEntity { @Getter
@Setter
private String userName; @Getter
@Setter
private Integer age; @Override
public String toString() {
return "UserEntity [userName=" + userName + ", age=" + age + "]";
} /**
* 测试
* @param args
*/
public static void main(String[] args) {
UserEntity userEntity = new UserEntity();
userEntity.setUserName("zhangsan");
userEntity.setAge(20);
System.out.println(userEntity.toString());
log.info("####我是日志##########");
}
}

2、Lombok工作原理分析

会发现在Lombok使用的过程中,只需要添加相应的注解,无需再为此写任何代码。自动生成的代码到底是如何产生的呢?

核心之处就是对于注解的解析上。JDK5引入了注解的同时,也提供了两种解析方式。

  • 运行时解析

运行时能够解析的注解,必须将@Retention设置为RUNTIME,这样就可以通过反射拿到该注解。java.lang,reflect反射包中提供了一个接口AnnotatedElement,该接口定义了获取注解信息的几个方法,Class、Constructor、Field、Method、Package等都实现了该接口,对反射熟悉的朋友应该都会很熟悉这种解析方式。

  • 编译时解析

编译时解析有两种机制,分别简单描述下:

1)Annotation Processing Tool

apt自JDK5产生,JDK7已标记为过期,不推荐使用,JDK8中已彻底删除,自JDK6开始,可以使用Pluggable Annotation Processing API来替换它,apt被替换主要有2点原因:

  • api都在com.sun.mirror非标准包下
  • 没有集成到javac中,需要额外运行

2)Pluggable Annotation Processing API

JSR 269自JDK6加入,作为apt的替代方案,它解决了apt的两个问题,javac在执行的时候会调用实现了该API的程序,这样我们就可以对编译器做一些增强,这时javac执行的过程如下: 

Lombok本质上就是一个实现了“JSR 269 API”的程序。在使用javac的过程中,它产生作用的具体流程如下:

  1. javac对源代码进行分析,生成了一棵抽象语法树(AST);
  2. 运行过程中调用实现了“JSR 269 API”的Lombok程序;
  3. 此时Lombok就对第一步骤得到的AST进行处理,找到@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter方法定义的相应树节点;
  4. javac使用修改后的抽象语法树(AST)生成字节码文件,即给class增加新的节点(代码块);

在Lombok源码中,对应注解的实现都在HandleXXX中,比如@Getter注解的实现是HandleGetter.handle()。还有一些其它类库使用这种方式实现,比如Google AutoDagger等等。

3. Lombok的优缺点

优点:

  • 能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,提高了一定的开发效率;
  • 让代码变得简洁,不用过多的去关注相应的方法;
  • 属性做修改时,也简化了维护为这些属性所生成的getter/setter方法等;

缺点:

  • 不支持多种参数构造器的重载;
  • 虽然省去了手动创建getter/setter方法的麻烦,但大大降低了源代码的可读性和完整性,降低了阅读源代码的舒适度;

4. 总结

Lombok虽然有很多优点,但Lombok更类似于一种IDE插件,项目也需要依赖相应的jar包。Lombok依赖jar包是因为编译时要用它的注解,为什么说它又类似插件?因为在使用时,eclipse或IntelliJ IDEA都需要安装相应的插件,在编译器编译时通过操作AST(抽象语法树)改变字节码生成,变向的就是说它在改变java语法。它不像spring的依赖注入或者mybatis的ORM一样是运行时的特性,而是编译时的特性。这里我个人最感觉不爽的地方就是对插件的依赖!因为Lombok只是省去了一些人工生成代码的麻烦,但IDE都有快捷键来协助生成getter/setter等方法,也非常方便。

知乎上有位大神发表过对Lombok的一些看法:

这是一种低级趣味的插件,不建议使用。JAVA发展到今天,各种插件层出不穷,如何甄别各种插件的优劣?能从架构上优化你的设计的,能提高应用程序性能的 ,
实现高度封装可扩展的..., 像lombok这种,像这种插件,已经不仅仅是插件了,改变了你如何编写源码,事实上,少去了代码你写上去又如何?
如果JAVA家族到处充斥这样的东西,那只不过是一坨披着金属颜色的屎,迟早会被其它的语言取代。

虽然话糙但理确实不糙,试想一个项目有非常多类似Lombok这样的插件,个人觉得真的会极大的降低阅读源代码的舒适度。

虽然非常不建议在属性的getter/setter写一些业务代码,但在多年项目的实战中,有时通过给getter/setter加一点点业务代码,能极大的简化某些业务场景的代码。所谓取舍,也许就是这时的舍弃一定的规范,取得极大的方便。

我现在非常坚信一条理念,任何编程语言或插件,都仅仅只是工具而已,即使工具再强大也在于用的人,就如同小米加步枪照样能赢飞机大炮的道理一样。结合具体业务场景和项目实际情况,无需一味追求高大上的技术,适合的才是王道。

Lombok有它的得天独厚的优点,也有它避之不及的缺点,熟知其优缺点,在实战中灵活运用才是王道。

参考文章:

[1] Lombok介绍、使用方法和总结(部分转载)

[2] spring中注解的实现原理

Spring Boot -- 认识Spring Boot的更多相关文章

  1. 一:Spring Boot、Spring Cloud

    上次写了一篇文章叫Spring Cloud在国内中小型公司能用起来吗?介绍了Spring Cloud是否能在中小公司使用起来,这篇文章是它的姊妹篇.其实我们在这条路上已经走了一年多,从16年初到现在. ...

  2. Spring Kafka和Spring Boot整合实现消息发送与消费简单案例

    本文主要分享下Spring Boot和Spring Kafka如何配置整合,实现发送和接收来自Spring Kafka的消息. 先前我已经分享了Kafka的基本介绍与集群环境搭建方法.关于Kafka的 ...

  3. 使用Spring Session实现Spring Boot水平扩展

    小编说:本文使用Spring Session实现了Spring Boot水平扩展,每个Spring Boot应用与其他水平扩展的Spring Boot一样,都能处理用户请求.如果宕机,Nginx会将请 ...

  4. 一起来学spring Cloud | 第一章:spring Cloud 与Spring Boot

    目前大家都在说微服务,其实微服务不是一个名字,是一个架构的概念,大家现在使用的基于RPC框架(dubbo.thrift等)架构其实也能算作一种微服务架构. 目前越来越多的公司开始使用微服务架构,所以在 ...

  5. Spring,Spring MVC及Spring Boot区别

    什么是Spring?它解决了什么问题? 我们说到Spring,一般指代的是Spring Framework,它是一个开源的应用程序框架,提供了一个简易的开发方式,通过这种开发方式,将避免那些可能致使代 ...

  6. 基于Spring Boot、Spring Cloud、Docker的微服务系统架构实践

    由于最近公司业务需要,需要搭建基于Spring Cloud的微服务系统.遍访各大搜索引擎,发现国内资料少之又少,也难怪,国内Dubbo正统治着天下.但是,一个技术总有它的瓶颈,Dubbo也有它捉襟见肘 ...

  7. Spring Cloud Alibaba与Spring Boot、Spring Cloud之间不得不说的版本关系

    这篇博文是临时增加出来的内容,主要是由于最近连载<Spring Cloud Alibaba基础教程>系列的时候,碰到读者咨询的大量问题中存在一个比较普遍的问题:版本的选择.其实这类问题,在 ...

  8. Spring Cloud和Spring Boot的区别

    Spring MVC: Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面.Spring 框架提供了构建 Web 应用程序的全功能 MVC ...

  9. maven 聚合工程 用spring boot 搭建 spring cloud 微服务 模块式开发项目

    项目的简单介绍: 项目采用maven聚合工程 用spring boot 搭建 spring cloud的微服务 模块式开发 项目的截图: 搭建开始: 能上图 我少打字 1.首先搭建maven的聚合工程 ...

  10. Spring Boot(Spring的自动整合框架)

    Spring Boot 是一套基于Spring框架的微服务框架,由于Spring是一个轻量级的企业开发框架,主要功能就是用于整合和管理其他框架,想法是将平时主流使用到的框架的整合配置预先写好,然后通过 ...

随机推荐

  1. DC: 8-Write-up

    下载地址:点我 哔哩哔哩:点我 信息收集 确定网段,找到虚拟机的IP,扫端口和服务. ➜ ~ nmap -sn 192.168.116.1/24 Starting Nmap 7.80 ( https: ...

  2. leetcode #980 不同路径||| (java)

    在二维网格 grid 上,有 4 种类型的方格: 1 表示起始方格.且只有一个起始方格.2 表示结束方格,且只有一个结束方格.0 表示我们可以走过的空方格.-1 表示我们无法跨越的障碍.返回在四个方向 ...

  3. InnoDB中一棵B+树能存多少行数据

    https://www.jianshu.com/p/3578beed5a68 https://www.cnblogs.com/tongongV/p/10952102.html InnoDB 存储引擎最 ...

  4. Tarjan算法与割点割边

    目录 Tarjan算法与无向图的连通性 1:基础概念 2:Tarjan判断割点 3:Tarjan判断割边 Tarjan算法与无向图的连通性 1:基础概念 在说Tarjan算法求解无向图的连通性之前,先 ...

  5. Educational Codeforces Round 72 (Rated for Div. 2)C(暴力)

    #define HAVE_STRUCT_TIMESPEC#include<bits/stdc++.h>using namespace std;char s[200007];int a[20 ...

  6. 学习不一样的Vue1:环境搭建

    学习不一样的Vue1:环境搭建  发表于 2017-05-31 |  分类于 web前端|  |  阅读次数 11677 首先 首发博客: 我的博客 项目源码: 源码 项目预览: 预览 因为个人的喜好 ...

  7. Linux centosVMware Tomcat介绍、安装jdk、安装Tomcat

    一.Tomcat介绍 Tomcat是Apache软件基金会(Apache Software Foundation)的Jakarta项目中的一个核心项目,由Apache.Sun和其他一些公司及个人共同开 ...

  8. 从零开始编写IntelliJ IDEA插件

    写Java代码的时候,经常会涉及到重复性的操作,这个时候就会想要是有这样一个插件就好了,如果是大家都会遇到的场景,IDE或许已经提供了,再不然也有可能有人编写了相关的插件.要是这个操作是你们的编码环境 ...

  9. 使用oracle 的 PL/Sql 定时执行一个存储过程

    CSDN日报20170322--<关于软件研发的一些体会总结> 同步博客至 CSDN ,让更多开发者看到你的文章 看微博技术大咖解析互联网应用架构实战 使用oracle 的 PL/Sql ...

  10. Lesson 13 The search for oil

    What do oilmen want to achieve as soon as they strike oil? The deepest holes of all are made for oil ...