How to become a T – type talent, vertical in an industry, must have a setThe knowledge systemHere, just one word insist ~

preface

Design patterns are often used during development, and good architecture requires design patterns. The Factory design pattern is one of the creation patterns used when a class or interface has multiple subclasses and returns a specific subclass based on input. The factory design pattern is responsible for instantiation from the client to the factory class. In fact, we all understand the principle, then how to implement the factory class through APT technology? Next, direct coding, annotating theoretical knowledge, please move on to the details of annotating knowledge. Thank you ~

Contents

Define instantiated object annotations

ObjectFactory: This annotation identifies the subclass that needs to be added to the factory class. @Retention(retentionPolicy.class) : indicates compile-time annotations. @target (ElementType.TYPE): represents the modified class. The annotation parameter defines the type variable, which represents the factory Class object, and the key variable, which represents the identity of the annotation modifier Class in the factory Class.

@Retention(RetentionPolicy.CLASS) @Target(ElementType.TYPE) public @interface ObjectFactory { Class type(); // Parent name String key(); / / id}Copy the code

Custom annotation handlers

Different annotation types correspond to different annotation processors, which are mainly used for annotation parsing. To define compile-time annotations, we need to inherit AbstractProcessor to create an annotation processor and rewrite the parent class methods. In general, we just need to rewrite several important methods respectively:

Synchronized void init(ProcessingEnvironment) public Boolean process(Set<? extends TypeElement> set, RoundEnvironment RoundEnvironment) {3, annotated version public SourceVersion getSupportedSourceVersion (4), analytic type public Set < String > getSupportedAnnotationTypes()Copy the code

2.1. Annotation processor initialization

The AbstractProcessor annotation processor only calls the init(ProcessingEnvironment ProcessingEnvironment) method once per initialization, so it can be used to initialize utility classes.

@Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); / / annotation processor tool mProcessorUtils = ProcessorUtils getInstance (processingEnv); MGroupedClassesMap = new LinkedHashMap<>(); // Store different subclasses according to objectFactory.object () type. MGenerator = new ObjectFactoryGenerator(); }Copy the code

2.2. Bind annotations to annotation handlers

GetSupportedAnnotationTypes () method is the annotation processor can recognize annotation types. Here the annotation @ObjectFactory is stored in the LinkedHashSet data structure and the collection is returned to indicate that the annotation processor recognizes the target annotation object.

@Override
public Set<String> getSupportedAnnotationTypes() {
   LinkedHashSet<String> types = new LinkedHashSet<>(1);
   types.add(ObjectFactory.class.getCanonicalName());
    return types;
}
Copy the code

2.3 annotation parsing processing

The RoundEnvironment class provides a #getElementsAnnotatedWith() method that returns all information about the annotation type based on the annotation type. Element.getKind returns the annotation modifier type, converted to different Element subclasses depending on the type. #checkValidClass() checks whether an annotation is valid. Valid data is stored according to the annotation type, which identifies different factory classes. Once all the annotation information is parsed, it is stored in the LinkedHashMap collection and finally iterated through the collection data, looping through the # Generator method to generate boilerplate code.

@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { Set<? extends Element> factoryElements = roundEnvironment.getElementsAnnotatedWith(ObjectFactory.class); for (Element element : factoryElements) { ObjectFactoryClasses classes = null; try { ElementKind kind = element.getKind(); if (kind == ElementKind.CLASS) { classes = new ObjectFactoryClasses((TypeElement) element); if (! checkValidClass(classes)) { return true; / / check does not conform to the requirements, the error log output} ObjectFactoryGroupedClasses groupedClasses = mGroupedClassesMap. Get (classes) getQualifiedName ()); if (groupedClasses == null) { groupedClasses = new ObjectFactoryGroupedClasses(); mGroupedClassesMap.put(classes.getQualifiedName(), groupedClasses); } groupedClasses.addObjectFactoryClasses(classes); } } catch (IllegalArgumentException e) { mProcessorUtils.eLog(e.getMessage()); return true; } catch (IdAlreadyUsedException e) { ObjectFactoryClasses existing = e.getExisting(); assert classes ! = null; mProcessorUtils.eLog( "Conflict: The class %s is annotated with @%s with id ='%s' but %s already uses the same id", classes.getQualifiedName(), ObjectFactory.class.getSimpleName(), existing.getTypeElement().getQualifiedName().toString()); return true; }} try {// Generate template code for (String objects: mGroupedClassesMap.keySet()) { mGenerator.generator(objects, mGroupedClassesMap.get(objects), mProcessorUtils, processingEnv); } // MgroupedClassesmap.clear (); } catch (Exception e) { mProcessorUtils.eLog(e.getMessage()); } return true; }Copy the code

2.4. Annotation information verification

GetQualifiedName () checks whether the class is abstract and implements the same parent class. The common parent class name is obtained by reflection from the getQualifiedName() method. Parameter verification.

/** * type validation * @param classes * @return */ private Boolean checkValidClass(ObjectFactoryClasses) {TypeElement typeElement = classes.getTypeElement(); // Check the class qualifier if (! typeElement.getModifiers().contains(Modifier.PUBLIC)) { mProcessorUtils.eLog("The class $s not public", classes.getQualifiedName()); return false; } // Check if it is an ABSTRACT class if (typeElement.getmodifiers ().contains(Modifier abstract," + "You can't annotate abstract classes with @%s", classes.getQualifiedName(), ObjectFactory.class.getSimpleName()); return false; } // Check the inheritance relationship, Must be @ the ObjectFactory. Object () type subclass TypeElement superTypeElement = mProcessorUtils. ProcessorElementUtils (). getTypeElement(classes.getQualifiedName()); If (superTypeElement.getKind() == elementKind.interface) {if (! typeElement.getInterfaces().contains(superTypeElement.asType())) { mProcessorUtils.eLog("The class %s annotated with @%s  must implement the interface %s", typeElement.getQualifiedName().toString(), ObjectFactory.class.getSimpleName(), classes.getQualifiedName()); return false; } else { TypeElement currentClass = typeElement; while (true) { TypeMirror superclassType = currentClass.getSuperclass(); If (superclassType. GetKind () == typeKind.none) {// The base type is reached (java.lang.object), MProcessorUtils ("The class %s annotated with @%s must inherit from %s", typeElement.getQualifiedName().toString(), ObjectFactory.class.getSimpleName(), classes.getQualifiedName()); return false; } if (superclassType.toString().equals(classes.getQualifiedName())) { break; } currentClass = (TypeElement) mProcessorUtils.processorTypeUtils().asElement(superclassType); } } } for (Element element : typeElement.getEnclosedElements()) { if (element.getKind() == ElementKind.CONSTRUCTOR) { ExecutableElement constructorElement = (ExecutableElement) element; if (constructorElement.getParameters().size() == 0 && constructorElement.getModifiers().contains(Modifier.PUBLIC)) { return true; // mprocessorUtils. eLog("The class %s must provide an public empty default constructor", classes.getQualifiedName()); return false; }Copy the code

Parsing annotation information to generate boilerplate code

3.1. Parse annotation information

In above roundEnvironment. GetElementsAnnotatedWith () to get the @ ObjectFactory annotations TypeElement object, through TypeElement available annotations parameter information.

/** * @Author: WeiShuai * @Time: 2020/5/12 * @Description: Public class ObjectFactoryClasses {private TypeElement mTypeElement; private String mQualifiedName; private String mSimpleName; private String mKey; public ObjectFactoryClasses(TypeElement typeElement) { this.mTypeElement = typeElement; ObjectFactory factory = mTypeElement.getAnnotation(ObjectFactory.class); this.mKey = factory.key(); if ("".equals(mKey)) { throw new IllegalArgumentException( String.format("key() in @%s for class %s is null or empty! that's not allowed", ObjectFactory.class.getSimpleName(), mTypeElement.getQualifiedName().toString())); } try { Class<? > clazz = factory.type(); mQualifiedName = clazz.getCanonicalName(); mSimpleName = clazz.getSimpleName(); } catch (MirroredTypeException mte) { DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror(); TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement(); mQualifiedName = classTypeElement.getQualifiedName().toString(); mSimpleName = classTypeElement.getSimpleName().toString(); }} public String getQualifiedName() {return {qualifiedName ()} public String getQualifiedName() {return mQualifiedName; ** @return qualified name */ public String getSimpleName() {return ** @return qualified name */ public String getSimpleName() {return  mSimpleName; Public TypeElement getTypeElement() {return mTypeElement; } @return */ public String getKey() {return mKey; }}Copy the code

3.2 encapsulate annotation information

The annotation information is encapsulated by the key field of the annotation @ObjectFactory parameter, which represents the annotation modifier class identifier.

/** * @Author: WeiShuai * @Time: 2020/5/12 * @Description: Save TypeElements object information * / public class ObjectFactoryGroupedClasses {private LinkedHashMap < String, ObjectFactoryClasses> mObjectFactoryMaps = new LinkedHashMap<>(); /** * Save ObjectFactory */ public void addObjectFactoryClasses(ObjectFactoryClasses) throws IdAlreadyUsedException { ObjectFactoryClasses objectFactoryClasses = mObjectFactoryMaps.get(classes.getKey()); if (objectFactoryClasses ! = null) { throw new IdAlreadyUsedException(objectFactoryClasses); } mObjectFactoryMaps.put(classes.getKey(), classes); } /** * getObjectFactory ** @param key */ public ObjectFactoryClasses getObjectFactoryClasses {return mObjectFactoryMaps.get(key); } @return */ public LinkedHashMap<String, ObjectFactoryClasses> getObjectFactoryClassesMaps() { return mObjectFactoryMaps; }}Copy the code

3.3. Generate boilerplate code

public class ObjectFactoryGenerator { public void generator(String clazzType, ObjectFactoryGroupedClasses groupedClasses, ProcessorUtils processorUtils, ProcessingEnvironment processingEnv) { if ("".equals(clazzType)) { throw new NullPointerException( String.format("getQualifiedName() is null or empty! that's not allowed")); } TypeElement superTypeElement = processorUtils.processorElementUtils(). getTypeElement(clazzType); String classes = superTypeElement.getSimpleName().toString(); MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(Constant.METHOD_FACTORY) .addModifiers(Modifier.PUBLIC) .returns(TypeName.get(superTypeElement.asType())); / / according to FactoryObject. Key () to obtain object methodBuilder. AddParameter (String class, "id"); //LinkedHashMap<K,V>,K: Using annotations corresponding object information LinkedHashMap < String, ObjectFactoryClasses > classesMaps = groupedClasses. GetObjectFactoryClassesMaps (); TypeElement typeElement = null; for (String key : classesMaps.keySet()) { typeElement = classesMaps.get(key).getTypeElement(); methodBuilder.addStatement("if( \"" + key + "\".equals(id)){"); methodBuilder.addStatement("return new " + typeElement.getQualifiedName().toString() + "()"); methodBuilder.addStatement("}"); } methodBuilder.addStatement("return null"); if (typeElement ! = null) { processorUtils.writeToFile(classes + Constant.FACTORY_SUFFIX, processorUtils.getPackageName(typeElement), methodBuilder.build(), processingEnv, null); }}}Copy the code

4. Function point test

4.1. Test the code

  1. Create an abstract BaseFragment class as a common parent, inherit the parent BaseFragment class from HomeFragment and MineFragment classes, and implement the unimplemented methods of the parent class.
public abstract class BaseFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView = inflater.inflate(getLayoutId(), null);
        return rootView;
    }

    public abstract int getLayoutId();
}
Copy the code
  1. Create HomeFragment and MineFragment classes. The BaseFragment class must be implemented because the @objectFactory annotation verification rule is limited. The annotation parameters are defined as type=. Class,key=” class id “.
@ObjectFactory(type = BaseFragment.class, key = "Home") public class HomeFragment extends BaseFragment { @Override public int getLayoutId() { return R.layout.fragment_home; }}Copy the code
@ObjectFactory(type = BaseFragment.class, key = "Mine") public class MineFragment extends BaseFragment { @Override public int getLayoutId() { return R.layout.fragment_mine; }}Copy the code

4.2 boilerplate code

Annotation information is specified by parsing, classes, methods, fields are built using APT techniques, and Java files are generated.

public final class BaseFragment$Factory { public BaseFragment objectFactory(String id) { if( "Home".equals(id)){; return new com.linwei.annotation.compile.HomeFragment(); }; if( "Mine".equals(id)){; return new com.linwei.annotation.compile.MineFragment(); }; return null; }}Copy the code

About me