24个写出漂亮代码的小技巧

这篇文章我会总结一些实用的有助于提高代码质量的建议,内容较多,建议收藏!

内容概览:

提取通用处理逻辑

注解、反射和动态代理是 Java 语言中的利器,使用得当的话,可以大大简化代码编写,并提高代码的可读性、可维护性和可扩展性。

我们可以利用 注解 + 反射注解+动态代理 来提取类、类属性或者类方法通用处理逻辑,进而避免重复的代码。虽然可能会带来一些性能损耗,但与其带来的好处相比还是非常值得的。

通过 注解 + 反射 这种方式,可以在运行时动态地获取类的信息、属性和方法,并对它们进行通用处理。比如说在通过 Spring Boot 中通过注解验证接口输入的数据就是这个思想的运用,我们通过注解来标记需要验证的参数,然后通过反射获取属性的值,并进行相应的验证。

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PersonRequest {

    @NotNull(message = "classId 不能为空")
    private String classId;

    @Size(max = 33)
    @NotNull(message = "name 不能为空")
    private String name;

    @Pattern(regexp = "(^Man$|^Woman$|^UGM$)", message = "sex 值不在可选范围")
    @NotNull(message = "sex 不能为空")
    private String sex;

    @Region
    private String region;

    @PhoneNumber(message = "phoneNumber 格式不正确")
    @NotNull(message = "phoneNumber 不能为空")
    private String phoneNumber;

}

相关阅读:一坨一坨的 if/else 参数校验,终于被 SpringBoot 参数校验组件整干净了! 。

通过 注解 + 动态代理 这种方式,可以在运行时生成代理对象,从而实现通用处理逻辑。比如说 Spring 框架中,AOP 模块正是利用了这种思想,通过在目标类或方法上添加注解,动态生成代理类,并在代理类中加入相应的通用处理逻辑,比如事务管理、日志记录、缓存处理等。同时,Spring 也提供了两种代理实现方式,即基于 JDK 动态代理和基于 CGLIB 动态代理(JDK 动态代理底层基于反射,CGLIB 动态代理底层基于字节码生成),用户可以根据具体需求选择不同的实现方式。

@LogRecord(content = "修改了订单的配送地址:从“#oldAddress”, 修改到“#request.address”",
        bizNo="#request.deliveryOrderNo")
public void modifyAddress(updateDeliveryRequest request){
    // 查询出原来的地址是什么
    LogRecordContext.putVariable("oldAddress", DeliveryService.queryOldAddress(request.getDeliveryOrderNo()));
    // 更新派送信息 电话,收件人、地址
    doUpdate(request);
}

相关阅读:美团技术团队:如何优雅地记录操作日志? 。

避免炫技式单行代码

代码没必要一味追求“短”,是否易于阅读和维护也非常重要。像炫技式的单行代码就非常难以理解、排查和修改起来都比较麻烦且耗时。

反例:

if (response.getData() != null && CollectionUtils.isNotEmpty(response.getData().getShoppingCartDTOList())) {
      cartList = response.getData().getShoppingCartDTOList().stream().map(CartResponseBuilderV2::buildCartList).collect(Collectors.toList());
}

正例:

T data = response.getData();
if (data != null && CollectionUtils.isNotEmpty(data.getShoppingCartDTOList())) {
  cartList = StreamUtil.map(data.getShoppingCartDTOList(), CartResponseBuilderV2::buildCartList);
}

相关阅读:一个较重的代码坏味:“炫技式”的单行代码 。

基于接口编程提高扩展性

基于接口而非实现编程是一种常用的编程范式,也是一种非常好的编程习惯,一定要牢记于心!

基于接口编程可以让代码更加灵活、更易扩展和维护,因为接口可以为不同的实现提供相同的方法签名(方法的名称、参数类型和顺序以及返回值类型)和契约(接口中定义的方法的行为和约束,即方法应该完成的功能和要求),这使得实现类可以相互替换,而不必改变代码的其它部分。另外,基于接口编程还可以帮助我们避免过度依赖具体实现类,降低代码的耦合性,提高代码的可测试性和可重用性。

就比如说在编写短信服务、邮箱服务、存储服务等常用第三方服务的代码时,我们可以先先定义一个接口,接口中抽象出具体的方法,然后实现类再去实现这个接口。

public interface SmsSender {
    SmsResult send(String phone, String content);
    SmsResult sendWithTemplate(String phone, String templateId, String[] params);
}

/*
 * 阿里云短信服务
 */
public class AliyunSmsSender implements SmsSender {
  ...
}

/*
 * 腾讯云短信服务
 */
public class TencentSmsSender implements SmsSender {
  ...
}

拿短信服务这个例子来说,如果需要新增一个百度云短信服务,直接实现 SmsSender 即可。如果想要替换项目中使用的短信服务也比较简单,修改的代码非常少,甚至说可以直接通过修改配置无需改动代码就能轻松更改短信服务。

操作数据库、缓存、中间件的代码单独抽取一个类

尽量不要将操作数据库、缓存、中间件的代码和业务处理代码混合在一起,而是要单独抽取一个类或者封装一个接口,这样代码更清晰易懂,更容易维护,一些通用逻辑也方便统一维护。

数据库:

public interface UserRepository extends JpaRepository<User, Long> {
  ...
}

缓存:

@Repository
public class UserRedis {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    public User save(User user) {
    }
}

消息队列:

// 取消订单消息生产者
public class CancelOrderProducer{
 ...
}
// 取消订单消息消费者
public class CancelOrderConsumer{
 ...
}

不要把业务代码放在 Controller 中

这个是老生常谈了,最基本的规范。一定不要把业务代码应该放在 Controller 中,业务代码就是要交给 Service 处理。

业务代码放到 Service 的好处

  1. 避免 Controller 的代码过于臃肿,进而难以维护和扩展。
  2. 抽象业务处理逻辑,方便复用比如给用户增加积分的操作可能会有其他的 Service 用到。
  3. 避免一些小问题比如 Controller 层通过 @Value注入值会失败。
  4. 更好的进行单元测试。如果将业务代码放在 Controller 中,会增加测试难度和不确定性。

错误案例:

@RestController
public class UserController {
    @Autowired
    private UserRepository userRepository;

    @GetMapping("/users/{id}")
    public Result<UserVO> getUser(@RequestParam(name = "userId", required = true) Long userId) {
        User user = repository.findById(id)
                  .orElseThrow(() -> new UserNotFoundException(id));
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);//演示使用
        // 可能还有其他业务操作
        ...
        return Result.success(userVO);
    }
    ...
}

静态函数放入工具类

静态函数/方法不属于某个特定的对象,而是属于这个类。调用静态函数无需创建对象,直接通过类名即可调用。

静态函数最适合放在工具类中定义,比如文件操作、格式转换、网络请求等。

/**
 * 文件工具类
 */
public class FileUtil extends PathUtil {

    /**
     * 文件是否为空<br>
     * 目录:里面没有文件时为空 文件:文件大小为0时为空
     *
     * @param file 文件
     * @return 是否为空,当提供非目录时,返回false
     */
    public static boolean isEmpty(File file) {
        // 文件为空或者文件不存在直接返回 true
        if (null == file || false == file.exists()) {
            return true;
        }
        if (file.isDirectory()) {
            // 文件是文件夹的情况
            String[] subFiles = file.list();
            return ArrayUtil.isEmpty(subFiles);
        } else if (file.isFile()) {
            // 文件不是文件夹的情况
            return file.length() <= 0;
        }

        return false;
    }
}

善用现有的工具类库

Java 的一大优势就是生态特别好, 包含了许多好用的工具类库和框架,几乎覆盖了所有的需求场景。很多事情我们完全不需要自己从头开始做,利用现有的稳定可靠的工具类库可以大大提高开发效率。

比如 Excel 文档处理,你可以考虑下面这几个开源的工具类库:

  • easyexcel :快速、简单避免 OOM 的 Java 处理 Excel 工具。
  • excel-streaming-reader:Excel 流式代码风格读取工具(只支持读取 XLSX 文件),基于 Apache POI 封装,同时保留标准 POI API 的语法。
  • myexcel:一个集导入、导出、加密 Excel 等多项功能的工具包。

再比如 PDF 文档处理:

  • pdfbox :用于处理 PDF 文档的开放源码 Java 工具。该项目允许创建新的 PDF 文档、对现有文档进行操作以及从文档中提取内容。PDFBox 还包括几个命令行实用程序。PDFBox 是在 Apache 2.0 版许可下发布的。
  • OpenPDF:OpenPDF 是一个免费的 Java 库,用于使用 LGPL 和 MPL 开源许可创建和编辑 PDF 文件。OpenPDF 基于 iText 的一个分支。
  • itext7:iText 7 代表了想要利用利用好 PDF 的开发人员的更高级别的 sdk。iText 7 配备了更好的文档引擎、高级和低级编程功能以及创建、编辑和增强 PDF 文档的能力,几乎对每个工作流都有好处。
  • FOP :Apache FOP 项目的主要的输出目标是 PDF。

我的网站上总结了 Java 开发常用的一些工具类库,可以作为参考:http://javaguide.cn/open-source-project/tool-library.html 。

善用设计模式

实际开发项目的过程中,我们应该合理地使用现有的设计模式来优化我们的代码。不过,切忌为了使用设计模式而使用。

新来了个同事,设计模式用的是真优雅呀!这篇文章中介绍了 9 种在源码中非常常见的设计模式:

  1. 工厂模式(Factory Pattern) :通过定义一个工厂方法来创建对象,从而将对象的创建和使用解耦,实现了“开闭原则”。
  2. 建造者模式(Builder Pattern) :通过链式调用和流式接口的方式,创建一个复杂对象,而不需要直接调用它的构造函数。
  3. 单例模式(Singleton Pattern) :确保一个类只有一个实例,并且提供一个全局的访问点,比如常见的 Spring Bean 单例模式。
  4. 原型模式(Prototype Pattern) :通过复制现有的对象来创建新的对象,从而避免了对象的创建成本和复杂度。
  5. 适配器模式(Adapter Pattern) :将一个类的接口转换成客户端所期望的接口,从而解决了接口不兼容的问题。
  6. 桥接模式(Bridge Pattern) :将抽象部分与实现部分分离开来,从而使它们可以独立变化。
  7. 装饰器模式(Decorator Pattern) :动态地给一个对象添加一些额外的职责,比如 Java 中的 IO 流处理。
  8. 代理模式(Proxy Pattern) :为其他对象提供一种代理以控制对这个对象的访问,比如常见的 Spring AOP 代理模式。
  9. 观察者模式(Observer Pattern) :定义了对象之间一种一对多的依赖关系,从而当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并自动更新。

策略模式替换条件逻辑

策略模式是一种常见的优化条件逻辑的方法。当代码中有一个包含大量条件逻辑(即 if 语句)的方法时,你应该考虑使用策略模式对其进行优化,这样代码更加清晰,同时也更容易维护。

假设我们有这样一段代码:

public class IfElseDemo {

    public double calculateInsurance(double income) {
        if (income <= 10000) {
            return income*0.365;
        } else if (income <= 30000) {
            return (income-10000)*0.2+35600;
        } else if (income <= 60000) {
            return (income-30000)*0.1+76500;
        } else {
            return (income-60000)*0.02+105600;
        }

    }
}

下面是使用策略+工厂模式重构后的代码:

首先定义一个接口 InsuranceCalculator,其中包含一个方法 calculate(double income),用于计算保险费用。

public interface InsuranceCalculator {
    double calculate(double income);
}

然后,分别创建四个类来实现这个接口,每个类代表一个保险费用计算方式。

public class FirstLevelCalculator implements InsuranceCalculator {
    public double calculate(double income) {
        return income * 0.365;
    }
}

public class SecondLevelCalculator implements InsuranceCalculator {
    public double calculate(double income) {
        return (income - 10000) * 0.2 + 35600;
    }
}

public class ThirdLevelCalculator implements InsuranceCalculator {
    public double calculate(double income) {
        return (income - 30000) * 0.1 + 76500;
    }
}

public class FourthLevelCalculator implements InsuranceCalculator {
    public double calculate(double income) {
        return (income - 60000) * 0.02 + 105600;
    }
}

最后,我们可以为每个策略类添加一个唯一的标识符,例如字符串类型的 name 属性。然后,在工厂类中创建一个 Map 来存储策略对象和它们的标识符之间的映射关系(也可以用 switch 来维护映射关系)。

import java.util.HashMap;
import java.util.Map;

public class InsuranceCalculatorFactory {
    private static final Map<String, InsuranceCalculator> CALCULATOR_MAP = new HashMap<>();

    static {
        CALCULATOR_MAP.put("first", new FirstLevelCalculator());
        CALCULATOR_MAP.put("second", new SecondLevelCalculator());
        CALCULATOR_MAP.put("third", new ThirdLevelCalculator());
        CALCULATOR_MAP.put("fourth", new FourthLevelCalculator());
    }

    public static InsuranceCalculator getCalculator(double income) {
        if (income <= 10000) {
            return CALCULATOR_MAP.get("first");
        } else if (income <= 30000) {
            return CALCULATOR_MAP.get("second");
        } else if (income <= 60000) {
            return CALCULATOR_MAP.get("third");
        } else {
            return CALCULATOR_MAP.get("fourth");
        }
    }
}

这样,就可以通过 InsuranceCalculatorFactory 类手动获取相应的策略对象了。

double income = 40000;
// 获取第三级保险费用计算器
InsuranceCalculator calculator = InsuranceCalculatorFactory.getCalculator(income);
double insurance = calculator.calculate(income);
System.out.println("保险费用为:" + insurance);

这种方式允许我们在运行时根据需要选择不同的策略,而无需在代码中硬编码条件语句。

相关阅读:Replace Conditional Logic with Strategy Pattern - IDEA 。

除了策略模式之外,Map+函数式接口也能实现类似的效果,代码一般还要更简洁一些。

下面是使用Map+函数式接口重构后的代码:

首先,在 InsuranceCalculatorFactory 类中,将 getCalculator 方法的返回类型从 InsuranceCalculator 改为 Function<Double, Double>,表示该方法返回一个将 double 类型的 income 映射到 double 类型的 insurance 的函数。

import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

public class InsuranceCalculatorFactory {
    private static final Map<String, Function<Double, Double>> CALCULATOR_MAP = new HashMap<>();

    static {
        CALCULATOR_MAP.put("first", income -> income * 0.365);
        CALCULATOR_MAP.put("second", income -> (income - 10000) * 0.2 + 35600);
        CALCULATOR_MAP.put("third", income -> (income - 30000) * 0.1 + 76500);
        CALCULATOR_MAP.put("fourth", income -> (income - 60000) * 0.02 + 105600);
    }

    public static Function<Double, Double> getCalculator(double income) {
        if (income <= 10000) {
            return CALCULATOR_MAP.get("first");
        } else if (income <= 30000) {
            return CALCULATOR_MAP.get("second");
        } else if (income <= 60000) {
            return CALCULATOR_MAP.get("third");
        } else {
            return CALCULATOR_MAP.get("fourth");
        }
    }
}

然后,在调用工厂方法时,可以使用 Lambda 表达式或方法引用来代替实现策略接口的类。

double income = 40000;
Function<Double, Double> calculator = InsuranceCalculatorFactory.getCalculator(income);
double insurance = calculator.apply(income);
System.out.println("保险费用为:" + insurance);;

复杂对象使用建造者模式

复杂对象的创建可以使用建造者模式优化。

使用 Caffeine 创建本地缓存的代码示例:

Caffeine.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterWrite(60, TimeUnit.DAYS)
                // 初始的缓存空间大小
                .initialCapacity(100)
                // 缓存的最大条数
                .maximumSize(500)
                .build();

链式处理优先使用责任链模式

责任链模式在实际开发中还是挺实用的,像 MyBatis、Netty、OKHttp3、SpringMVC、Sentinel 等知名框架都大量使用了责任链模式。

如果一个请求需要进过多个步骤处理的话,可以考虑使用责任链模式。

责任链模式下,存在多个处理者,这些处理者之间有顺序关系,一个请求被依次传递给每个处理者(对应的是一个对象)进行处理。处理者可以选择自己感兴趣的请求进行处理,对于不感兴趣的请求,转发给下一个处理者即可。如果满足了某个条件,也可以在某个处理者处理完之后直接停下来。

责任链模式下,如果需要增加新的处理者非常容易,符合开闭原则。

Netty 中的 ChannelPipeline 使用责任链模式对数据进行处理。我们可以在 ChannelPipeline 上通过 addLast() 方法添加一个或者多个ChannelHandler (一个数据或者事件可能会被多个 Handler 处理) 。当一个 ChannelHandler 处理完之后就将数据交给下一个 ChannelHandler

ChannelPipeline pipeline = ch.pipeline()
      // 添加一个用于对 HTTP 请求和响应报文进行编解码的 ChannelHandler
      .addLast(HTTP_CLIENT_CODEC, new HttpClientCodec())
       // 添加一个对 gzip 或者 deflate 格式的编码进行解码的 ChannelHandler
      .addLast(INFLATER_HANDLER, new HttpContentDecompressor())
       // 添加一个用于处理分块传输编码的 ChannelHandler
      .addLast(CHUNKED_WRITER_HANDLER, new ChunkedWriteHandler())
       // 添加一个处理 HTTP 请求并响应的 ChannelHandler
      .addLast(AHC_HTTP_HANDLER, new HttpHandler);

Tomcat 中的请求处理是通过一系列过滤器(Filter)来完成的,这同样是责任连模式的运用。每个过滤器都可以对请求进行处理,并将请求传递给下一个过滤器,直到最后一个过滤器将请求转发到相应的 Servlet 或 JSP 页面。

public class CompressionFilter implements Filter {
    // ...
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        // 检查是否支持压缩
        if (isCompressable(request, response)) {
            // 创建一个自定义的响应对象,用于在压缩数据时获取底层输出流
            CompressionServletResponseWrapper wrappedResponse = new CompressionServletResponseWrapper(
                    (HttpServletResponse) response);
            try {
                // 将请求转发给下一个过滤器或目标 Servlet/JSP 页面
                chain.doFilter(request, wrappedResponse);
                // 压缩数据并写入原始响应对象的输出流
                wrappedResponse.finishResponse();
            } catch (IOException e) {
                log.warn(sm.getString("compressionFilter.compressFailed"), e); //$NON-NLS-1$
                handleIOException(e, wrappedResponse);
            }
        } else {
            // 不支持压缩,直接将请求转发给下一个过滤器或目标 Servlet/JSP 页面
            chain.doFilter(request, response);
        }
    }
    // ...
}

相关阅读:聊一聊责任链模式 。

使用观察者模式解耦

观察者模式也是解耦的利器。当对象之间存在一对多关系,可以使用观察者模式,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者,观察者收到通知之后可以根据通知的内容去针对性地做一些事情。

Spring 事件就是基于观察者模式实现的。

1、定义一个事件。

public class CustomSpringEvent extends ApplicationEvent {
    private String message;

    public CustomSpringEvent(Object source, String message) {
        super(source);
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}

2、创建事件发布者发布事件。

@Component
public class CustomSpringEventPublisher {
    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publishCustomEvent(final String message) {
        System.out.println("Publishing custom event. ");
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }
}

3、创建监听器监听并处理事件(支持异步处理事件的方式,需要配置线程池)。

@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
    @Override
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println("Received spring custom event - " + event.getMessage());
    }
}

抽象父类利用模板方法模式定义流程

多个并行的类实现相似的代码逻辑。我们可以考虑提取相同逻辑在父类中实现,差异逻辑通过抽象方法留给子类实现。

对于相同的流程和逻辑,我们还可以借鉴模板方法模式将其固定成模板,保留差异的同时尽可能避免代码重复。

下面是一个利用模板方法模式定义流程的示例代码:

public abstract class AbstractDataImporter {
    private final String filePath;

    public AbstractDataImporter(String filePath) {
        this.filePath = filePath;
    }

    public void importData() throws IOException {
        List<String> data = readDataFromFile();
        validateData(data);
        saveDataToDatabase(data);
    }

    protected abstract List<String> readDataFromFile() throws IOException;

    protected void validateData(List<String> data) {
        // 若子类没有实现该方法,则不进行数据校验
    }

    protected abstract void saveDataToDatabase(List<String> data);

    protected String getFilePath() {
        return filePath;
    }
}

在上面的代码中,AbstractDataImporter 是一个抽象类。该类提供了一个 importData() 方法,它定义了导入数据的整个流程。具体而言,该方法首先从文件中读取原始数据,然后对数据进行校验,最后将数据保存到数据库中。

其中,readDataFromFile()saveDataToDatabase() 方法是抽象的,由子类来实现。validateData() 方法是一个默认实现,可以通过覆盖来定制校验逻辑。getFilePath() 方法用于获取待导入数据的文件路径。

子类继承 AbstractDataImporter 后,需要实现 readDataFromFile()saveDataToDatabase() 方法,并覆盖 validateData() 方法(可选)。例如,下面是一个具体的子类 CsvDataImporter 的实现:

public class CsvDataImporter extends AbstractDataImporter {
    private final char delimiter;

    public CsvDataImporter(String filePath, char delimiter) {
        super(filePath);
        this.delimiter = delimiter;
    }

    @Override
    protected List<String> readDataFromFile() throws IOException {
        List<String> data = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(getFilePath()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                data.add(line);
            }
        }
        return data;
    }

    @Override
    protected void validateData(List<String> data) {
        // 对 CSV 格式的数据进行校验,例如检查是否每行都有相同数量的字段等
    }

    @Override
    protected void saveDataToDatabase(List<String> data) {
        // 将 CSV 格式的数据保存到数据库中,例如将每行解析为一个对象,然后使用 JPA 保存到数据库中
    }
}

在上面的代码中,CsvDataImporter 继承了 AbstractDataImporter 类,并实现了 readDataFromFile()saveDataToDatabase() 方法。它还覆盖了 validateData() 方法,以支持对 CSV 格式的数据进行校验。

通过以上实现,我们可以通过继承抽象父类并实现其中的抽象方法,来定义自己的数据导入流程。另外,由于抽象父类已经定义了整个流程的结构和大部分默认实现,因此子类只需要关注定制化的逻辑即可,从而提高了代码的可复用性和可维护性。

相关阅读:21 | 代码重复:搞定代码重复的三个绝招 - Java 业务开发常见错误 100 例 。

善用 Java 新特性

Java 版本在更新迭代过程中会增加很多好用的特性,一定要善于使用 Java 新特性来优化自己的代码,增加代码的可阅读性和可维护性。

就比如火了这么多年的 Java 8 在增强代码可读性、简化代码方面,相比 Java 7 增加了很多功能,比如 Lambda、Stream 流操作、并行流(ParallelStream)、Optional 可空类型、新日期时间类型等。

Lambda 优化排序代码示例:

// 匿名内部类实现数组从小到大排序
Integer[] scores = {89, 100, 77, 90,  86};
Arrays.sort(scores,new Comparator<Integer>(){
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
});
for(Integer score:scores){
    System.out.print(score);
}
// 使用 Lambda 优化
Arrays.sort(scores,(o1,o2)->o1.compareTo(o2) );
// 还可以像下面这样写
Arrays.sort(scores,Comparator.comparing(Integer::intValue));

Optional 优化代码示例:

private Double calculateAverageGrade(Map<String, List<Integer>> gradesList, String studentName)
  throws Exception {
 return Optional.ofNullable(gradesList.get(studentName))// 创建一个Optional对象,传入参数为空时返回Optional.empty()
   .map(list -> list.stream().collect(Collectors.averagingDouble(x -> x)))// 对 Optional 的值进行操作
   .orElseThrow(() -> new NotFoundException("Student not found - " + studentName));// 当值为空时,抛出指定的异常
}

再比如 Java 17 中转正的密封类(Sealed Classes) ,Java 16 中转正的记录类型(record关键字定义)、instanceof 模式匹配等新特性。

record关键字优化代码示例:

/**
 * 这个类具有两个特征
 * 1. 所有成员属性都是final
 * 2. 全部方法由构造方法,和两个成员属性访问器组成(共三个)
 * 那么这种类就很适合使用record来声明
 */
final class Rectangle implements Shape {
    final double length;
    final double width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    double length() { return length; }
    double width() { return width; }
}
/**
 * 1. 使用record声明的类会自动拥有上面类中的三个方法
 * 2. 在这基础上还附赠了equals(),hashCode()方法以及toString()方法
 * 3. toString方法中包括所有成员属性的字符串表示形式及其名称
 */
record Rectangle(float length, float width) { }

使用 Bean 自动映射工具

我们经常在代码中会对一个数据结构封装成 DO、DTO、VO 等,而这些 Bean 中的大部分属性都是一样的,所以使用属性拷贝类工具可以帮助我们节省大量的 set 和 get 操作。

常用的 Bean 映射工具有:Spring BeanUtils、Apache BeanUtils、MapStruct、ModelMapper、Dozer、Orika、JMapper 。

由于 Apache BeanUtils 、Dozer 、ModelMapper 性能太差,所以不建议使用。MapStruct 性能更好而且使用起来比较灵活,是一个比较不错的选择。

这里以 MapStruct 为例,简单演示一下转换效果。

1、定义两个类 EmployeeEmployeeDTO

public class Employee {
    private int id;
    private String name;
    // getters and setters
}

public class EmployeeDTO {
    private int employeeId;
    private String employeeName;
    // getters and setters
}

2、定义转换接口让 EmployeeEmployeeDTO互相转换。

@Mapper
public interface EmployeeMapper {
    // Spring 项目可以将 Mapper 注入到 IoC 容器中,这样就可以像 Spring Bean 一样调用了
    EmployeeMapper INSTANT = Mappers.getMapper(EmployeeMapper.class);

    @Mapping(target="employeeId", source="entity.id")
    @Mapping(target="employeeName", source="entity.name")
    EmployeeDTO employeeToEmployeeDTO(Employee entity);

    @Mapping(target="id", source="dto.employeeId")
    @Mapping(target="name", source="dto.employeeName")
    Employee employeeDTOtoEmployee(EmployeeDTO dto);
}

3、实际使用。

//  EmployeeDTO 转  Employee
Employee employee = EmployeeMapper.INSTANT.employeeToEmployeeDTO(employee);

//  Employee 转  EmployeeDTO
EmployeeDTO employeeDTO = EmployeeMapper.INSTANT.employeeDTOtoEmployee(employeeDTO);

相关阅读:

  • MapStruct,降低无用代码的神器 - 大淘宝技术 - 2022 (推荐):对于 MapStruct 的各种操作介绍的更详细一些,涉及到一对多字段互转、为转换加缓存、 利用 Spring 进行依赖注入等高级用法。
  • 告别 BeanUtils,Mapstruct 从入门到精通 - 大淘宝技术 - 2022 :主要和 Spring 的 BeanUtils 做了简单对比,介绍的相对比较简单。

规范日志打印

1、不要随意打印日志,确保自己打印的日志是后面能用到的。

打印太多无用的日志不光影响问题排查,还会影响性能,加重磁盘负担。

2、打印日志中的敏感数据比如身份证号、电话号、密码需要进行脱敏。相关阅读:Spring Boot 3 步完成日志脱敏,简单实用!!

3、选择合适的日志打印级别。最常用的日志级别有四个: DEBUG、INFO、WARN、ERROR。

  • DEBUG(调试):开发调试日志,主要开发人员开发调试过程中使用,生产环境禁止输出 DEBUG 日志。
  • INFO(通知):正常的系统运行信息,一些外部接口的日志,通常用于排查问题使用。
  • WARN(警告):警告日志,提示系统某个模块可能存在问题,但对系统的正常运行没有影响。
  • ERROR(错误):错误日志,提示系统某个模块可能存在比较严重的问题,会影响系统的正常运行。

4、生产环境禁止输出 DEBUG 日志,避免打印的日志过多(DEBUG 日志非常多)。

5、应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。

Spring Boot 应用程序可以直接使用内置的日志框架 Logback,Logback 就是按照 SLF4J API 标准实现的。

6、异常日志需要打印完整的异常信息。

反例:

try {
    //读文件操作
    readFile();
} catch (IOException e) {
    // 只保留了异常消息,栈没有记录
    log.error("文件读取错误, {}", e.getMessage());
}

正例:

try {
    //读文件操作
    readFile();
} catch (IOException e) {
    log.error("文件读取错误", e);
}

7、避免层层打印日志。

举个例子:method1 调用 method2,method2 出现 error 并打印 error 日志,method1 也打印了 error 日志,等同于一个错误日志打印了 2 遍。

8、不要打印日志后又将异常抛出。

反例:

try {
  	 ...
} catch (IllegalArgumentException e) {
    log.error("出现异常啦", e);
    throw e;
}

在日志中会对抛出的一个异常打印多条错误信息。

正例:

try {
  	 ...
} catch (IllegalArgumentException e) {
    log.error("出现异常啦", e);
}
// 或者包装成自定义异常之后抛出
try {
  	 ...
} catch (IllegalArgumentException e) {
    throw new MyBusinessException("一段对异常的描述信息.", e);
}

相关阅读:15 个日志打印的实用建议 。

规范异常处理

阿里巴巴 Java 异常处理规约如下:

阿里巴巴Java异常处理规约

统一异常处理

所有的异常都应该由最上层捕获并处理,这样代码更简洁,还可以避免重复输出异常日志。 如果我们都在业务代码中使用try-catch或者try-catch-finally处理的话,就会让业务代码中冗余太多异常处理的逻辑,对于同样的异常我们还需要重复编写代码处理,还可能会导致重复输出异常日志。这样的话,代码可维护性、可阅读性都非常差。

Spring Boot 应用程序可以借助 @RestControllerAdvice@ExceptionHandler 实现全局统一异常处理。

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Result businessExceptionHandler(HttpServletRequest request, BusinessException e){
        ...
        return Result.faild(e.getCode(), e.getMessage());
    }
    ...
}

使用 try-with-resource 关闭资源

  1. 适用范围(资源的定义): 任何实现 java.lang.AutoCloseable或者 java.io.Closeable 的对象
  2. 关闭资源和 finally 块的执行顺序:try-with-resources 语句中,任何 catch 或 finally 块在声明的资源关闭后运行

《Effective Java》中明确指出:

面对必须要关闭的资源,我们总是应该优先使用 try-with-resources 而不是try-finally。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources语句让我们更容易编写必须要关闭的资源的代码,若采用try-finally则几乎做不到这点。

Java 中类似于InputStreamOutputStreamScannerPrintWriter等的资源都需要我们调用close()方法来手动关闭,一般情况下我们都是通过try-catch-finally语句来实现这个需求,如下:

//读取文本文件的内容
Scanner scanner = null;
try {
    scanner = new Scanner(new File("D://read.txt"));
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} finally {
    if (scanner != null) {
        scanner.close();
    }
}

使用 Java 7 之后的 try-with-resources 语句改造上面的代码:

try (Scanner scanner = new Scanner(new File("test.txt"))) {
    while (scanner.hasNext()) {
        System.out.println(scanner.nextLine());
    }
} catch (FileNotFoundException fnfe) {
    fnfe.printStackTrace();
}

当然多个资源需要关闭的时候,使用 try-with-resources 实现起来也非常简单,如果你还是用try-catch-finally可能会带来很多问题。

通过使用分号分隔,可以在try-with-resources块中声明多个资源。

try (BufferedInputStream bin = new BufferedInputStream(new FileInputStream(new File("test.txt")));
     BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(new File("out.txt")))) {
    int b;
    while ((b = bin.read()) != -1) {
        bout.write(b);
    }
}
catch (IOException e) {
    e.printStackTrace();
}

不要把异常定义为静态变量

不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动 new 一个异常对象抛出。

// 错误做法
public class Exceptions {
    public static BusinessException ORDEREXISTS = new BusinessException("订单已经存在", 3001);
...
}

其他异常处理注意事项

  • 抛出完整具体的异常信息(避免 throw new BIZException(e.getMessage()这种形式的异常抛出),尽量自定义异常,而不是直接使用 RuntimeExceptionException
  • 优先捕获具体的异常类型。
  • 捕获了异常之后一定要处理,避免直接吃掉异常。
  • ......

接口不要直接返回数据库对象

接口不要直接返回数据库对象(也就是 DO),数据库对象包含类中所有的属性。

// 错误做法
public UserDO getUser(Long userId) {
  return userService.getUser(userId);
}

原因:

  • 如果数据库查询不做字段限制,会导致接口数据庞大,浪费用户的宝贵流量。
  • 如果数据库查询不做字段限制,容易把敏感字段暴露给接口,导致出现数据的安全问题。
  • 如果修改数据库对象的定义,接口返回的数据紧跟着也要改变,不利于维护。

建议的做法是单独定义一个类比如 VO(可以看作是接口返回给前端展示的对象数据)来对接口返回的数据进行筛选,甚至是封装和组合。

public UserVo getUser(Long userId) {
  UserDO userDO = userService.getUser(userId);
  UserVO userVO = new UserVO();
  BeanUtils.copyProperties(userDO, userVO);//演示使用
  return userVO;
}

统一接口返回值

接口返回的数据一定要统一格式,遮掩更方面对接前端开发的同学以及其他调用该接口的开发。

通常来说,下面这些信息是必备的:

  1. 状态码和状态信息:可以通过枚举定义状态码和状态信息。状态码标识请求的结果,状态信息属于提示信息,提示成功信息或者错误信息。
  2. 请求数据:请求该接口实际要返回的数据比如用户信息、文章列表。
public enum ResultEnum implements IResult {
    SUCCESS(2001, "接口调用成功"),
    VALIDATE_FAILED(2002, "参数校验失败"),
    COMMON_FAILED(2003, "接口调用失败"),
    FORBIDDEN(2004, "没有权限访问资源");

    private Integer code;
    private String message;
    ...
}

public class Result<T> {
    private Integer code;
    private String message;
    private T data;
    ...
    public static <T> Result<T> success(T data) {
        return new Result<>(ResultEnum.SUCCESS.getCode(), ResultEnum.SUCCESS.getMessage(), data);
    }

    public static Result<?> failed() {
        return new Result<>(ResultEnum.COMMON_FAILED.getCode(), ResultEnum.COMMON_FAILED.getMessage(), null);
    }
    ...
}

对于 Spring Boot 项目来说,可以使用 @RestControllerAdvice 注解+ ResponseBodyAdvic接口统一处理接口返回值,实现代码无侵入。篇幅问题这里就不贴具体实现代码了,比较简单,具体实现方式可以参考这篇文章:Spring Boot 无侵入式 实现 API 接口统一 JSON 格式返回 。

需要注意的是,这种方式在 Spring Cloud OpenFeign 的继承模式下是有侵入性,解决办法见:SpringBoot 无侵入式 API 接口统一格式返回,在 Spring Cloud OpenFeign 继承模式具有了侵入性 。

实际项目中,其实使用比较多的还是下面这种比较直接的方式:

public class PostController {

	@GetMapping("/list")
	public R<List<SysPost>> getPosts() {
  ...
		return R.ok(posts);
	}
}

上面介绍的无侵入的方式,一般改造旧项目的时候用的比较多。

远程调用设置超时时间

开发过程中,第三方接口调用、RPC 调用以及服务之间的调用建议设置一个超时时间。

我们平时接触到的超时可以简单分为下面 2 种:

  • 连接超时(ConnectTimeout) :客户端与服务端建立连接的最长等待时间。
  • 读取超时(ReadTimeout) :客户端和服务端已经建立连接,客户端等待服务端处理完请求的最长时间。实际项目中,我们关注比较多的还是读取超时。

一些连接池客户端框架中可能还会有获取连接超时和空闲连接清理超时。

如果没有设置超时的话,就可能会导致服务端连接数爆炸和大量请求堆积的问题。这些堆积的连接和请求会消耗系统资源,影响新收到的请求的处理。严重的情况下,甚至会拖垮整个系统或者服务。

我之前在实际项目就遇到过类似的问题,整个网站无法正常处理请求,服务器负载直接快被拉满。后面发现原因是项目超时设置错误加上客户端请求处理异常,导致服务端连接数直接接近 40w+,这么多堆积的连接直接把系统干趴了。

相关阅读:超时&重试详解 。

正确使用线程池

在 10 个线程池最佳实践和坑! 这篇文章中,我总结了 10 个使用线程池的注意事项:

  1. 线程池必须手动通过 ThreadPoolExecutor 的构造函数来声明,避免使用 Executors 类创建线程池,会有 OOM 风险。
  2. 监测线程池运行状态。
  3. 建议不同类别的业务用不同的线程池。
  4. 别忘记给线程池命名。
  5. 正确配置线程池参数。
  6. 别忘记关闭线程池。
  7. 线程池尽量不要放耗时任务。
  8. 避免重复创建线程池。
  9. 使用 Spring 内部线程池时,一定要手动自定义线程池,配置合理的参数,不然会出现生产问题(一个请求创建一个线程)
  10. 线程池和 ThreadLocal 共用,可能会导致线程从 ThreadLocal 获取到的是旧值/脏数据。

敏感数据处理

  1. 返回前端的敏感数据比如身份证号、电话、地址信息要根据业务需求进行脱敏处理,示例:163****892
  2. 保存在数据库中的密码需要加盐之后使用哈希算法(比如 BCrypt)进行加密。
  3. 保存在数据库中的银行卡号、身份号这类敏感数据需要使用对称加密算法(比如 AES)保存。
  4. 网络传输的敏感数据比如银行卡号、身份号需要用 HTTPS + 非对称加密算法(如 RSA)来保证传输数据的安全性。
  5. 对于密码找回功能,不能明文存储用户密码。可以采用重置密码的方式,让用户通过验证身份后重新设置密码。
  6. 在代码中不应该明文写入密钥、口令等敏感信息。可以采用配置文件、环境变量等方式来动态加载这些信息。
  7. 定期更新敏感数据的加密算法和密钥,以保证加密算法和密钥的安全性和有效性。
本文转载于网络 如有侵权请联系删除

相关文章

  • 查看Python安装路径_Python安装路径

    大家好,又见面了,我是你们的朋友全栈君。查看Python安装路径方法在使用python的时候,有时候会需要找到python包的安装位置,怎么办?对于Windows平台,打开cmd使用命令py-0p【其中0是零】显示已安装的python版本且带路径的列表,参见下图:其中带星号*的为默认版本。其它还有一、对于Windows平台,打开cmd输入命令wherePython,回车(即按下Enter键)可输出Python的安装路径。参见下图:【提示:若安装了python虚拟环境virtualenv,其路径也将显示。】【如何清除命令行窗口(cmd)内容 输入cls回车】二、在IDLE(Python自带的简洁的集成开发环境)中先输入importsys回车再输入sys.path回车其中红线标识的就是路径,将其中的\\替换为\即可。但这种方式只能看到当前运行的Python的安装路径。三、以操作系统是Windows10计算机为例,右键此电脑,属性->高级系统设置->环境变量,在“用户变量”(对当前用户起作用)或“系统变量”(对此计算机上所有的用户起作用)找到path行点击选中,点击“编辑”找到带

  • Discourse 如何启用 matomo 代码跟踪

    如何在matomo中启用代码跟踪?当你问到这个问题的时候,我们确定自你应该对Matomo比较了解,并且知道Matomo是干什么的了。Matomo的前身是Piwik,好像是最近才开始改名的,是个非常出名的开源网站统计分析程序,提供了顶级关键字和搜索引擎,网站,社交媒体网站,首页网址,页面标题,用户国家,提供商,操作系统,浏览器市场份额,屏幕分辨率。更加主要的是,你将会获得所有用户访问的数据,能够有效避免因为网站屏蔽给你带来的问题,比如说GA在很多时候就访问不了。Matomo是基于PHP和MySql平台的,搭建也非常容易。我们这里就主要讲如何在Discourse中嵌入进去。首先你需要为你的主题安装主题组件,安装的方法请参考:Discourse如何安装一个主题组件或者主题组件页面中的内容。我们使用的仓库地址为:https://github.com/ossez-com/discourse-matomo-analytics,这个Fork仓库地址https://github.com/discourse/discourse-matomo-analytics.git的内容的。我们使用自己的仓库地址主要

  • 深度学习 | 双重注意力机制之CBAM

    点击上方“算法与数据之美”,选择“置顶公众号”更多精彩等你来!之前我们分享了2017年的冠军图像分类模型SENet,今天给大家带来的这篇2018年发表在ECCV上的论文不仅考虑到了不同特征通道的重要性不一,还考虑到了同一个特征通道的不同位置的重要性程度。也就是说既包含通道的注意力机制,又包含空间的注意力机制。相比于只关注通道的SENet,ConvolutionalBlockAttentionModule(CBAM)取得了更好的效果。上图为整个CBAM的示意图,先是通过注意力机制模块,然后是空间注意力模块,对于两个模块先后顺序对模型性能的影响,本文作者也给出了实验的数据对比,先通道再空间要比先空间再通道以及通道和空间注意力模块并行的方式效果要略胜一筹。那么这个通道注意力模块和空间注意力模块又是如何实现的呢?通道注意力模块这个部分大体上和SENet的注意力模块相同,主要的区别是CBAM在S步采取了全局平均池化以及全局最大池化,两种不同的池化意味着提取的高层次特征更加丰富。接着在E步同样通过两个全连接层和相应的激活函数建模通道之间的相关性,合并两个输出得到各个特征通道的权重。最后,得到特征通

  • ​什么问题最让程序员头秃?我们分析了11种语言的11000个问题

    导读:自2008年成立以来,StackOverflow一直在拯救所有类型的开发人员。自那时以来,开发人员提出了数百万个关于开发领域的问题。但是,迫使开发者转向StackOverflow的问题都是什么呢?作者:NickRoberts编译/来源:AI科技大本营(ID:rgznai100)我们选择了11种最流行的编程语言(以StackOverflow标签的频率来衡量),并进行了一项研究,旨在揭示这些问题中的某些共性和差异。但在这之前,让我们先瞧瞧如下所示的11种语言。就所提问题的数量而言,JavaScript是自StackOverflow成立以来最常被问到的编程语言。这可能是由于其在众多不同应用和服务中无处不在:无论你以任何方式在网络上工作,都可能需要了解一些JavaScript。但是,尽管JavaScript可能是整体排名最高的编程语言,但当我们按时间划分数据时,我们发现需要一个新的王冠。2011年,《哈佛商业评论》将数据科学家标记为“21世纪最性感的工作(SexiestJobofthe21stCentury)”。从那以后,数据科学家常用的Python语言的受欢迎程度一直在增长……以至于到

  • 是混合云还是私有云?AWS到底在嘴硬什么?

    AWS一直是纯粹的公有云供应商,即使它已经对混合云做出了点头,但未曾有过其他大的动作。AWS尚未采用混合云的最重要步骤将成为许多企业用户欢迎的举措,但可能会引起其他人的担忧。11月28日,AWS与VMware合作推出了新的云服务:AWSOutposts。AWSOutposts是由AWS硬件和软件组成的计算和存储机架,将允许需要本地基础结构的企业客户在自己的数据中心中运行AWS云基础架构。Outposts源起于什么?该服务首先将基于AWS自己的硬件,提供两种软件:VMwareCloudonAWS:可直接使用VMware控制面板;AWSNative:允许客户使用AWS云中相同的AWSAPI在本地进行计算和存储。VMwareCEOGelsinger表示,VMware不仅在AWSOutposts上提供VMwareCloud选项,还在其他产品中构建集成点,支持新形式的基础架构。AWSCEOAndyJassy表示,AWS不仅会提供装载其软件服务的硬件机架,还会为客户安装和维护这些硬件,它将在2019年开始发布。Jassy同时强调,AWSOutposts的推出是应广大企业客户的呼声。企业客户都长期使

  • ss-panel-v3-mod魔改版常用审计规则分享分享

    前面我们讲了《基于宝塔搭建SS-Panel教程》、《【图文】使用SS-Panel以及怎么部署SSR后端教程》、《SS-Panel魔改面板.config.php文件详解》三篇教程,接下来给各位几个非常实用的审计规则,可以有效防止滥用和避免开机场后遇到的一些麻烦。首先是屏蔽SPAM,防止有恶意用户使用你的酸酸服务器进行邮件滥发:(Subject|HELO|SMTP)复制如图填写即可:接着是禁止BT下载,防止有些逗比用户用你的酸酸服务器BT下载版权内容或者是踩到蜜灌被版权方投诉,导致商家把你的服务器停止。BitTorrentprotocol复制如图填写即可:还有这些规则就不一一截图啦禁用BT防止版权争议BitTorrentprotocol复制数据包明文匹配禁止百度高精度定位防止IP与客户端地理位置被记录(api|ps|sv|offnavi|newvector|ulog\.imap|newloc)(\.map|)\.(baidu|n\.shifen)\.com复制数据包明文匹配禁止360有毒服务屏蔽360(.+\.|^)(360|so)\.(cn|com)复制数据包明文匹配禁止邮件滥发防止垃圾

  • 定制人脸图像没那么难!使用TL-GAN模型轻松变脸

    选自Medium作者:ShaoboGUAN机器之心编译参与:王淑婷、路、张倩基于描述生成逼真图像是一项比较困难的任务。本文介绍了一项新研究TransparentLatent-spaceGAN(TL-GAN),它使用英伟达的pg-GAN模型,利用潜在空间中的特征轴,轻松完成图像合成和编辑任务。使用TL-GAN模型进行受控人脸图像合成的示例。教电脑根据描述生成照片判别任务VS生成任务描述一张图像对人类来说相当容易,我们在很小的时候就能做到。在机器学习中,这项任务是一个判别分类/回归问题,即从输入图像预测特征标签。随着最近ML/AI技术(尤其是深度学习模型)的进步,它们开始在这些任务中脱颖而出,有时会达到甚至超过人类的表现,如视觉目标识别(例如,从AlexNet到ResNet在ImageNet分类任务上的表现)和目标检测/分割(如从RCNN到YOLO在COCO数据集上的表现)等场景中展示的一样。然而,另一方面,基于描述生成逼真图像却要困难得多,需要多年的平面设计训练。在机器学习中,这是一项生成任务,比判别任务难多了,因为生成模型必须基于更小的种子输入产出更丰富的信息(如具有某些细节和变化的完

  • 非 SDK 接口常见问题 | Android 开发者 FAQ Vol.13

    常规问题Q1:什么是非SDK接口?A:非SDK接口指不在官方AndroidSDK涵盖范围内的Java字段和方法。此类接口是SDK的内部实现细节,可能随时会被修改,且不对开发者另行通知。常规问题Q2:AndroidP在非SDK接口使用限制方面采取了哪些举措?A:谷歌正在逐步限制非SDK接口的使用:针对不同接口采取不同形式的限制(详情请参照条目“应用运行时,我应该如何检测非SDK接口的使用?”)。若您正在使用非SDK接口进行开发,请特别注意限制对应用行为造成的影响。常规问题Q3:如果我正在使用非SDK接口,我应该如何提交请求,申请重新评估该接口?A:请提交申请并提供需要使用此接口的详细原因。常规问题Q4:非SDK接口都有哪些不同管控名单?且不同名单下接口的限制又有何不同?A:以下为各名单的具体说明:白名单:SDK本身浅灰名单:仍允许调用的非SDK方法和字段深灰名单若应用的targetSDK低于AndroidP(即targetSdkVersion<28):允许调用深灰名单中的接若应用的targetSDK为AndroidP或更高(即targetSdkVersion>=28):深灰名

  • 【学习】Hadoop大数据学习线路图

    入门知识对于我们新手入门学习hadoop的朋友来说,首先了解一下云计算和云计算技术是有必要的。下面先是介绍云计算和云计算技术的:云计算,是一种基于互联网的计算方式,通过这种方式,共享的软硬件资源和信息可以按需求提供给计算机和其他设备,主要是基于互联网的相关服务地增加、使用和交付模式,通常涉及通过互联网来提供动态易扩展且经常是虚拟化的资源。云是网络、互联网的一种比喻说法。过去在图中往往用云来表示电信网,后来也用来表示互联网和底层基础设施的抽象。狭义云计算指IT基础设施的交付和使用模式,指通过网络以按需、易扩展的方式获得所需资源;广义云计算指服务地交付和使用模式,指通过网络以按需、易扩展的方式获得所需服务。这种服务可以是IT和软件、互联网相关,也可是其他服务。它意味着计算也可作为一种商品通过互联网进行流通。什么是云计算?什么是云计算技术?在世界上云计算已经大面流行,有很流行的Google、Drive、SkyDrive、Dropbox、亚马逊云服务等等。在国内百度云存储、360云存储都是比较流行的。我们接下来就应该会想到大数据存储,目前开源市场上最流行的应该是hadoop分布式存储,已经有大

  • php登录实例代码

    1login.php <?php //$conn=mysql_connect("localhost","root","root")ordie("Couldnotconnectmysql!".msyql_error()."<br/>"); //mysql_query("createdatabasecookie_user")ordie("Createdatabasefailed!".mysql_error()."<br/>"); //mysql_select_db("cookie_user"); /*$sql="createtableuser( idint(10)notnullauto_increment, namevarchar(50)defaultnull, passwordvarchar(50)defaultnull, ageint(5)defaul

  • jedis:连接池(JedisPool)使用示例

    Jedis实例不是线程安全的,所以不可以多个线程共用一个Jedis实例,但是创建太多的实现也不好因为这意味着会建立很多sokcet连接。 JedisPool是一个线程安全的网络连接池。可以用JedisPool创建一些可靠Jedis实例,可以从池中获取Jedis实例,使用完后再把Jedis实例还回JedisPool。这种方式可以避免创建大量socket连接并且会实现高效的性能.JedisPool初始化JedisPoolConfigjedisPoolConfig=newJedisPoolConfig(); //设置最大10个连接 jedisPoolConfig.setMaxTotal(10); pool=newJedisPool(jedisPoolConfig,"localhost");复制使用JedisPool1.JedisPool#getResource()方法从连接池中取得一个Jedis实例, 2.使用Jedis实例进行正常的数据操作 3.Jedis实例使用完后要把它再放回连接池。资源释放关于如何将使用完后的Jedis实例还回连接池,网上看到的大部分文章都是建议用

  • 线程池中你不容错过的一些细节

    背景上周分享了一篇《一个线程罢工的诡异事件》,最近也在公司内部分享了这个案例。无独有偶,在内部分享的时候也有小伙伴问了之前分享时所提出的一类问题:这其实是一类共性问题,我认为主要还是两个原因:我自己确实也没讲清楚,之前画的那张图还需要再完善,有些误导。第二还是大家对线程池的理解不够深刻,比如今天要探讨的内容。线程池的工作原理首先还是来复习下线程池的基本原理。我认为线程池它就是一个调度任务的工具。众所周知在初始化线程池会给定线程池的大小,假设现在我们有1000个线程任务需要运行,而线程池的大小为10~20,在真正运行任务的过程中他肯定不会创建这1000个线程同时运行,而是充分利用线程池里这10~20个线程来调度这1000个任务。而这里的10~20个线程最后会由线程池封装为ThreadPoolExecutor.Worker对象,而这个Worker是实现了Runnable接口的,所以他自己本身就是一个线程。深入分析这里我们来做一个模拟,创建了一个核心线程、最大线程数、阻塞队列都为2的线程池。这里假设线程池已经完成了预热,也就是线程池内部已经创建好了两个线程Worker。当我们往一个线程池丢一

  • git 创建标签 tag

    1.gittag<name>就可以打一个新标签 加上-a参数来创建一个带备注的tag,备注信息由-m指定。如果你未传入-m则创建过程系统会自动为你打开编辑器让你填写备注信息。 gittag-atagName-m"mytag"复制   2. 列出已有的tag gittag复制 3.给指定的某个commit号加tag gittag-av1.29fceb02-m"mytag" 复制 4.将tag同步到远程服务器 gitpushoriginv1.0复制 推送所有: gitpushorigin--tags复制      顶 Top 收藏 关注 评论

  • 软件测试的策略是什么?

    软件测试策略:在一定的软件测试标准、测试规范的指导下,依据测试项目的特定环境约束而规定的软件测试的原则、方式、方法的集合。

  • 行内 外部 样式 引入图片

    <Image style={{height:'25px'}} //src="../../../../../title1.png" //src={require("./resource/title.jpg").default} src={require('design/assets/images/resource/title.jpg').default} //src="https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png" />复制 background:url('../images/resource/proxyBg.png')no-repeat!important; /*背景图垂直、水平均居中*/ background-position:centercenter!important; /*让背景图基于容器大小伸缩*/ background-size:cover!important;

  • VMware 安装MAC

    客户使用的MAC系统,自己没有,单独买一台又不划算,虚拟机就成了很好的选择。 因为之前有使用Kali,虚拟机已经安装过了,主要是按照MAC系统。 1、下载unlocker-master,解压后以管理员方式运行“win-install.cmd”,否则没有MAC系统的安装选项。 2、MAC系统镜像,下载的dmg镜像,安装失败,使用UltraISO转格式为ISO,还是安装失败,下载CDR格式镜像,进入界面,但是卡在苹果界面,提示“客户机操作系统已禁用CPU,请关闭或者重置虚拟机”。 3、在虚拟机安装的MAC系统所在文件夹中找到VMX文件,记事本格式打开,添加cpuid.1.eax="0000:0000:0000:0001:0000:0110:1010:0101" 再次打开安装,顺利安装完成

  • 01导入配置类和用户自定义添加db。ImportBeanDefinitionRegistrar和DeferredImportSelector

    =============================== ---@Import(AutoConfigurationImportSelector.class) 只要是@Import+DeferredImportSelector实现类都可以注入到dbregistry(可以仿照springboot的AutoConfigurationImportSelector的写法) ConfigurationClassParser ConfigurationClassParserparser=newConfigurationClassParser( this.metadataReaderFactory,this.problemReporter,this.environment, this.resourceLoader,this.componentScanBeanNameGenerator,registry); parser也注入了registry,但是此时还没有注入到registry -processDeferredImportSelectors(){...}; AutoConfigurationI

  • c 语言函数传参的sizeof问题

    今天用c写服务器后端时,调用一些函数的时候出了bug。 我函数传参传的是数组、结构体等 然后在函数中用sizeof的时候我惊奇的发现居然很怪。  举个例子 #include<iostream> #include<typeinfo> #include<stdio.h> #include<string.h> usingnamespacestd; //charstr[]="hello"; voidtest(charc[]) { printf("%s\n",c); printf("函数内大小:%d\n",sizeof(c));//8 printf("函数内长度:%d\n",strlen(c));//14 //这么神奇 } intmain() { test("recv_useless_s"); charc[]="recv_useless_s"; printf("函数外大小:%d\n",sizeof(c)); //printf("char大小:%d\n",sizeof(char*)); return0; }复制 &n

  • Spring Boot 学习笔记(三)

    一、脑补SSM  How2JSSM系列教程 二、脑补Spring、SpringMVC、Mybatis  基础功底是必须的 三、SpringBoot知识点完善  还是根据How2J教程里学习的   作  者:月暮 出  处:https://www.cnblogs.com/AardWolf/ 特此声明:欢迎园子的大大们指正错误,共同进步。如有问题或建议,也请各位大佬多多赐教!如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。 版权声明:本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

  • 基于jQuery扁平多颜色选项卡切换代码

    基于jQuery扁平多颜色选项卡切换代码,支持自动轮播切换,鼠标滑过切换的jQuery特效。效果图如下: 在线预览    源码下载 实现的代码。 html代码: <divclass="wrap"> <divclass="part1clearfix"> <divclass="slide"> <divclass="slide_cont"> <ulclass="clearfix"> <listyle="background-color:#4ab9f3;"><imgsrc="images/banner_01.png"/></li> <listyle="background-color:#4fc34e;"><imgsrc="images/banner_02.png"/></li> <listyle="background-color:#ff852a;"><imgsrc="images/banner_03.

  • 四、docker 镜像(二)

    常用镜像仓库 官方仓库:hub.docker.com 自己的私有仓库:Harbor 阿里云私有仓库:registry.cn-hangzhou.aliyuncs.com 复制 登录镜像仓库 #格式 dockerlogin 注:默认情况下,dockerlogin登录的是官方仓库,如果登录其他镜像仓库则需要指定镜像仓库的URL连接。 #镜像仓库申请地址: https://cr.console.aliyun.com/cn-shanghai/instances/repositories #实例 [root@Centos7~]#dockerloginregistry.cn-hangzhou.aliyuncs.com Username:xxxxxxx Password: WARNING!Yourpasswordwillbestoredunencryptedin/root/.docker/config.json. Configureacredentialhelpertoremovethiswarning.See https://docs.docker.com/engine/reference/

相关推荐

推荐阅读