重叠构造函数 、JavaBean模式、建造者模式、Spring的隐性大手

发布于:2025-03-23 ⋅ 阅读:(25) ⋅ 点赞:(0)

构造函数对我来讲是很平常的一个东西,今天来谈谈新的收获。

重叠构造函数

通常我们定义好实体类后,不会特意的去调整构造函数。

public class Person {
  private int age;
  private String name;
  private int sex;
  private int weight;
  
  public Person(int age,String name,int sex,int weight){
      this.age = age;
      this.name = name;
      this.sex = sex;
      this.weight = weight;
  }
}

如果想要其中的某个参数得到固定的默认值来减少入参的个数,我们就可以采用重叠构造的形式。

public class Person {
  private int age;
  private String name;
  private int sex;
  private int weight;
   
  public Person(int age){
     // 调用到的依旧是下方的Person构造函数 而 后三个入参以默认值的形式进行赋值
     // Person(age,"jack",1,60)
     // 不过这种写法需要写在构造函数的首行
     this(age,"jack",1,60);
   }
   
   public Person(int age,String name){
     this(age,name,1,60);
   }
 
   public Person(int age,String name,int sex){
     this(age,name,sex,60);
   }
  
  public Person(int age,String name,int sex,int weight){
      this.age = age;
      this.name = name;
      this.sex = sex;
      this.weight = weight;
  }
}

上面所述的代码中,我们分别重载了三个方法,针对不同的默认参数进行区分,目前看起来虽然达到了目的,但随着入参的增多,显示是不可取的。
当然 如果可以的话,我们重载的几个方法可以在定义方法名的时候就采用不同的名称,方便使用者通过名称就可以进行区分而不是每次都进入方法内部进行识别。

JavaBean模式

面对多个可选入参的构造时,可采用JavaBean的形式去创建对象实例。

import java.io.Serializable;

// 1. 实现 Serializable 接口 序列化方便数据持久化和网络传输
public class User implements Serializable {
    // 2. 私有属性
    private Long id;
    private String name;
    private String email;

    // 3. 无参构造器(必须)
    public User() {}

    // 4. Getter 和 Setter 方法(必须)
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

上面的代码中,可以看出,不同的变量拥有独立的赋值逻辑,很好的解决了重叠构造带来的重复性设定的问题。
接着就要说他的坏处,有一句话是这么说的:“过于灵活的东西,同样代表他具有不可控性。”

书中是这么说的。

  • 在构造函数被分到了多个调用中,构造的过程中JavaBean可能处于不一致的状态。
  • JavaBean模式使得把类做成不可变的可能性不复存在

不一致可以从以下几个方面简单了解:

  • 分步初始化导致中间状态不一致
    创建一个Person对象需要调用setName()和setAge()。在仅设置setName()后,对象已存在但age可能为默认值(如0)。若此时其他代码读取该对象,可能得到无效数据。
  • ‌违反约束
    一个类中要求width和height必须同时为正数。通过JavaBeans分别设置时,可能在中间步骤出现width=10, height=-5的无效状态。
  • 多线程读取问题
    多线程环境下,线程A正在设置setName()和setAge(),线程B可能在中间状态读取到半初始化的Person对象。

面对JavaBean的灵活性,需要增加的限制应当就是部分可控性。

建造者模式构造

public class TestBuild {
  private final int age;
  private final String name;
  private final int sex;
  private final int weight;
  
  // 定义Build静态类
  public static class Builder{
      // 首先定义两个需要被限制的属性变量
      private final int sex;
      private final int age;
      
      // 随意可变属性变量
      private String name;
      private int weight;
      
      // 定义Build构造函数
      public Builder(int sex,int age){
          this.sex = sex;
          this.age = age;
      }
      
      // 利用类似setter的形式对可变参数赋值
      public Builder name(String val){
          this.name = val;
          return this;
      }
      
     public Builder weight(int val){
          this.weight = val;
          return this;
      }
      
      // 最终通过无参构造函数生成实例对象
      public TestBuild build(){
          return new TestBuild(this);
      }
  }
  
  // 将赋值好的build对象入参实例类的构造方法中进行赋值
  private TestBuild(Builder builder){
      // 构造时集中校验
      if (builder.age < 0){
           throw new IllegalArgumentException();
      }
      if (builder.sex != 0){
           throw new IllegalArgumentException();
      }
      age = builder.age;
      name = builder.name;
      sex = builder.sex;
      weight = builer.weight;
  }
}

// 实例调用demo
TestBuild tb = new TestBuild.Builder(25,1).name("jack").weight(60).build();

建造者模式通过在类中设定一个新的静态类build,build类中围绕必传不可变参数生成构造函数,其余参数依旧按照setter的形式定义,build的整个过程不存在分步赋值的逻辑,最终将build作为入参统一的生成要返回的实例对象。

Spring看起来为什么简单

想到平时用的最多的Spring中,通常只是无参构造+getter/setter,那Spring帮我们做了什么事情来保证实例数值的一致性呢。

  • Spring 支持ORM框架对实体对象进行数据库数据的写入绑定,而绑定的过程会通过反射一次性将所有字段都进行赋值后才会返回创建实例对象,构造过程由框架控制,无需手动处理,这也是让人我忽略掉的过程。
  • Spring通过依赖注入机制管理Bean的依赖关系,确保Bean的创建和初始化过程由Spring容器统一控制,避免了手动管理Bean时可能出现的状态不一致问题‌。
  • Spring支持多种Bean作用域(如Singleton、Prototype、Request、Session等),确保在不同场景下Bean的生命周期和状态管理的一致性。
  • Spring提供了声明式事务管理,通过@Transactional注解或XML配置,确保数据库操作的一致性和原子性,避免了因事务未提交或回滚导致的数据不一致问题‌。
  • Spring通过三级缓存机制解决Bean之间的循环依赖问题,确保Bean在初始化过程中不会因为循环引用(A、B类互相引用)而导致状态不一致‌。
    • 一级缓存 存储已经实例化 赋值 初始化好的实例对象
    • 二级缓存 存储已经实例化 但还未赋值的实例对象
    • 三级缓存 实例还未创建 由spring生成代理对象先提供其他类获取

大致先了解这么几条。

番外篇

为什么在JavaBean中 无参构造函数是必须的呢

答:Spring是通过反射机制来对实例对象进行赋值的 而反射中获取对象实例的newInstance()方法就是对无参构造函数的调用

// JavaBean定义
public class User {
    private String name;
    
    public User() {} 
    
    public void setName(String name) { 
        this.name = name; 
    }
}

// 模拟Spring容器创建对象
public class SpringSimulator {
    public static void main(String[] args) throws Exception {
        // 步骤1:通过类全限定名获取Class对象
        Class<?> clazz = Class.forName("com.example.User");
        
        // 步骤2:调用newInstance()方法(依赖无参构造器)
        Object user = clazz.newInstance(); // 此时user对象已创建
        
        // 步骤3:通过反射调用setter方法注入属性
        Method setNameMethod = clazz.getMethod("setName", String.class);
        setNameMethod.invoke(user, "Alice");
        
        System.out.println(user); // 输出:User{name=Alice}
    }
}

但是回想平时的代码,一般情况下很少显式的书写无参构造函数,也不会报错,查了一番后可能是因为这只是javaBean的一种强制规范(可能会存在框架找不到显式无参构造而抛出异常的问题),而Spring中没有强制要求。

小结

小归小,妙归妙。
知道的不一定用得上,但是不知道的一定用不上。
还记得一开始接触框架的时候就明白它只是一种方便程序员的工具,但是渐渐地也让人们都蒙在了鼓里。