• Spring Boot -- 认识Spring Boot


    在前面我们已经学习过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 MVC Web  -->
        <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;

    这里主要是利用的maven的父子依赖关系实现的。

    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中注解的实现原理

  • 相关阅读:
    ffmpeg filter过滤器 基础实例及全面解析
    gcc的memcpy性能测试
    repo基本操作
    repo或者git diff -uno差异文件全路径备份
    cmake视频教程
    Rust Macro assert!
    Rust迭代器测试
    钟南山每天喝牛奶
    查网页的最后修改时间
    王勃学习车辆超速监测管理系统
  • 原文地址:https://www.cnblogs.com/zyly/p/12251179.html
Copyright © 2020-2023  润新知