@NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor(搭配@FieldDefaults、@NonFinal)的区别以及在springboot常用地方

注解解释、区别:

@NoArgsConstructor:生成无参的构造方法。
@AllArgsConstructor:生成该类下全部属性的构造方法。(主讲)
@RequiredArgsConstructor:生成该类下被final修饰或者带有@non-null的构造方法。(主讲)

代码解析区别:

1. @AllArgsConstructor

@AllArgsConstructor
public class Student{

    private String name;

    // 被final修饰
    private final String age;

    @NonNull
    private String sex;
}

根据反编译查看代码(反编译工具jad的使用可点击此处查看):

public class Student
{

    public Student(String name, String age, String sex)
    {
        if(sex == null)
        {
            throw new NullPointerException("sex is marked non-null but is null");
        } else
        {
            this.name = name;
            this.age = age;
            this.sex = sex;
            return;
        }
    }

    private String name;
    private final String age;
    private String sex;
}

结果:默认只要是该类下的字段,无论什么修饰,都会被参与构造。

2. @RequiredArgsConstructor

@RequiredArgsConstructor
public class Student
{
    private String name;

    // 被final修饰
    private final String age;

    @NonNull
    private String sex;
}

根据反编译查看代码:

public class Student
{

    public Student(String age, String sex)
    {
        if(sex == null)
        {
            throw new NullPointerException("sex is marked non-null but is null");
        } else
        {
            this.age = age;
            this.sex = sex;
            return;
        }
    }

    private String name;
    private final String age;
    private String sex;
}

结果:只构造了有final或者@no-null修饰的字段。

场景:

在springboot中,对于一个bean类,注入其他bean的时候,常见的是使用@Autowired,实际上也可以使用构造函数注入,这个时候就可以使用@AllArgsConstructor或者@RequiredArgsConstructor来代替。

场景使用—代替@Autowired注入bean对象:

1. @AllArgsConstructor

@Component
public class BeanTest1
{
}

@Component
public class BeanTest2
{
}

@Component
public class BeanTest3
{
}

//Controller层
@RestController
@ToString
@AllArgsConstructor
public class BeanTestController
{
    // 注入三个bean对象,完全没有使用Autowired注解
    private BeanTest1 beanTest1;

    @NonNull
    private BeanTest2 beanTest2;

    private final BeanTest3 beanTest3;
}

//测试类
@Component
public class ConstructorRunner implements ApplicationRunner
{

    @Autowired
    BeanTestController beanTestController;

    @Override
    public void run(ApplicationArguments args) throws Exception
    {
        System.out.println(beanTestController);
    }
}

结果:完完全全可以注入bean对象。

2. @RequiredArgsConstructor

//Controller层,换成@RequiredArgsConstructor
@RestController
@ToString
@RequiredArgsConstructor
public class BeanTestController
{
    // 注入三个bean对象,完全没有使用Autowired注解
    private BeanTest1 beanTest1;

    @NonNull
    private BeanTest2 beanTest2;

    private final BeanTest3 beanTest3;
}


结果:由图可以看出,没有被修饰final或者@no-null的属性无法被注入,因此,建议使用@RequiredArgsConstructor的时候,对需要的字段加上final修饰。
注:强调对需要的字段,为什么要强调,请看下面的例子:

3. @RequiredArgsConstructor 与 @AllArgsConstructor 在注入bean上的区别

根据上面两个例子,我们可以看出无论是那种方法都可以注入bean属性对象,只是@RequiredArgsConstructor 是针对有条件的,没有什么区别。

但如果是下面的需求呢:
在该类下,部分字段还需要使用@Value来注入值呢?

使用@AllArgsConstructor

@RestController
@ToString
@AllArgsConstructor
public class BeanTestController
{
    // 注入三个bean对象,完全没有使用Autowired注解
    private BeanTest1 beanTest1;

    @NonNull
    private BeanTest2 beanTest2;

    private final BeanTest3 beanTest3;

    @Value("${constructor.name:hello}")
    private String name;
}

出现以下报错:

使用@RequiredArgsConstructor

//Controller层,换成@RequiredArgsConstructor
@RestController
@ToString
@RequiredArgsConstructor
public class BeanTestController
{
    // 注入三个bean对象,完全没有使用Autowired注解
    private BeanTest1 beanTest1;

    @NonNull
    private BeanTest2 beanTest2;

    private final BeanTest3 beanTest3;

    @Value("${constructor.name:hello}")
    private String name;
}

结论:
一个bean如果使用构造函数进行bean属性注入,那么当然构造函数不能加上name。
因为加上,在创建ConstructorDemo该bean的时候,需要找类型为String,名字是name的bean对象,当然是不存在,必然会报错。
因此,当然不能使用@AllArgsConstructor了,只能使用@RequiredArgsConstructor

总结:

上面只是举例了代替@Autowired的例子,实际上在json转化为对象,以及在spring中从配置文件读取配置使用@ConfigurationProperties以及@ConstructorBinding的时候,都可以使用构造函数赋值,都可以用到上面的两个@AllArgsConstructor、@RequiredArgsConstructor。
具体:只要记得,那些字段需要赋值,就把它列进构造方法的参数里面即可。

拓展:

@RequiredArgsConstructor搭配@FieldDefaults、@NonFinal。

注解解释:
@FieldDefaults:可以为被注解的类或枚举中的每个字段添加访问修饰符(public, private, 或 protected)。它还可以为注释的类或枚举中的每个字段添加final。
参数介绍:
添加访问修饰符:level=AccessLevel.PRIVATE(其余可填写PUBLIC,MODULE,PROTECTED,PACKAGE,PRIVATE,NONE),默认是NONE
添加final:makeFinal=true(默认是false关闭)。
注意:任何必须保持非final的字段可以用@NonFinal(也在lombok.experimental包中)来注释。

@NonFinal:作用于类、变量,表示变量不加 final。

@RequiredArgsConstructor和@FieldDefaults搭配使用

@RestController
@ToString
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true)
public class BeanTestController
{
    private BeanTest1 beanTest1;

    private BeanTest2 beanTest2;

    private BeanTest3 beanTest3;
}


反编译后代码,可以看出都加了final:

public class BeanTestController
{

    public String toString()
    {
        return (new StringBuilder()).append("BeanTestController(beanTest1=").append(beanTest1).append(", beanTest2=").append(beanTest2).append(", beanTest3=").append(beanTest3).append(")").toString();
    }

    public BeanTestController(BeanTest1 beanTest1, BeanTest2 beanTest2, BeanTest3 beanTest3)
    {
        this.beanTest1 = beanTest1;
        this.beanTest2 = beanTest2;
        this.beanTest3 = beanTest3;
    }

    private final BeanTest1 beanTest1;
    private final BeanTest2 beanTest2;
    private final BeanTest3 beanTest3;
}

结果:注入成功

通过上面可以看出,@FieldDefaults(makeFinal = true)会为每个字段添加final,然后@RequiredArgsConstructor注入,但是如果在该类下,部分字段还需要使用@Value来注入值呢?
代码:

@RestController
@ToString
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true)
public class BeanTestController
{
    private BeanTest1 beanTest1;

    private BeanTest2 beanTest2;

    private BeanTest3 beanTest3;

    @Value("${constructor.name:hello}")
    private String name;
}

反编译后代码:

public class BeanTestController
{

    public String toString()
    {
        return (new StringBuilder()).append("BeanTestController(beanTest1=").append(beanTest1).append(", beanTest2=").append(beanTest2).append(", beanTest3=").append(beanTest3).append(", name=").append(name).append(")").toString();
    }

    public BeanTestController(BeanTest1 beanTest1, BeanTest2 beanTest2, BeanTest3 beanTest3, String name)
    {
        this.beanTest1 = beanTest1;
        this.beanTest2 = beanTest2;
        this.beanTest3 = beanTest3;
        this.name = name;
    }

    private final BeanTest1 beanTest1;
    private final BeanTest2 beanTest2;
    private final BeanTest3 beanTest3;
    private final String name;
}


结果:注入失败,出现报错,
结论:
一个bean如果使用构造函数进行bean属性注入,那么当然构造函数不能加上name。
因为加上,在创建ConstructorDemo该bean的时候,需要找类型为String,名字是name的bean对象,当然是不存在,必然会报错。
因此,要用@NonFinal,不让使用@value注入的值被创建ConstructorDemo该bean时使用

@RequiredArgsConstructor和@FieldDefaults、@NonFinal搭配使用

代码:

@RestController
@ToString
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true)
public class BeanTestController
{
    private BeanTest1 beanTest1;

    private BeanTest2 beanTest2;

    private BeanTest3 beanTest3;

    @NonFinal
    @Value("${constructor.name:hello}")
    private String name;
}


反编译后代码:

public class BeanTestController
{

    public String toString()
    {
        return (new StringBuilder()).append("BeanTestController(beanTest1=").append(beanTest1).append(", beanTest2=").append(beanTest2).append(", beanTest3=").append(beanTest3).append(", name=").append(name).append(")").toString();
    }

    public BeanTestController(BeanTest1 beanTest1, BeanTest2 beanTest2, BeanTest3 beanTest3)
    {
        this.beanTest1 = beanTest1;
        this.beanTest2 = beanTest2;
        this.beanTest3 = beanTest3;
    }

    private final BeanTest1 beanTest1;
    private final BeanTest2 beanTest2;
    private final BeanTest3 beanTest3;
    private String name;
}

结果:注入成功

引用:
https://www.ab62.cn/article/11041.html
https://blog.csdn.net/qq_31635851/article/details/122123278

posted @ 2022-09-09 11:39  chandol  阅读(2390)  评论(0编辑  收藏  举报