一行注解优化服务层百行@Autowire代码
点击公众关注号,“技术干货”及时达!
@Autowire可以说是我们日常开发中使用最频繁的一个注解了, 相信你在日常开发中一定看到过如下类似的代码:
publicclassUserService{
@Autowire
privatexxxMapperxxxMapper;
@Autowire
privatexxx1Mapperxxx1Mapper;
@Autowire
privatexxxServicexxxService;
//...此处省略更多的@Autowire注解的使用
}
事实上,在实际的业务中有时需要注入的属性能达到十几个之多处,也就是说@Autowire在一个类中会频繁的重复出现。
那有没一种方式能让我们不用每次都重复写@Autowire的方式呢?肯定是有的,答案就是今天我们讨论的@RequiredArgsConstructor。
什么是@RequiredArgsConstructor@RequiredArgsConstructor 是 Lombok 提供的注解之一,其主要用于在类上,并为类中标有 final 字段的生成相应的构造方法。具体使用如下所示:
@RequiredArgsConstructor
publicclassExamplePo{
privatefinalStringname;
privatefinalintage
privateStringnameCode;
publicstaticvoidmain(String[]args){
ExamplePoexamplePo=newExamplePo("name",12);
}
}
在上述代码中,在Main方法中使用@RequiredArgsConstructor生成了一个构造方法以供我们构建实体ExamplePo。
细心的读者可能已经注意到了在ExamplePo中定义了name、age、nameCode三个字段,但是当我们在Main方法中构建ExamplePo实体对象时,由于nameCode并未使用final修饰,所以仅能根据name和age两个属性来完成构建。
众所周知,final 修饰的字段在声明时必须进行初始化,且一旦被初始化后其值就不能再被修改。
因此为了保证字段的不变性,@RequiredArgsConstructor 会自动生成构造方法时,会为检查类内部被 final 字段修饰的全部变量,并在构造方法中进行初始化。
@RequiredArgsConstructor这样做不仅确保了对象在创建时所有 final 字段都得到正确的初始化,而且一旦初始化后,它们的值不可变。
剖析@RequiredArgsConstructor原理为了能透彻剖析Lombok中@RequiredArgsConstructor 的逻辑,这里我们借助注解处理器(Annotation Processor)来模拟实现对在编译阶段生成和修改字节码,以对Lombok中的@RequiredArgsConstructor的解析进行模拟。
为此,我们首先自定义一个MyRequiredArgsConstructor注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public@interfaceMyRequiredArgsConstructor{
booleanincludeAllFields()defaultfalse;
}
然后,我们编写一个MyRequiredArgsConstructorProcessor来对我们自定义的MyRequiredArgsConstructor进行解析。其逻辑如下:
//...省略包信息导入
@SupportedAnnotationTypes("com.example.annotation.MyRequiredArgsConstructor")
publicclassMyRequiredArgsConstructorProcessorextendsAbstractProcessor{
@Override
publicbooleanprocess(Set?extendsTypeElementannotations,RoundEnvironmentroundEnv){
1遍历Class文件,判断其是否有MyRequiredArgsConstructor注解
for(Elementelement:roundEnv.getElementsAnnotatedWith(MyRequiredArgsConstructor.class)){
if(elementinstanceofTypeElement){
TypeElementtypeElement=(TypeElement)element;
2收集MyRequiredArgsConstructor注解所修饰类中字段信息
ListVariableElementfinalFields=newArrayList();
ListFieldSpecfields=newArrayList();
for(ElementenclosedElement:typeElement.getEnclosedElements()){
if(enclosedElement.getKind()==ElementKind.FIELD){
VariableElementfield=(VariableElement)enclosedElement;
finalFields.add(field);
FieldSpecfieldSpec=FieldSpec.builder(TypeName.get(field.asType()),field.getSimpleName().toString(),Modifier.PRIVATE,Modifier.FINAL)
.build();
fields.add(fieldSpec);
}
}
MethodSpec.BuilderconstructorBuilder=MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC);
//3遍历属性内容,生成构造方法
for(VariableElementfield:finalFields){
constructorBuilder.addParameter(TypeName.get(field.asType()),field.getSimpleName().toString());
constructorBuilder.addStatement("this.$N=$N",field.getSimpleName(),field.getSimpleName());
}
MethodSpecconstructor=constructorBuilder.build();
//4进行class文件的回写
TypeSpecclassWithConstructor=TypeSpec.classBuilder(String.valueOf(typeElement.getSimpleName()))
.addModifiers(Modifier.PUBLIC)
.addMethod(constructor)
.addFields(fields)
.build();
JavaFilejavaFile=JavaFile.builder("com.example.pojo",classWithConstructor)
.build();
try{
JavaFileObjectsourceFile=processingEnv.getFiler().createSourceFile("com.example.pojo."+typeElement.getSimpleName()+"WithConstructor");
Writerwriter=sourceFile.openWriter();
javaFile.writeTo(writer);
writer.close();
}catch(IOExceptione){
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,e.toString());
}
}
}
returntrue;
}
}
上述代码的process大致逻辑如下,其首先会对加载类进行逐个遍历,从而找出带有MyRequiredArgsConstructor注解的Class文件。
然后,对目标类文件中的字段信息进行逐个遍历,收集其所有的field字段信息,并存储到finalFields列表中。
最后,依据扫描得到的字段信息创建一个公共的构造函数,构造函数的入参即为类中的所有字段,并在构造函数中将参数赋值给对应的字段。
更进一步,我们将MyRequiredArgsConstructor标注在MyClass上,然后手动调用我们MyRequiredArgsConstructorProcessor进行编译其结果如下图所示。
(注:不熟悉MyRequiredArgsConstructor如何调用的可参考笔者之前的:Java注解能力提升:教你解析保留策略为源码阶段的注解)
@MyRequiredArgsConstructor
@SuppressWarnings("unused")
publicclassMyClass{
privateStringname;
privateint
privateStringaddress;
privateStringoptionalField;
}
image.png可以看到,我们自定义的MyRequiredArgsConstructorProcessor成功的对标有MyRequiredArgsConstructor类进行了解析,并生为其成了相关的构造器。
至此,我们便通过Jdk提供给我们的AbstractProcessor接口成功地对Lombk中的@RequiredArgsConstructor的解析原理进行了模拟。
(注:Lombok内部在实现对注解解析时其实也是通过继承AbstractProcessor接口,来完成对相关LomBok注解的解析处理,感兴趣的读者可自行对Lombok中的LombokProcessor进行分析)
明白了@RequiredArgsConstructor的用途以及工作原理后,接下来我们便来用@RequiredArgsConstructor来改造我们的@Autowire。
使用@RequiredArgsConstructor减少@Autowire的书写正如我们前面介绍的那样,如果类上加上@RequiredArgsConstructor,那么需要注入的类所需的的关键字段需要通过final声明进行修饰。 为此我们对开头出现的UserService进行改造,具体如下所示:
@Service
@RequiredArgsConstructor(onConstructor_={@Lazy,@Autowired})
publicclassUserService{
privatefinalxxxMapperxxxMapper;
privatefinalxxx1Mapperxxx1Mapper;
privatefinalxxxServicexxxService;
//...此处省略更多的@Autowire注解的使用
}
到在上述代码中,我们在用到了@RequiredArgsConstructor 注解的onConstructor_ 属性,该属性允许你为生成的构造函数添加额外的注解。
这个属性是一个注解数组,使你可以为Lombok 自动生成的构造函数添加多个注解。因此上述代码最终生成的代码造类似于:
publicclassUserService{
privatefinalxxxMapperxxxMapper;
privatefinalxxx1Mapperxxx1Mapper;
privatefinalxxxServicexxxService;
//...此处省略更多的@Autowire注解的使用
@Autowire
@Lazy
publicUserService(xxxMapperxxxMapper,xxx1Mapperxxx1Mapper,
xxxServicexxxService){
//....省略属性注入
}
}
笔者在使用@RequiredArgsConstructor的onConstructor_属性中除了使用@Autowire外,还使用了@Lazy注解,这主要目的是为了解决因循环依赖的发生。
因为当 Spring容器遇到 @Lazy 注解时,它不会立即创建该 bean的实例,而是创建一个代理对象。只有当该代理对象第一次被访问时,Spring容器才会创建真正的 bean实例并进行注入。
总结至此,我们就对Lombok中的@RequiredArgsConstructor的原理和使用场景进行了详细的介绍,结合@RequiredArgsConstructor我们完全可以对服务层频繁出现的@Autowire注解进行优化。
点击公众关注号,“技术干货”及时达!
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线