网站首页 包含标签 Java 的所有文章

  • logback详解及入门案例

    1. logback 日志的作用 记录系统和接口的使用情况,比如请求日志 记录和分析用户的行为,比如网站访问日志 调试程序,和控制台的作用类似,但是控制台中的内容并不会保存到文件中,而日志可以长期保存。 帮助我们排查和定位错误。比如在系统抛出异常时,将异常信息记录到日志,可以事后复盘。 通过分析日志还能够优化代码逻辑、提升系统性能、稳定性等。 1.1. logback介绍 Logback继承自log4j。Logback的架构非常的通用,适用于不同的使用场景。 通过上图可以看到logback和Log4j都是slf4j规范的具体实现,我们在程序中直接调用的API其实都是slf4j的api,底层则是真正的日志实现组件—logback或者log4j。 Logback 构建在三个主要的类上:Logger,Appender 和 Layout。这三个不同类型的组件一起作用能够让开发者根据消息的类型以及日志的级别来打印日志。 Logger作为日志的记录器,把它关联到应用的对应的context后,主要用于存放日志对象,也可以定义日志类型、级别。各个logger 都被关联到一个 LoggerContext,LoggerContext负责制造logger,也负责以树结构排列各 logger。 Appender主要用于指定日志输出的目的地,目的地可以是控制台、文件、 数据库等。 Layout 负责把事件转换成字符串,输出格式化的日志信息。 logback的maven坐标: <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.12</version> </dependency> 1.2. logback层级 在 logback中每一个 logger 都依附在 LoggerContext 上,它负责产生 logger,并且通过一个树状的层级结构来进行管理。 一个 Logger 被当作为一个实体,它们的命名是大小写敏感的,并且遵循以下规则: 如果一个logger的名字加上一个**.**作为另一个logger名字的前缀,那么该logger就是另一个logger的祖先。如果一个logger与另一个logger之间没有其它的logger,则该logger就是另一个logger的父级。 举例: 名为com.zbbmeta的logger是名为com.zbbmeta.service的logger的父级 名为com的logger是名为com.zbbmeta的logger的父级,是名为com.zbbmetat.service的logger的祖先 在logback中有一个root logger,它是logger层次结构的最高层,它是一个特殊的logger,因为它是每一个层次结构的一部分。 1.3. logback日志输出等级 logback的日志输出等级分为:TRACE, DEBUG, INFO, WARN, ERROR。 如果一个给定的logger没有指定一个日志输出等级,那么它就会继承离它最近的一个祖先的层级。 为了确保所有的logger都有一个日志输出等级,root logger会有一个默认输出等级 — DEBUG。 1.4 logback初始化步骤 logback会在类路径下寻找名为logback-test.xml的文件 如果没有找到,logback会继续寻找名为logback.groovy的文件 如果没有找到,logback会继续寻找名为logback.xml的文件 如果没有找到,将会在类路径下寻找文件META-INFO/services/ch.qos.logback.classic.spi.Configurator,该文件的内容为实现了Configurator接口的实现类的全限定类名 如果以上都没有成功,logback会通过BasicConfigurator为自己进行配置,并且日志将会全部在控制台打印出来 最后一步的目的是为了保证在所有的配置文件都没有被找到的情况下,提供一个默认的配置。 2. logback入门案例 2.1. 案例一 本案例是一个logback简单应用,并且不提供配置文件而使用其提供的默认配置。 第一步:创建maven工程logback-demo并配置pom.xml文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.zbbmeta</groupId> <artifactId>logback-demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.12</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies> </project> 第二步:编写单元测试 package com.zbbmeta.logback; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.LoggerContext; import ch.qos.logback.core.util.StatusPrinter; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 测试logback日志组件的使用 */ public class LogbackTest { //简单使用 @Test public void test1(){ //通过工厂对象获得一个logger日志记录器对象 Logger logger = LoggerFactory.getLogger("com.zbbmeta.test"); //当前logger对象的日志输出级别为debug,从root logger继承来的 //使用trace级别记录日志 logger.trace("trace..."); logger.debug("debug..."); logger.info("info..."); logger.warn("warn..."); logger.error("error..."); } //打印日志内部状态 @Test public void test2(){ //通过工厂对象获得一个logger日志记录器对象 Logger logger = LoggerFactory.getLogger("com.zbbmeta.test"); //当前logger对象的日志输出级别为debug,从root logger继承来的 //使用trace级别记录日志 logger.trace("trace..."); logger.debug("debug..."); logger.info("info..."); logger.warn("warn..."); logger.error("error..."); // 打印内部的状态 LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory(); StatusPrinter.print(lc); // 说明找文件步骤-->logback-test.xml-->logback.xml-->configuration /* |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml] |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.xml] |-INFO in ch.qos.logback.classic.BasicConfigurator@691a7f8f - Setting up default configuration.*/ } /* * 日志输出级别:ERROR > WARN > INFO > DEBUG > TRACE * */ //测试默认的日志输出级别 @Test public void test3(){ Logger logger = LoggerFactory.getLogger("com.zbbmeta.logback.HelloWorld"); logger.error("error ..."); logger.warn("warn ..."); logger.info("info ..."); logger.debug("debug ..."); //因为默认的输出级别为debug,所以这一条日志不会输出 logger.trace("trace ..."); } //设置日志输出等级 @Test public void test4(){ ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger)LoggerFactory.getLogger("com.zbbmeta.logback.HelloWorld"); logger.setLevel(Level.DEBUG);//设置当前日志记录器对象的日志输出级别 logger.error("error ..."); logger.warn("warn ..."); logger.info("info ..."); logger.debug("debug ..."); //因为默认的输出级别为debug,所以这一条日志不会输出 logger.trace("trace ..."); } //测试Logger的继承 @Test public void test5(){ ch.qos.logback.classic.Logger logger = (ch.qos.logback.classic.Logger)LoggerFactory.getLogger("com.zbbmeta"); logger.setLevel(Level.TRACE);//设置当前日志记录器对象的日志输出级别 //当前记录器对象是上面记录器对象的子级,从父级继承输出等级---INFO Logger logger1 = LoggerFactory.getLogger("com.zbbmeta.test"); logger1.error("error ..."); logger1.warn("warn ..."); logger1.info("info ..."); logger1.debug("debug ..."); logger1.trace("trace ..."); } //Logger获取,根据同一个名称获得的logger都是同一个实例 @Test public void test6(){ Logger logger1 = LoggerFactory.getLogger("com.zbbmeta.test"); Logger logger2 = LoggerFactory.getLogger("com.zbbmeta.test"); System.out.println(logger1 == logger2); } //参数化日志 @Test public void test7(){ Logger logger = LoggerFactory.getLogger("com.zbbmeta"); String value = "world"; logger.info("参数值为:" + value); logger.debug("hello {}","world"); } } 2.2 案例二 本案例是logback中Spring Boot项目中的应用。 第一步:创建maven工程springboot-logback-demo并配置pom文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.7.15</version> </parent> <groupId>com.zbbmeta</groupId> <artifactId>springboot-logback-demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> </dependencies> </project> 第二步:在resources下编写logback配置文件logback-base.xml和logback-spring.xml logback-base.xml:作为基础配置 <?xml version="1.0" encoding="UTF-8"?> <included> <contextName>logback</contextName> <!-- name的值是变量的名称,value的值时变量定义的值 定义变量后,可以使“${}”来使用变量 --> <property name="log.path" value="logs" /> <!-- 彩色日志 --> <!-- 彩色日志依赖的渲染类 --> <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" /> <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" /> <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" /> <!-- 彩色日志格式 --> <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/> <!--输出到控制台--> <appender name="LOG_CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <Pattern>${CONSOLE_LOG_PATTERN}</Pattern> <!-- 设置字符集 --> <charset>UTF-8</charset> </encoder> </appender> <!--输出到文件--> <appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <!-- 正在记录的日志文件的路径及文件名 --> <file>${log.path}/logback.log</file> <!--日志文件输出格式--> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> <charset>UTF-8</charset> </encoder> <!-- 日志记录器的滚动策略,按日期,按大小记录 --> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 每天日志归档路径以及格式 --> <fileNamePattern>${log.path}/info/log-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize> </timeBasedFileNamingAndTriggeringPolicy> <!--日志文件保留天数--> <maxHistory>15</maxHistory> </rollingPolicy> </appender> </included> logback-spring.xml <?xml version="1.0" encoding="UTF-8"?> <configuration> <!--引入其他配置文件--> <include resource="logback-base.xml" /> <!-- <logger>用来设置某一个包或者具体的某一个类的日志打印级别、 以及指定<appender>。<logger>仅有一个name属性, 一个可选的level和一个可选的addtivity属性。 name:用来指定受此logger约束的某一个包或者具体的某一个类。 level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF, 如果未设置此属性,那么当前logger将会继承上级的级别。 addtivity:是否向上级logger传递打印信息。默认是true。 --> <!--开发环境--> <springProfile name="dev"> <logger name="com.zbbmeta.controller" additivity="false" level="debug"> <appender-ref ref="LOG_CONSOLE"/> </logger> </springProfile> <!--生产环境--> <springProfile name="pro"> <logger name="com.zbbmeta.controller" additivity="false" level="info"> <appender-ref ref="LOG_FILE"/> </logger> </springProfile> <!-- root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性 level:设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF 默认是DEBUG 可以包含零个或多个元素,标识这个appender将会添加到这个logger。 --> <root level="info"> <appender-ref ref="LOG_CONSOLE" /> <appender-ref ref="LOG_FILE" /> </root> </configuration> 第三步:编写application.yml server: port: 8999 #logging: # #在Spring Boot项目中默认加载类路径下的logback-spring.xml文件 # config: classpath:logback.xml logging: #在Spring Boot项目中默认加载类路径下的logback-spring.xml文件 config: classpath:logback-spring.xml spring: profiles: active: dev 第四步:创建TutorialController package com.zbbmeta.controller; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; import java.util.Objects; /** * @author springboot * @description: TODO */ @RestController @RequestMapping("/api") public class TutorialController { private static final Logger log = LoggerFactory.getLogger(TutorialController.class); /** * 根据ID查询Tutorial * @param id * @return */ @GetMapping("/tutorials/{id}") public void getTutorialById(@PathVariable("id") long id) { log.trace("trace..."); log.debug("debug..."); log.info("info..."); log.warn("warn..."); log.error("error..."); } } 第五步:创建启动类 package com.zbbmeta; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * @author springboot * @description: TODO */ @SpringBootApplication public class LogbackDemoApplication { public static void main(String[] args) { SpringApplication.run(LogbackDemoApplication.class,args); } } 启动项目,访问地址:http://localhost:8999/api/tutorials/1 可以看到控制台已经开始输出日志信息。 修改application.yml文件中的开发模式为pro,重启项目这日志输出到了文件中。 ...

    2023-12-22 值得一看 161
  • Java8 reduce操作详解

    什么是reduce Java8 中有两大最为重要的改变,其一是 Lambda 表达式,另一个就是 Stream API 了。 Stream 是 Java8 中处理集合的关键抽象概念,它将数据源流化后,可以执行非常复杂的查找、过滤和映射数据、排序、切片、聚合统计等操作。操作之后会产生一个新的流,而数据源则不会发生改变。 在使用 Stream 操作的过程中,往往有三个步骤, 1. 创建Stream 从一个数据源(集合,数组)中,新建一个 Stream 流。 2. 中间操作 一个中间操作链,对 Stream 流的数据进行处理。比如查找、过滤、映射转换等。 3. 终止操作 一个终止操作,执行中间操作链,并产生结果。常用的终止操作有 forearch、collect、match、count、min、max、reduce 等。 其中本文主要讲解的 reduct 操作就属于是 Stream 流操作中的终止操作。 reduce 操作是一种通用的归约操作,它可以实现从 Stream 中生成一个值,其生成的值不是随意的,而是根据指定的计算模型。 比如终止操作中提到 count、min 和 max 方法,因为常用而被纳入标准库中。事实上这些方法都是一种 reduce 操作。 本文大纲如下, reduce 操作三要素 为了方便大家理解 reduce (规约)操作,先给大家演示一下 reduce 操作的相关代码示例, List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); int result = numbers .stream() .reduce(0, (subtotal, element) -> subtotal + element); assertThat(result).isEqualTo(21); 可以看到,我们的 reduce 操作接受了三个参数,返回了一个 int 基本类型。 在 Stream API 中,提供了三个 reduct 操作方法,根据参数不同进行区分。 对应上方代码示例,也就是使用了接受两个参数的 reduce 方法,但其实接受两个参数的 reduce 方法的代码逻辑是和接受三个参数的 reduce 方法是一致的。通过上方截图可以看出。 所以这里,我就直接给大家介绍下 reduce 操作的三个参数分别有什么作用即可。 identiy 参数 identiy(初始值)是 reduce 操作的初始值,也就是当元素集合为空时的默认结果。对应上方代码示例,也就是说 reduce 操作的初始值是 0。 accumulator 参数 accumulator(累加器)是一个函数,它接受两个参数,reduce 操作的部分元素和元素集合中的下一个元素。它返回一个新的部分元素。在这个例子中,累加器是一个 lambda 表达式,它将集合中两个整数相加并返回一个整数:(a, b) -> a + b。 combiner 参数 combiner(组合器)是一个函数,它用于在 reduce 操作被并行化或者当累加器的参数类型和实现类型不匹配时,将 reduce 操作的部分结果进行组合。在上面代码示例中,我们不需要使用组合器,因为上面我们的 reduce 操作不需要并行,而且累加器的参数类型和实现类型都是 Integer。 为了方便大家理解 reduce 操作的内部逻辑,我给大家绘制了上面代码示例的执行示意图,如下, 如何使用 reduce 操作 为了更好地理解初始值,累加器和组合器的功能,让我们看一些基本的例子。 使用 reduce 查询整数集合的最小值 // 创建一个整数集合 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); // 找出集合中的最小值 Integer min = numbers.stream().reduce((integer, integer2) -> { if (integer < integer2) { return integer; } else { return integer2; } }).get(); // 输出结果 System.out.println(min); // 1 在这个例子中,我们使用了一个参数的 reduce 操作,它接受一个累加器函数。累加器函数会返回集合两个元素中,较小的元素。 最终我们就可以找出集合中最小值 1。 使用 reduce 操作拼接字符串列表 我们可以对一个字符串列表使用 reduce 操作,将它们拼接成一个单一的字符串: // 创建一个字符串列表 List<String> letters = Arrays.asList ("a", "b", "c", "d", "e"); // 使用 reduce 操作拼接字符串列表 String result = letters .stream () .reduce ("", (partialString, element) -> partialString + element); // 输出结果 System.out.println (result); // abcde 在这个例子中,我们将初始值设为 “”,累加器函数设为 (a, b) -> a + b,它表示将两个字符串拼接起来。 我们可以看到,reduce 操作将累加器函数反复应用到列表中的每个元素上,得到最终的结果 abcde。 使用并行流计算整数列表的总和 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5,6); // 使用并行流和 reduce() 方法计算整数列表的总和 int result = numbers.parallelStream() .reduce(0, (a, b) -> a + b, Integer::sum); // 输出结果 System.out.println(result); // 21 在这个例子中,我们使用 parallelStream() 方法将列表转换为并行流,再使用 reduce() 方法对整数列表进行 reduce 操作,并使用 Integer::sum 作为合并函数 combiner,将并行计算的结果合并。 使用并行流的好处能够充分利用多核 CPU 的优势,使用多线程加快对集合数据的处理速度。 不过并行流也不是任何时候都可以使用的,并行流执行过程中实际按照多线程执行,多线程编程有的问题,并行流都有。 比如多线程的线程安全,执行顺序等问题,并行流都是有的。这一点需要大家注意。 最后聊两句 本文介绍了 Java8 Stream 流中,reduce 操作的相关概念和接收参数,包含初始值,累加器和组合器,最后介绍了 reduce 操作如何使用,希望大家喜欢。 ...

    2023-12-22 值得一看 125
  • 什么是Tomcat,安装及配置教程

    1、Tomcat 介绍 什么是 Tomcat Tomcat 是 Apache 软件基金会一个核心项目,是一个开源免费的轻量级 web 服务器,支持 Servlet / jsp 少量JavaEE规范,Tomcat 也被称为 Web 容器、Servlet 容器。 官网:https://tomcat.apache.org/ 什么是 JavaEE JavaEE:Java Enterprise Edition,Java 企业版。指 Java 企业级开发的技术规范总和。 包含 13 项技术规范:JDBC、JNDI、EJB、RMI、JSP、Servlet、XML、JMS、Java IDL、JTS、JTA、JavaMail、JAF 2、Tomcat 使用配置 2.1、Tomcat 下载启动 Tomcat 下载安装 下载:https://tomcat.apache.org/ 安装:Tomcat 是绿色版,直接解压就可以了,建议:不要有中文的目录,目录层次不要太深 打开apache-tomcat目录就能看到如下目录结构,每个目录中包含的内容需要认识下 Tomcat 启动关闭 启动:双击:bin\startup.bat 关闭 直接 x 掉运行窗口:强制关闭 bin\shutdown.bat:正常关闭 ctrl+c:正常关闭 Tomcat 访问 访问方式:浏览器输入localhost:8080,Tomcat 默认端口是 8080 2.2、Tomcat 启动乱码 问题 控制台有中文乱码,需要修改 conf/logging.prooperties 进入 Tomcat 的 conf 目录 找到 logging.properties 文件 将里面所有的 UTF-8 替换成 GBK 注意:建议使用 Vscode 打开或者其他工具,直接查找替换,避免发生错误 2.3、Tomcat 端口号修改 进入 Tomcat 的 conf 目录下 找到 server.xml 文件,打开 找到下列代码位置,大概在 69 行,修改 port=你想要的位置 <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /> 端口号冲突 3、Tomcat 项目部署 项目部署的两种方法 直接将项目复制到 webapps 目录下 采用压缩文件.war 的方式 将整个项目使用压缩工具打包成 zip 文件 改 zip 的扩展名为 war 复制到 webapps 目录下,tomcat 会自动解压成一个同名的目录 注意:里面的文件不能有中文名 部署演示:直接将项目复制到 webapps 目录下 项目名为:hello,将其文件复制到 webapps 目录下 启动 Tomcat 后,访问 hello 部署演示:采用压缩文件.war 的方式 将项目压缩成 zip 文件 改 zip 的扩展名为 war 复制到 webapps 目录下,tomcat 会自动解压成一个同名的目录 访问测试 4、IDEA 中使用 Tomcat 方式 通过 Maven 的 package 命令可以将项目打包成 war 包,将 war 文件拷贝到 Tomcat 的 webapps 目录下,启动 Tomcat 就可以将项目部署成功,然后通过浏览器进行访问即可。 然而我们在开发的过程中,项目中的内容会经常发生变化,如果按照上面这种方式来部署测试,是非常不方便的 如何在 IDEA 中能快速使用 Tomcat 呢? 将本地 Tomcat 集成到 IDEA 中 打开添加本地 Tomcat 的面板 指定本地 Tomcat 配置本地 Tomcat 将项目部署到集成 Tomcat 里面 扩展内容:xxx.war 和 xxx.war exploded 这两种部署项目模式的区别? war 模式是将 WEB 工程打成 war 包,把 war 包发布到 Tomcat 服务器上 war exploded 模式是将 WEB 工程以当前文件夹的位置关系发布到 Tomcat 服务器上 war 模式部署成功后,Tomcat 的 webapps 目录下会有部署的项目内容 war exploded 模式部署成功后,Tomcat 的 webapps 目录下没有,而使用的是项目的 target 目录下的内容进行部署 建议大家都选 war 模式进行部署,更符合项目部署的实际情况 ...

    2023-12-18 技术教程 314
  • Spring Boot与HttpClient:轻松实现GET和POST请求

    1. HttpClient介绍 HttpClient 是Apache Jakarta Common 下的子项目,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。   1.1. HttpClient作用 发送HTTP请求 接收响应数据 思考:为什么要在Java程序中发送Http请求?有哪些应用场景呢? 1.2. HttpClient应用场景 当我们在使用扫描支付、查看地图、获取验证码、查看天气等功能时 其实,应用程序本身并未实现这些功能,都是在应用程序里访问提供这些功能的服务,访问这些服务需要发送HTTP请求,并且接收响应数据,可通过HttpClient来实现。 1.3 HttpClient的maven坐标 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> 1.4. HttpClient的核心API HttpClient:Http客户端对象类型,使用该类型对象可发起Http请求。 HttpClients:可认为是构建器,可创建HttpClient对象。 CloseableHttpClient:实现类,实现了HttpClient接口。 HttpGet:Get请求类型。 HttpPost:Post请求类型。 1.5. HttpClient发送请求步骤 创建HttpClient对象 创建Http请求对象 调用HttpClient的execute方法发送请求 2. SpringBoot快速入门HttpClient 对HttpClient编程工具包有了一定了解后,那么,我们使用HttpClient在Java程序当中来构造Http的请求,并且把请求发送出去,接下来,就通过入门案例分别发送GET请求和POST请求,具体来学习一下它的使用方法。 项目结构 创建HttpClient-demo,并导入对应依赖到pom.xml 完整项目结果   pom.xml 依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.7.15</version> </parent> <groupId>com.zbbmeta</groupId> <artifactId>HttpClient-demo</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.2</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.13</version> </dependency> </dependencies> </project> 2.1. GET方式请求 实现步骤: 创建HttpClient对象 创建请求对象 发送请求,接受响应结果 解析结果 关闭资源 在com.zbbmeta.controller包下创建HttpClientController接口类,并创建testGET()方法 package com.zbbmeta.controller; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicHeader; import org.apache.http.util.EntityUtils; import org.junit.jupiter.api.Test; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Arrays; /** * @author springboot葵花宝典 * @description: TODO */ @RestController @RequestMapping(value = "/httpclient") @Slf4j @Api(tags = "HttpClient测试接口") public class HttpClientController { @GetMapping("/httpget") @ApiOperation(value = "http员工状态") public String testGET() throws Exception{ //创建httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); //创建请求对象 HttpGet httpGet = new HttpGet("http://localhost:8899/admin/employee/status"); //发送请求,接受响应结果 CloseableHttpResponse response = httpClient.execute(httpGet); //获取服务端返回的状态码 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("服务端返回的状态码为:" + statusCode); HttpEntity entity = response.getEntity(); String body = EntityUtils.toString(entity); System.out.println("服务端返回的数据为:" + body); //关闭资源 response.close(); httpClient.close(); return "服务端返回的数据为:" + body; }n "服务端返回的数据为:" + body; } } 启动项目,PostMan访问http://localhost:8899/httpclient/httpget请求 测试结果   2.2. POST方式请求 在HttpClientTest中添加POST方式请求方法,相比GET请求来说,POST请求若携带参数需要封装请求体对象,并将该对象设置在请求对象中。 实现步骤: 创建HttpClient对象 创建请求对象 发送请求,接收响应结果 解析响应结果 关闭资源 在com.zbbmeta.controller包下创建HttpClientController接口类,并创建testPost()方法 @PostMapping("/httppost") @ApiOperation(value = "http员工登录") public String testPost() throws Exception{ //创建httpclient对象 CloseableHttpClient httpClient = HttpClients.createDefault(); //创建请求对象 HttpPost httpPost = new HttpPost("http://localhost:8899/admin/employee/login"); JSONObject jsonObject = new JSONObject(); jsonObject.put("username","admin"); jsonObject.put("password","123456"); StringEntity entity = new StringEntity(jsonObject.toString()); //指定请求编码方式 entity.setContentEncoding("utf-8"); //数据格式 entity.setContentType("application/json"); httpPost.setEntity(entity); //发送请求 CloseableHttpResponse response = httpClient.execute(httpPost); //解析返回结果 int statusCode = response.getStatusLine().getStatusCode(); System.out.println("响应码为:" + statusCode); HttpEntity entity1 = response.getEntity(); String body = EntityUtils.toString(entity1); System.out.println("响应数据为:" + body); //关闭资源 response.close(); httpClient.close(); return body; } 测试结果 ...

    2023-11-09 220
  • Java面试题:如何用Zookeeper实现分布式锁?

    Zookeeper是一个分布式协调服务,可以用来实现分布式锁的功能。 分布式锁是一种控制多个分布式系统之间同步访问共享资源的机制。 Zookeeper实现分布式锁的原理如下:   首先,需要在 Zookeeper 中创建一个持久节点作为锁的根节点,例如 /lock。 然后,每个需要获取锁的客户端都在 /lock 节点下创建一个临时顺序节点,例如 /lock/seq-0000000001。这样可以利用 Zookeeper 的节点唯一性和顺序性特性。 接着,每个客户端都获取 /lock 节点下的所有子节点,并按照序号排序,判断自己创建的节点是否是最小的。如果是,说明获取到了锁,可以执行相关操作。 如果不是最小的,说明没有获取到锁,需要等待。此时,客户端可以监听自己前一个节点的变化(例如删除),一旦监听到事件发生,就重新判断自己是否是最小的节点。 最后,当客户端执行完操作后,需要释放锁,即删除自己创建的临时顺序节点。这样,后面等待的客户端就可以收到通知,继续尝试获取锁。 以上就是 Zookeeper 实现分布式锁的基本原理。 实现方式: 实际开发过程中,可以 curator 工具包封装的API帮助我们实现分布式锁。 <dependency> <groupId>org.apache.curator</groupId> <artifactId>curator-recipes</artifactId> </dependency> 1、客户端想要获取锁,就在 Zookeeper 上创建一个临时的、有序的节点,这个节点相当于一把锁。  2、客户端查看 Zookeeper 上所有的节点,按照顺序排列,看看自己创建的节点是不是最小的。  3、判断是否获得锁,如果是读操作,只要自己前面没有写操作的节点,就可以获取锁,然后开始执行读逻辑。如果是写操作,只有自己是最小的节点,才可以获取锁,然后开始执行写逻辑。  4、如果没有获取到锁,就要等待。如果是读操作,就监听自己前面最近的一个写操作的节点。如果是写操作,就监听自己前面最近的一个节点。一旦监听到这个节点被删除了,就重新判断是否可以获取锁。 Curator 的几种锁方案 : 1、InterProcessMutex:分布式可重入排它锁2、InterProcessSemaphoreMutex:分布式排它锁3、InterProcessReadWriteLock:分布式读写锁下面例子模拟 50 个线程使用重入排它锁 InterProcessMutex 同时争抢锁: 实例: public class InterprocessLock { public static void main(String[] args) { CuratorFramework zkClient = getZkClient(); String lockPath = "/lock"; //通过InterProcessMutex创建分布式锁 InterProcessMutex lock = new InterProcessMutex(zkClient, lockPath); //模拟50个线程抢锁 for (int i = 0; i < 50; i++) { new Thread(new TestThread(i, lock)).start(); } } static class TestThread implements Runnable { private Integer threadFlag; private InterProcessMutex lock; public TestThread(Integer threadFlag, InterProcessMutex lock) { this.threadFlag = threadFlag; this.lock = lock; } @Override public void run() { try { lock.acquire(); System.out.println("第"+threadFlag+"线程获取到了锁"); //等到1秒后释放锁 Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); }finally { try { lock.release(); } catch (Exception e) { e.printStackTrace(); } } } } private static CuratorFramework getZkClient() { String zkServerAddress = "127.0.0.1:2181"; ExponentialBackoffRetry retryPolicy = new ExponentialBackoffRetry(1000, 3, 5000); CuratorFramework zkClient = CuratorFrameworkFactory.builder() .connectString(zkServerAddress) .sessionTimeoutMs(5000) .connectionTimeoutMs(5000) .retryPolicy(retryPolicy) .build(); zkClient.start(); return zkClient; } } </div> ...

    2023-10-25 172
  • java面试题:为什么JDK 15要废弃偏向锁?

    为什么JDK 15要废弃偏向锁? 要想说清楚这个问题,你得先知道什么是偏向锁,它是在哪里使用的。 这就不得不提到Synchronized的锁升级过程了。 在JDK 1.6及之前的版本中,Synchronized关键字,它可以让一个对象只能被一个线程使用,这样就可以避免多个线程同时修改同一个对象造成的混乱。 但是,这种方式也有一个缺点,就是每次一个线程要使用一个对象时,都要先检查这个对象是否被别的线程占用了,如果是的话,就要等待别的线程用完才能继续。 这个过程会消耗很多时间和资源,影响程序的效率。 为了解决这个问题,Java在后来的1.7 之后对 Synchronized 的实现做了一些改进,让它可以根据不同的情况,采用不同的方式来控制对象的使用。 具体来说,有四种方式: 无锁状态:这是最简单的一种方式,就是没有任何限制,任何线程都可以随时使用对象。 偏向锁状态:这是一种优化的方式,就是假设一个对象只会被一个线程使用,所以当第一个线程使用对象时,就会把自己的标记放在对象上,表示这个对象属于自己。这样,下次这个线程再使用对象时,就不用检查了,直接就可以用。但是如果有别的线程也想用这个对象,就要先把标记去掉,然后再竞争。 轻量级锁状态:这是一种折中的方式,就是当有多个线程想用同一个对象时,不会立刻让它们等待,而是先让它们在自己的内存里复制一份对象的信息,然后各自修改。最后再比较一下谁修改得最快,谁就可以用对象。这样可以减少等待的时间,提高效率。 重量级锁状态:这是最原始的一种方式,就是当有多个线程想用同一个对象时,只能让其中一个线程用,其他的线程都要等待。这样可以保证对象的安全性,但是会降低效率。 在Java中,每个对象都有一个特殊的区域叫做mark word(标记字),它可以记录对象的一些信息。其中有两位或三位是用来表示对象现在处于哪种状态的。具体来说: 如果两位是“01”,表示无锁状态或偏向锁状态。如果第三位是“0”,表示无锁状态; 如果第三位是“1”,表示偏向锁状态。如果两位是“00”,表示轻量级锁状态。 如果两位是“10”,表示重量级锁状态。 但是,在Java 15中 ,偏向锁被取消了 ,所以现在只有三种状态:无锁状态、轻量级锁状态和重量级锁状态。 回到最开始的问题:为什么JDK 15要废弃偏向锁? 取消偏向锁状态的原因是,偏向锁状态在现代的应用场景下已经不再有明显的性能优势,反而会增加虚拟机的复杂度和维护成本。 具体的原因是: 1. 偏向锁状态是一种优化技术,用于减少无竞争情况下的同步开销。 它假设一个对象只会被一个线程使用,所以当第一个线程使用对象时,就会把自己的标记放在对象上,表示这个对象属于自己。 这样,下次这个线程再使用对象时,就不用检查了,直接就可以用。 但是如果有别的线程也想用这个对象,就要先把标记去掉,然后再竞争。 2. 偏向锁状态在过去能够带来很大的性能提升,是因为有些应用会使用很多不必要的同步操作。 比如早期的Java集合类(如Hashtable和Vector),它们每次访问都要同步。 现在的应用一般会使用非同步的集合类(如HashMap和ArrayList)或者更高效的并发数据结构(如ConcurrentHashMap、CopyOnWriteArrayList等)来处理单线程或多线程的情况。 这意味着如果更新代码使用这些新的类,就可以获得更好的性能,而不需要依赖偏向锁状态。 3. 偏向锁状态也有一个缺点,就是当有竞争发生时,需要进行一次代价很高的撤销操作。 所以只有当应用有大量的无竞争的同步操作时,偏向锁状态才有优势。 但是随着硬件和软件的发展,原子操作的代价已经降低了很多,而且现在的应用往往使用线程池和工作队列来处理并发任务,这些场景下偏向锁状态反而会降低性能。 4. 偏向锁状态还给虚拟机带来了很多复杂度和维护难度,只有少数几个最有经验的工程师能够理解和修改它。它也影响了一些新特性的设计和实现。 综上所述,偏向锁状态已经不再适合现代的Java应用,所以在Java 15 之后被废弃。 ...

    2023-10-25 195
  • Java面试题:不使用锁如何实现线程安全的单例?

    面试官问: 不使用锁,如何实现线程安全的单例? 如果不能使用synchronized和lock的话,想要实现单例可以通过饿汉模式、枚举、以及静态内部类的方式实现。 饿汉: 其实都是通过定义静态的成员变量,以保证instance可以在类初始化的时候被实例化。 // 单例模式 // 饿汉式(静态变量) class Singleton { // 1. 构造器私有化 private Singleton() {} // 2. 本类内部创建对象实例 private final static Singleton instance = new Singleton(); // 静态变量 // 3. 提供一个公有的静态方法,返回实例对象 public static Singleton getInstance() { return instance; } } 但是,如果从始至终未使用过这个实例,会造成内存浪费 静态内部类: 这种方式和饿汉方式只有细微差别,只是做法上稍微优雅一点。 // 静态内部类实现 class Singleton { // 构造器私有化 private Singleton() {} // 写一个静态内部类,含一个静态属性Singleton private static class SingletonHolder { private static final Singleton INSTANCE = new Singleton(); } // 提供一个静态的公有方法,直接返回SingleInstance.INSTANCE public static Singleton getInstance() { return SingletonHolder.INSTANCE; } } 原理和饿汉一样。这种方式是Singleton类被装载了,INSTANCE 对象不一定被初始化。 因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance 枚举: 借助了 JDK1.5 中添加的枚举来实现单例模式,不仅避免了多线程同步问题,而且还防止反序列化重新创建新的对象 // 枚举实现单例 enum Singleton { INSTANCE; // 属性 } 其实,如果把枚举类进行反编译,你会发现他也是使用了static final来修饰每一个枚举项。 final class Singleton extends Enum { public static final Singleton INSTANCE; private static final Singleton $VALUES[]; public static Singleton[] values() { return (Singleton[])$VALUES.clone(); } public static Singleton valueOf(String name) { return (Singleton)Enum.valueOf(cn/itsource/logweb/utils/Singleton, name); } private Singleton(String s, int i) { super(s, i); } static { INSTANCE = new Singleton("INSTANCE", 0); $VALUES = (new Singleton[] { INSTANCE }); } } 其实,上面三种方式,都是依赖静态数据在类初始化的过程中被实例化这一机制的。 但是,如果真要较真的话,ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。 也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的(线程安全的)。 那么,除了上面这三种,还有一种无锁的实现方式,那就是CAS。 public class Singleton { // 使用了 AtomicReference 封装单例对象 private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); private Singleton() {} public static Singleton getInstance() { while (true) { // 使用 AtomicReference.get 获取 Singleton singleton = INSTANCE.get(); if (null != singleton) { return singleton; } // 使用 CAS 乐观锁进行非阻塞更新 singleton = new Singleton(); if (INSTANCE.compareAndSet(null, singleton)) { return singleton; } } } } 用CAS的好处在于不需要使用传统的锁机制来保证线程安全。  但是我们的实现方式中,用了一个while循环一直在进行重试,所以,这种方式有一个比较大的缺点在于,如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。 ...

    2023-10-25 179
  • Java快速排序

    排序算法主要分为10大类,他们的优缺点主要体现在时间复杂度和空间复杂度上,还有代码的难易程度上 这里我主要是对快速排序做一个再理解,因为在学习快速排序的时候遇到了一些问题,这些问题真的是很容易让人掉头发 快速排序的基本实现逻辑 在待排序数组中找一个基值,将比这个基值小的值移动到基值左边,将比这个基值大的值移动到基值右边,然后再将基值左右两边的数据重复次操作(递归),最后就能得到有序的一组数据 虽然听起来比较简单,但是我在理解的时候还是花费了一些时间 举一个例子 这是一个初始的数组:-10,20,15,1,0,30,-78,62,50 假设我们就取中间值0作为基值那么经过一次排序之后:-10,-78,0,1,20,15,30,62,50 (大概就是这种) 然后再将基值左边的数{-10,-78}进行操作:-78,-10 然后再将基值右边的数{1,20,15,30,62,50}进行操作:1, 15, 20, 30, 62, 50 ... 最后得到:-78, -10, 0, 1, 15, 20, 30, 50, 62 这里面需要注意的点,我就是在这里被坑了,这里的基值不一定是中间值,你随便在数组中找一个来当做基值都可以,只不过找中间值的话可能更好理解(我并不觉得) 代码实现 package com.quiteStore; import java.util.Arrays; public class QuiteStore { public static void main(String[] args) { int[] array = new int[]{-10,20,15,1,0,30,-78,62,50}; QuikeSote.sote(array,0,array.length-1); } } //找到一个中间值(比较值)比较中间值两边值的大小将小的值放在左边,将大的值放在右边,一次进行这样的操作 class QuikeSote{ public static void sote(int[] array,int left,int right){ //找到中间值(基值) int medianIndex = (left + right) / 2; int median = array[medianIndex]; int temp = 0; //比较两边值的大小,从左边找到一个大的值,右边找到一个小的值,将这两个值交换, //特殊情况:左边的值都是小的,右边的值都是大的,此时就找完一轮 while(left < right){ while(array[left]<median && left <= right){ left++; } //出循环说明找到了>=median的值 while(array[right]>median && left <= right){ right--; } //出循环说明找到了<=median的值 //如果左下表和右下标相遇说明这一轮已经找完,那么就将基值和左下标或右下标对应的值进行交换,因为有一种特殊情况会使得基值发生改变 //如果没有特殊情况交换之后也没事,因为左右下标所对应的值就是基值 if(left>=right){ temp = array[left]; array[left] = median; median = temp; break; } //交换两个值 temp = array[left]; array[left] = array[right]; array[right] = temp; //将右下标前移一位,因为右下标对应的值就是median,没必要进行再次比较,可能出现死循环 if(array[left] == median){ right--; } //将右下标前移一位,因为左下标对应的值就是median,没必要进行再次比较,可能出现死循环 if(array[right] == median){ left++; } System.out.println(Arrays.toString(array)); //左递归 QuikeSote.sote(array,0,left-1); //右递归 QuikeSote.sote(array,right+1, array.length-1); } } 从执行图中也可以看出 最后哪一行就是最终结果 ...

    2022-02-14 581
  • Java 为什么不采用 360 垃圾清理来进行垃圾回收呢?

    作为早期 Java 的开发者之一,我们团队当初确实尝试过使用 360 垃圾清理来对 Java 进行垃圾回收。 早些年,我们曾发布了使用 360 垃圾回收的 Java 试用版本,部分用户使用了这个版本之后,又成功地回到单身贵族家庭。为了对他们的付出表示感谢,我们邀请了这批用户来参观我们的 Java 炸鱼实验室。由于省去不必要的恋爱、婚姻花销,这批用户有更多的时间加班了。 我们当时收到了几百家公司的负责人的感谢信,他们在向我们表示感谢时说到,他们的员工现在有更多的时间专心工作了,当然公司的工时制度也进行了及时的跟进与创新,其中比较有名的几种工时制度,有大小周、超级大小周、996、007,这些新的工时制度真是人类工作史上的伟大创新。其中一位华氏集团的负责人的欣喜地告诉我们的商务,现在他们的员工们都斗志昂扬,积极为公司添砖加瓦、发光发热,原来好几天的工作任务,现在一两天就能完成了,因此公司短期内也取得了巨大的营收。同时,求职市场上,从这类公司离职的员工和管理层也特别受欢迎:一位叫张小方的普通员工,由于这段工作经历,让刚毕业两年的他具有三年多的工作经验,薪资也因此上涨了 0.5 倍;另一位某黄姓高管,受到前公司的工时制度的启发,回国后创立榨斗斗公司,全公司开启超级硬核奋斗模式,三年里成功将公司做到上市。 然而,另一方面,国际健康组织、国际卫生组织、国际人权组织等国际组织对我们在 Java 中使用 360 的垃圾回收这一做法表示遗憾。一位来自中国的周大树曾公开地发表了一篇谴责性文章,文章谴责了这些开启硬核奋斗模式的公司,同时谴责我们是始作俑者。他发表在《劳动者之歌》上的文章将这些试用了带 360 垃圾回收功能的员工比作野草,他说:野草,根本不深,花叶不美,然而吸取露,吸取水,吸取陈死人的血和肉,当它生存时,还要遭删刈,还要遭践踏。 最终事态发展到我们无法控制的程度了,这并不是我们 Java 开发团队所期望的。由于 360 垃圾回收具有庞大的用户群体,我们在 Java 直接采用,虽然可以降低部分 Java 新用户的学习成本,但这一事件影响范围也比较广泛。那么为啥在 Java 中使用 360 的垃圾回收会导致单身员工数量大幅度地增多呢?我们起初对这一现象也觉得不可思议,后来我们成立了专门的调查组,感谢调查组同事的辛勤工作,最终我们找到了真相: 众所周知,360 垃圾清理,顾名思义,一年中只能工作 360 天,有 5 天是不能工作的,而我们都知道 Java 虚拟机每天都会产生垃圾,这显然是不能满足要求的。Java 一般都用于大型服务器后端服务开发,其访问量都是非常巨大的,如果一年 5 天不能工作,那么可靠性只能达到 98.9%,这是远远不够的。而且不算闰年,闰年不能工作的时间将由 5 天变成 6 天,这对于使用 Java 作为开发语言的互联网公司是无法接受的。 大多数年轻人,在大学毕业之后就离开了家乡,去城市打拼,然后认识了另外一半。用他们当中比较流行的一句话来解释一下这种现象——“故乡再无春夏秋”,因此他们一般只有在冬天,也就是年底的最后几天才有时间回一趟家乡,于是和丈母娘见面、谈婚论嫁的日子理所当然地被安排在这几天。 试想,你和你的女友约好年底最后几天去见丈母娘,并在见面后将彩礼转到丈母娘的卡上。 但是由于年底的最后几天支付宝和相关的银行服务器采用了 Java,而 Java 采用了 360 进行垃圾回收,最终由于你无法完成彩礼转账,导致丈母娘拒绝将女儿嫁给你。 你最终: 老婆 -1 这是大多数人无法接受的,由于不少未婚青年都是在年底回去和丈母娘谈判的,所以最终: 未婚青年们: 老婆s - 10086 这样社会上光棍就多了,社会上光棍多了就不利于社会的稳定,所以最终有关部门会介入了调查。调查后最终定位到问题的原因是使用 360 垃圾回收的 Java 无法在年底的 5 天或者 6 天工作。 当然,我们也尝试和 360 团队进行沟通,360 团队拒绝为一年剩下的五天提供服务,他们解释说,这样会导致他们的同事需要加班,他们不希望社会上已经形成的 996、007 等不良风气蔓延到他们公司。 因此,我们最终没有在 Java 中使用 360 的垃圾回收,并忍痛下掉了使用 360 垃圾回收的 Java 版本。 Java 团队和 360 团队都是伟大的团队,他们在垃圾回收中做的工作不分伯仲,都值得我们尊敬。 ...

    2021-04-21 556
  • Java项目中存在大量判空代码,怎么解决?

    问题 为了避免空指针调用,我们经常会看到这样的语句 if (someobject != null) {     someobject.doCalc(); } 最终,项目中会存在大量判空代码,多么丑陋繁冗!如何避免这种情况?我们是否滥用了判空呢? 回答 这是初、中级程序猿经常会遇到的问题。他们总喜欢在方法中返回null,因此,在调用这些方法时,也不得不去判空。另外,也许受此习惯影响,他们总潜意识地认为,所有的返回都是不可信任的,为了保护自己程序,就加了大量的判空。 吐槽完毕,回到这个题目本身,进行判空前,请区分以下两种情况: null 是一个有效有意义的返回值(Where null is a valid response in terms of the contract; and) null是无效有误的(Where it isn’t a valid response.) 你可能还不明白这两句话的意思,不急,继续往下看,接下来将详细讨论这两种情况 先说第2种情况 null就是一个不合理的参数,就应该明确地中断程序,往外抛错误。这种情况常见于api方法。例如你开发了一个接口,id是一个必选的参数,如果调用方没传这个参数给你,当然不行。你要感知到这个情况,告诉调用方“嘿,哥们,你传个null给我做甚"。 相对于判空语句,更好的检查方式有两个 assert语句,你可以把错误原因放到assert的参数中,这样不仅能保护你的程序不往下走,而且还能把错误原因返回给调用方,岂不是一举两得。(原文介绍了assert的使用,这里省略) 也可以直接抛出空指针异常。上面说了,此时null是个不合理的参数,有问题就是有问题,就应该大大方方往外抛。 第1种情况会更复杂一些。 这种情况下,null是个”看上去“合理的值,例如,我查询数据库,某个查询条件下,就是没有对应值,此时null算是表达了“空”的概念。 这里给一些实践建议: 假如方法的返回类型是collections,当返回结果是空时,你可以返回一个空的collections(empty list),而不要返回null.这样调用侧就能大胆地处理这个返回,例如调用侧拿到返回后,可以直接print list.size(),又无需担心空指针问题。(什么?想调用这个方法时,不记得之前实现该方法有没按照这个原则?所以说,代码习惯很重要!如果你养成习惯,都是这样写代码(返回空collections而不返回null),你调用自己写的方法时,就能大胆地忽略判空) 返回类型不是collections,又怎么办呢? 那就返回一个空对象(而非null对象),下面举个“栗子”,假设有如下代码 public interface Action {   void doSomething();} public interface Parser {   Action findAction(String userInput);} 其中,Parse有一个接口FindAction,这个接口会依据用户的输入,找到并执行对应的动作。假如用户输入不对,可能就找不到对应的动作(Action),因此findAction就会返回null,接下来action调用doSomething方法时,就会出现空指针。 解决这个问题的一个方式,就是使用Null Object pattern(空对象模式) 我们来改造一下 类定义如下,这样定义findAction方法后,确保无论用户输入什么,都不会返回null对象: public class MyParser implements Parser {   private static Action DO_NOTHING = new Action() {     public void doSomething() { /* do nothing */ }   };   public Action findAction(String userInput) {     // ...     if ( /* we can't find any actions */ ) {       return DO_NOTHING;     }   } } 对比下面两份调用实例 1.冗余: 每获取一个对象,就判一次空 Parser parser = ParserFactory.getParser(); if (parser == null) {   // now what?   // this would be an example of where null isn't (or shouldn't be) a valid response } Action action = parser.findAction(someInput); if (action == null) {   // do nothing}  else {   action.doSomething(); } 2.精简 ParserFactory.getParser().findAction(someInput).doSomething(); 因为无论什么情况,都不会返回空对象,因此通过findAction拿到action后,可以放心地调用action的方法。扩展一下:Java:如何更优雅的处理空值? 其他回答精选: 如果要用equal方法,请用object<不可能为空>.equal(object<可能为空>)) 例如: 使用 "bar".equals(foo) 而不是 foo.equals("bar") Java8或者guava lib中,提供了Optional类,这是一个元素容器,通过它来封装对象,可以减少判空。不过代码量还是不少。不爽。 如果你想返回null,请停下来想一想,这个地方是否更应该抛出一个异常。 ...

    2021-04-17 567
  • JAVA常见异常类型整理

    算数异常类:ArithmeticExecption   空指针异常类型:NullPointerException   类型强制转换类型:ClassCastException   数组负下标异常:NegativeArrayException   数组下标越界异常:ArrayIndexOutOfBoundsException   违背安全原则异常:SecturityException   文件已结束异常:EOFException   文件未找到异常:FileNotFoundException   字符串转换为数字异常:NumberFormatException   操作数据库异常:SQLException   输入输出异常:IOException   方法未找到异常:NoSuchMethodException   下标越界异常:IndexOutOfBoundsExecption   系统异常:SystemException   创建一个大小为负数的数组错误异常:NegativeArraySizeException   数据格式异常:NumberFormatException   安全异常:SecurityException   不支持的操作异常:UnsupportedOperationException ...

    2020-10-22 768

联系我们

在线咨询:点击这里给我发消息

QQ交流群:KirinBlog

工作日:8:00-23:00,节假日休息

扫码关注