Java 小记 — Spring Boot 的实践与思考,互联网 面试官 如何面试


写在最前面,我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家。扫码加微信好友进【程序员面试学习交流群】,免费领取。也欢迎各位一起在群里探讨技术。

前言

本篇随笔用于记录我在学习 Java 和构建 Spring Boot 项目过程中的一些思考,包含架构、组件和部署方式等。下文仅为概要,待闲时逐一整理为详细文档。


1. 组件

开源社区如火如荼,若在当下我们还要去重复 “造轮子” 那真是罪过罪过(当然也并不意味着所有的一切都可拿来即用,了解他,使用他,若有能力,去完善它)。因此,当我们拿到需求的时候首先应当进行拆解,哪些模块在社区中已有比较成熟的解决方案,然后大致罗列一个粗略的所需组件列表(后续根据架构的设计和兼容情况再进行调整)。

1.1 ORM

用于解耦实体对象的装载过程,他让我们的编程过程更关注业务逻辑本身,其重要性毋庸多言。在 Spring Boot 中比较主流的 ORM 框架有 Spring-Data-JPA 和 MyBatis。

JPA 规范的好处是我们几乎完全专注于业务逻辑本身,JpaRepository 中的接口能够满足大部分简单的操作逻辑了,若要扩展,我们也可以再抽离出一个父类,添加自定义的通用 CRUD 操作,如此一来仓储层的代码变得异常优雅和简洁。以下是一个简单的实现案例:


application.yml:

spring:

  datasource:

    driver-class-name: com.mysql.jdbc.Driver

    url: jdbc:mysql://mysql:3306/youclk?characterEncoding=utf8&useSSL=false

    username: root

    password: youclk

  jpa:

    hibernate:

      ddl-auto: create

    show-sql: true

BookRespository.java:


public interface BookRespository extends JpaRepository<Book,Integer> {

}

Book.java:


@Data

public class Book  {

    private Long id;

    private String name;

    private Date createTime;

}

BookRespositoryTest.java:


@RunWith(SpringRunner.class)

@SpringBootTest

public class BookRespositoryTest {

    @Autowired

    private BookRespository repository;

    @Test

    public void saveTest() {

        Assert.assertEquals(repository.findAll().size(),0);

    }

}

映射得到的 sql 如下:

Hibernate: select book0_.id as id1_0_, book0_.name as name2_0_, book0_.number as number3_0_ from book book0_

如果是几年前的话我肯定会首选 JPA, 但是期间近两年的 EFCore 开发经历让我的选择变得谨慎。C# 是 Lambda 和 Linq 的先驱者,因此 .NET + EF 实践 Code First 着实优雅。然而在迁移 EFCore 的过程中遇到的问题真是不少,比如说 EFCore 1.x 的时候处理 GroupBy 是全表扫描然后拿到内存中过滤。对于旧项目的迁移我们一般没有精力去验证 ORM 映射生成的每条 SQL 语句,而且本地环境因数据基数少,测试阶段很难直观地体现出来,但部署后就悲剧了,服务和数据库一起都要死要死的。

由此引发的思考是当进行里程碑版本的升级和迁移的时候,新版本 ORM 框架所生成的 SQL 还能否完全正确体现之前代码中的逻辑。

在 .NET Core 中除了 EFCore 还有一个非常优秀的 ORM 框架是 Dapper,这个和 MyBatis 非常像,相当于半自动档吧,开发者能更好地掌控 SQL,但牺牲了一定的简洁。介于曾经的入坑经历,至少在查询方面我选择 MyBatis,实例如下:

BookRespository.java:


public interface BookRepository {

    List<Book> findAll();

    @Select("SELECT name FROM book WHERE id = #{id}")

    @Results({

            @Result(property = "name", column = "name")

    })

    String findBookName(Long id);

}

BookRepository.xml:

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >

<mapper namespace="com.youclk.demo.repositories.BookRepository" >

    <resultMap id="BaseResultMap" type="com.youclk.demo.domain.Book" >

        <id column="id" property="id" jdbcType="BIGINT" />

        <result column="name" property="name" jdbcType="VARCHAR" />

        <result column="createTime" property="createTime" jdbcType="DATE" />

    </resultMap>

    <sql id="Base_Column_List" >

        id, name, createTime

    </sql>

    <select id="findAll" resultMap="BaseResultMap"  >

        SELECT

        <include refid="Base_Column_List" />

        FROM book

    </select>

</mapper>

Application.java 中加一行 MapperScan 注解:


@SpringBootApplication()

@MapperScan("com.youclk.demo.repositories")

public class DemoApplication {

    public static void main(String[] args) {

        SpringApplication.run(DemoApplication.class, args);

    }

}

以上 BookRepository.java 中分别使用了注解和 xml,虽然在 Spring 中注解是王道,但我还是喜欢 xml 这种方式。一方面接口上写这么一堆 SQL 和返回类型看起来很难受,头重脚轻,而且没有突出重点,另一方面在于 IDEA 中对 xml 的智能提示相当友好,效率上也不见得是写注解快多少。不过采用注解方式少了些配置文件,项目结构更优。

1.2 日志

Java 中主流的日志框架有 JUL(java.util.logging)、Log4j、Log4j2 和 Logback 这四款,JUL 因过于简陋优先淘汰,剩下的三款都是同一个作者开发,Log4j 太旧速度慢,Log4j2 太新问题多,因此 Logback 就是最优解,对应的接口门面我选择 SLF4j。需要导入的包有: slf4j-api、ogback-classic 和 logback-core,以下是我的案例:

logback-spring.xml:

<configuration>

    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

        <encoder>

            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{35} - %msg%n</pattern>

        </encoder>

    </appender>

    <appender name="FILE_WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">

        <filter class="ch.qos.logback.classic.filter.LevelFilter">

            <level>WARN</level>

            <onMatch>ACCEPT</onMatch>

            <onMismatch>DENY</onMismatch>

        </filter>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

            <fileNamePattern>log/com.youclk.demo/warm/%d{yyyy-MM-dd}.log</fileNamePattern>

            <maxHistory>90</maxHistory>

        </rollingPolicy>

        <encoder>

            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{35} - %msg%n</pattern>

        </encoder>

    </appender>

    <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">

        <filter class="ch.qos.logback.classic.filter.LevelFilter">

            <level>ERROR</level>

            <onMatch>ACCEPT</onMatch>

            <onMismatch>DENY</onMismatch>

        </filter>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

            <fileNamePattern>log/com.youclk.demo/error/%d{yyyy-MM-dd}.log</fileNamePattern>

            <maxHistory>90</maxHistory>

        </rollingPolicy>

        <encoder>

            <pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{35} - %msg%n</pattern>

        </encoder>

    </appender>

    <root level="warn">

        <appender-ref ref="STDOUT"/>

        <appender-ref ref="FILE_WARN"/>

        <appender-ref ref="FILE_ERROR"/>

    </root>

</configuration>

LoggerTest.java:


@RunWith(SpringRunner.class)

@SpringBootTest

@Slf4j

public class LoggerTest {

    @Test

    public void testOutput() {

        log.error("error");

        log.warn("warn");

        log.info("info");

    }

}

以上将日志的输出整理归类,若要分布式部署,则只要每个 Container 都挂载 log 目录便可做日志的集中管理。Logback 更详细信息可查阅 “官方文档”

1.3 缓存

Memcached 和 Redis 都老生常谈了,Redis 支持更多的数据结构和操作,并且二者性能差距不大,因此选他无疑,实现上也极其简单,如下:


@RunWith(SpringRunner.class)

@SpringBootTest

public class RedisTest {

    @Autowired

    private RedisTemplate redisTemplate;

    @Test

    public void testSetCache() {

        ValueOperations<String, Book> operations = redisTemplate.opsForValue();

        operations.set("my-book",new Book(),5, TimeUnit.SECONDS);

        System.out.println(operations.get("my-book"));

    }

}

另外还有一种注解的用法功能上要强大不少:

@Service

@CacheConfig(cacheNames="book")

public class BookService {

    @Autowired

    private BookRepository bookRepository;

    @Cacheable()

    public List<Book> findAll() {

        return bookRepository.findAll();

    }

}

2. 架构

程序员界一直存在着一条所谓的 “语言鄙视链”,曾经为了 “打嘴炮” 而粗略地对比过 Java 和 C#,由于未深入探究,因此我一直以来的观念都是 C# 的语法糖比 Java 优雅太多。直到我切身感受了使用 Java 构建项目,或许就原生的二者来说确实是 C# 更优雅,但加上社区的力量可就不好说了。比如习惯了 C# 自动属性的我最不喜欢的就是 Java 那么一堆冗长的 get 和 set, 直到我认识了 lombok,简直汗颜啊,源码注解原来还能这么灵活地使用,由此展开只要你足够有耐心,想要什么语法糖自定义注解去实现就好。还有其他的零零总总, Java 中注解和 AOP 范式的成熟应用拓展了更多的 “编程姿势” 呵(C# 中通过反射也能做到,奈何封闭的生态导致为其造轮子的不多,现今微软已拥抱开源,给个祝福吧,互相竞争才更有意思嘛)。

回归本节主题,在 .NET 中 DDD 架构的应用可谓是相当普及,你要是不晓得领域、充血模型、事件驱动等概念都不好意思说熟悉 .NET。

这里简要概括一下,顺便谈谈我的想法,传统 DDD 架构主要分四层,分别为:User Interface(用户界面层)、Application(应用层)、Domain(领域层) 和 Infrastructure(基础设施层)。界面层就不说了,应用层主要起协调作用,比如一个请求从用户界面层过来,应用层应当分析其需要哪几个领域模块参与,并协调他们工作,但其本身不应包含任何的业务规则,基础设施层在实际应用中最重要的功能就是提供数据持久化机制。领域层则为整个项目的核心,其应囊括几乎全部的业务规则,我们应当在该层根据项目需求设计领域模型,抽离出领域服务,每个领域模块应当专注于处理其自身的核心业务逻辑,非核心的业务可封装为领域事件交由异步队列处理。其次,领域层作为核心,他不应该对其他层有所依赖,因此一般他会包含基础设施层的实现接口。

之前对于领域模块中的通用逻辑或非核心业务,我通常的处理方案是封装为领域事件分发,现在想想如此做法不合理之处,领域事件有些被滥用了。介于 AOP 在 Spring Boot 的广泛应用,领域模型中除了领域实体、值对象、领域服务、领域事件和工作单元之外再加一个领域切面也是极好的。另外,对于领域实体最后的持久化操作如果使用 MyBatis 此类的 ORM 框架那整个编程过程就变得相当繁琐,在领域中比较容易做到的是对实体状态的跟踪,因此持久化选择 JPA 规范的 ORM 框架才更为合理,但在查询上我更喜欢 MyBatis,因此若要做读写分离的话, JPA 和 MyBatis 分别对应主备数据库操作正好。

3. 部署

自从习惯了 Docker 之后,我已经不适应服务的单独部署了,具体操作详见我的这篇博文:“Compose & Swarm”

结语

近期正在寻觅 “全栈” 或 “Java 开发” 的工作岗位,若有意向,欢迎留言,微信: youclk。


我的公众号《有刻》,我们共同成长!



转载:https://www.cnblogs.com/youclk/p/8566805.html

推荐内容:
十大经典排序算法最强总结(含JAVA代码实现)
为什么说 Java 程序员到了必须掌握 Spring Boot 的时候?
Java 面试知识点解析(二)——高并发编程篇
java实现 redis的发布订阅(简单易懂)
Java--面试通关要点
Java高级工程师面试题总结及参考答案
Java面试总结
java 面试收集
知名互联网公司校招 Java 开发岗面试知识点解析
知名互联网公司校招 Java 开发岗面试知识点解析

猜你喜欢

转载自blog.csdn.net/agoodcoder777/article/details/89417638
今日推荐