最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java注解處理器實(shí)戰(zhàn)(附詳細(xì)示例代碼)

 更新時(shí)間:2025年10月10日 08:39:22   投稿:daisy  
這篇文章主要介紹了Java注解處理器實(shí)戰(zhàn)的相關(guān)資料,通過(guò)編譯時(shí)APT生成代碼、運(yùn)行時(shí)反射實(shí)現(xiàn)視圖注入,同時(shí)指導(dǎo)如何開(kāi)發(fā)自定義注解處理器,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下

注解處理器

注解強(qiáng)大的地方在于: 我們可以在運(yùn)行時(shí)或者編譯時(shí)處理注解. 在編譯時(shí)或者運(yùn)行時(shí)處理注解的機(jī)制都可以稱(chēng)為一個(gè)注解處理器.

注解處理器的類(lèi)型

注解處理器的類(lèi)型分為兩大類(lèi):

  • 運(yùn)行時(shí)處理器: 這種機(jī)制是在程序運(yùn)行時(shí)利用反射機(jī)制去處理注解.
  • 編譯時(shí)處理器: 這種機(jī)制是在程序編譯時(shí)利用javac提供的一個(gè)apt工具來(lái)處理注解.

運(yùn)行時(shí)處理器的特點(diǎn): 該機(jī)制基于反射機(jī)制, 因此靈活性很大, 但是卻相對(duì)耗性能. 編譯時(shí)處理器的特點(diǎn):這種方法是在編譯時(shí)處理注解, 因此不存在性能的問(wèn)題, 但是靈活性也相對(duì)比較低.

在已有的開(kāi)源庫(kù)實(shí)現(xiàn)中, 普遍是結(jié)合編譯時(shí)處理器和反射機(jī)制這兩種方法. 舉個(gè)例子: ButterKnife. 它是一個(gè)View注入的庫(kù). 內(nèi)部實(shí)現(xiàn)的原理: 編譯時(shí)利用apt生成findViewById等一些列模板代碼, 然后在運(yùn)行時(shí)利用反射去實(shí)例生成的模板代碼類(lèi), 這樣完成了一次注入. 從這里可以看出, ButterKnife并沒(méi)有完全基于編譯時(shí)注解處理器. 而是加了反射.

這樣做的好處: 開(kāi)發(fā)者不需要手動(dòng)實(shí)例apt生成的類(lèi). 換句話(huà)說(shuō): 開(kāi)發(fā)者不需要了解生成類(lèi)的命令規(guī)則. 也許你可能會(huì)覺(jué)得利用反射會(huì)耗性能. 但是仔細(xì)想想, 如果沒(méi)有利用反射的話(huà), 開(kāi)發(fā)者都需要手動(dòng)編譯, 然后再實(shí)例化生成的類(lèi). 這個(gè)過(guò)程也是挺繁瑣的. 而且, 庫(kù)內(nèi)部是有做相應(yīng)的緩存的, 所以耗的性能還是相對(duì)比較低的.

對(duì)于反射的利用, 應(yīng)該適當(dāng)使用, 而不是避而不用或者濫用.

接下來(lái), 講講APT的一些知識(shí), 最后再仿寫(xiě)一個(gè)簡(jiǎn)單的ButterKnife作為實(shí)戰(zhàn).

AbstractProcessor

開(kāi)發(fā)注解處理器的第一個(gè)步驟就是繼承AbstractProcessor. 然后重寫(xiě)其四個(gè)方法:

  • public synchronized void init(ProcessingEnvironment processingEnvironment): 這個(gè)方法一般是做一些初始化的工作.
  • public SourceVersion getSupportedSourceVersion(): 該處理器所支持的JDK版本, 一般是支持到最新: SourceVersion.latestSupported().
  • public Set<String> getSupportedAnnotationTypes(): 你所要處理的注解的類(lèi)型.
  • public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment): 處理注解的方法. 這個(gè)是我們主要實(shí)現(xiàn)的方法. 返回值表示當(dāng)前這個(gè)注解類(lèi)型是否需要被隨后的注解處理器處理. true表示不需要, false表示后面的注解處理器可能會(huì)對(duì)本次處理的注解.

認(rèn)識(shí)這4個(gè)方法后, 我們還需要熟悉一下編寫(xiě)處理器涉及到的一些概念和API.

ProcessingEnvironment

ProcessingEnvironment是在init方法傳入的. 它表示APT框架的一個(gè)處理時(shí)上下文. 這個(gè)類(lèi)主要是提供一些工具類(lèi). 詳細(xì)看下面代碼注釋:

public interface ProcessingEnvironment {
    //獲取外部配置的參數(shù)
    Map<String,String> getOptions();
    //獲取Messager對(duì)象, 用于打印一些日志
    Messager getMessager();
    //獲取Filer對(duì)象, 該對(duì)象用于創(chuàng)建文件
    Filer getFiler();
    //獲取Elements, 它包含處理Element的一些工具方法
    Elements getElementUtils();
    //獲取Types, 它包含處理TypeMirror的一些工具方法
    Types getTypeUtils();
    SourceVersion getSourceVersion();
    Locale getLocale();
}

RoundEnvironment

當(dāng)APT啟動(dòng)時(shí), 它將會(huì)掃描源文件, 然后進(jìn)入process方法處理, 如果這個(gè)過(guò)程生成了新的文件的話(huà), 新文件會(huì)被APT再次作為輸入, 接著process會(huì)被apt再次調(diào)用, 如此循環(huán)下去, 直到?jīng)]有新的文件產(chǎn)生. RoundEnvironment為處理輪次的上下文環(huán)境. 下面是RoundEnvironment的方法簡(jiǎn)單介紹:

public interface RoundEnvironment {
    //用于判斷是否處理完了, 只有當(dāng)沒(méi)有新文件生成時(shí), 證明已經(jīng)處理完了, 才會(huì)返回true
    boolean processingOver();
    //上一次處理是否存在錯(cuò)誤
    boolean errorRaised();
    //獲取前一次處理的根元素集合
    Set<? extends Element> getRootElements();
    //獲取被指定的類(lèi)型元素標(biāo)注的元素集
    Set<? extends Element> getElementsAnnotatedWith(TypeElement a);
    //獲取被指定的注解標(biāo)注的元素集
    Set<? extends Element> getElementsAnnotatedWith(Class<? extends Annotation> a);
}

Element

Element代表Java靜態(tài)語(yǔ)言結(jié)構(gòu)的元素. 這樣聽(tīng)起來(lái)可能有點(diǎn)難以理解, 不過(guò)看下面的例子就會(huì)很好理解了:

package com.desperado.processors; //PackageElement
public class Test { //TypeElement: 代表類(lèi)或接口元素
    private int xixi; //VariableElement:
    public Test() { //ExecutableElement
    }
    public void haha(String arg) { //haha方法為T(mén)ExecutableElement
        int v; //VariableElement
    }
}
  • TypeElement: 代表類(lèi)或接口元素
  • VariableElement: 代表字段, 枚舉常量, 方法或構(gòu)造函數(shù)的參數(shù), 局部變量, 資源變量和異常參數(shù).
  • PackageElement: 代表包元素
  • ExecutableElement: 代表的方法, 構(gòu)造方法,和接口或者類(lèi)的初始化塊.

DeclaredType, TypeElement和TypeMirror

TypeElement表示類(lèi)或接口元素, 可以從中獲取類(lèi)名, 但是不能獲得類(lèi)本身的信息, 比如父類(lèi).

TypeMirror: 表示Java語(yǔ)言中的類(lèi)型, 其中包括: 基本類(lèi)型, 聲明類(lèi)型(類(lèi)類(lèi)型和接口類(lèi)型), 數(shù)組類(lèi)型, 類(lèi)型變量和空類(lèi)型. 也代表通配類(lèi)型參數(shù),可執(zhí)行文件的簽名和返回類(lèi)型.

DeclaredType: 代表聲明的類(lèi)型, 類(lèi)類(lèi)型還是接口類(lèi)型,當(dāng)然也包括參數(shù)化類(lèi)型,比如Set,也包括原始類(lèi)型.

開(kāi)發(fā)一個(gè)注解處理器

掌握了上面的概念后, 我們開(kāi)始來(lái)開(kāi)發(fā)一個(gè)仿ButterKnife的注解處理器.

步驟

  • 繼承AbstractProcessor并重寫(xiě)其中的四個(gè)方法.
  • 注冊(cè)注解處理器.

繼承AbstractProcessor

@AutoService(Processor.class)
public class ViewBindingProcessor extends AbstractProcessor {
    private Types types;
    private Filer filer;
    private Messager messager;
    private Elements elements;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        types = processingEnvironment.getTypeUtils();
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
        elements = processingEnvironment.getElementUtils();
    }
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //process
        return true;
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        return annotations;
    }
}

在getSupportedAnnotationTypes()中, 我們直接返回了需要處理的注解的全限定名稱(chēng)集合.

注冊(cè)處理器

寫(xiě)好的處理器還需要處理告訴Javac, 讓它編譯時(shí)運(yùn)行我們的處理器. 注冊(cè)的步驟為: 在項(xiàng)目新建一個(gè)resources目錄, 然后在resources內(nèi)新建這樣的目錄結(jié)構(gòu): /META-INF/services/. 最后在services中新建文件: javax.annotation.processing.Processor. 并且在文件上填寫(xiě)你的處理器的全路徑名稱(chēng). 例如: com.desperado.processors.ViewBindingProcessor.

其實(shí)還有一種更為簡(jiǎn)單的方法. 添加com.google.auto.service:auto-service:1.0-rc2這個(gè)庫(kù)(Google開(kāi)源的). 然后在你的處理器上添加注解: @AutoService(Processor.class). 這樣, 編譯時(shí)就會(huì)自動(dòng)幫我們生成注冊(cè)文件.

如何組織處理器結(jié)構(gòu)

為了不將注解處理器的代碼打包進(jìn)APK. 我們需要將注解和注解處理器分開(kāi). 具體的組織如下:

  • annotations: annotations是一個(gè)java lib. 是我們自定義的注解
  • processors: processors也是一個(gè)java lib. 是我們的注解處理器
  • viewbinding: 為提供為app的api.

annotations

首先我們?cè)赼nnotation模塊定義一個(gè)注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value() default -1;
}

processors

在講處理器模塊前, 我們先看看期望生成的代碼模板:

public class MainActivity$ViewBinding {
  public MainActivity$ViewBinding(MainActivity activity, View view) {
    if (activity == null) {
      return;
    }
    activity.mTvTextView = view.findViewById(2131165249);
  }
}

生成的代碼的類(lèi)名為: XXXX$ViewBinding

然后在構(gòu)造函數(shù)中對(duì)傳入的activity中的View進(jìn)行賦值.

定義處理規(guī)則.

  • BindView只能標(biāo)注字段.
  • BindView不能標(biāo)注接口中的字段, 只能標(biāo)注類(lèi)中的字段.
  • BindView不能標(biāo)注抽象類(lèi)中的字段.
  • BindView不能標(biāo)注被private, static或者final修飾的字段
  • BindView標(biāo)注字段所屬的類(lèi)必須是Activity的子類(lèi).
  • BindView標(biāo)注的字段必須是View的子類(lèi)

定義好這些規(guī)則后, 我們就可以來(lái)看處理器的代碼了.

處理過(guò)程

@AutoService(Processor.class)
public class ViewBindingProcessor extends AbstractProcessor {
    private Types types;
    private Filer filer;
    private Messager messager;
    private Elements elements;
    private Map<String, ViewClass> map = new HashMap<>();
    private static final String TYPE_ACTIVITY = "android.app.Activity";
    private static final String TYPE_VIEW = "android.view.View";
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        types = processingEnvironment.getTypeUtils();
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
        elements = processingEnvironment.getElementUtils();
    }
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        map.clear(); //該方法會(huì)被調(diào)用多次, 所以每次進(jìn)入時(shí), 首先清空之前的臟數(shù)據(jù)
        logNote("start process");
        for (Element e : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
            if (!isValid(e)) { //首先判斷被標(biāo)注的元素是否符合我們上面定的規(guī)則
                return true; //出現(xiàn)錯(cuò)誤, 停止編譯
            }
            logNote("start parse annotations");
            performParseAnnotations(e); //開(kāi)始解析注解
        }
        logNote("start generate code");
        generateCode(); //生成代碼
        return true;
    }
    private boolean isValid(Element element) {
        if (!(element instanceof VariableElement)) {
            logError("BindView只能標(biāo)注字段");
            return false;
        }
        VariableElement variableElement = (VariableElement) element;
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        if (typeElement.getKind() != ElementKind.CLASS) {
            logError("只能標(biāo)注類(lèi)中的字段");
            return false;
        }
        if (typeElement.getModifiers().contains(Modifier.ABSTRACT)) {
            logError("不能標(biāo)注抽象類(lèi)中的字段");
            return false;
        }
        for (Modifier modifier : element.getModifiers()) {
            if (modifier == Modifier.PRIVATE || modifier == Modifier.STATIC ||
                    modifier == Modifier.FINAL) {
                logError("BindView不能標(biāo)注被static," +
                        "private或者final的字段");
                return false;
            }
        }
        if (!isSubtype(typeElement.asType(), TYPE_ACTIVITY)) { //判斷被標(biāo)注的字段的類(lèi)是不是Activity的子類(lèi)
            logError(typeElement.getSimpleName() + "必須是 Activity的子類(lèi)");
            return false;
        }
        if (!isSubtype(variableElement.asType(), TYPE_VIEW)) { //判斷被標(biāo)注的字段是不是View的子類(lèi)
            logError("BindView只能標(biāo)注View的子類(lèi)");
            return false;
        }
        return true;
    }
    private boolean isSubtype(TypeMirror tm, String type) {
        boolean isSubType = false;
        while (tm != null) { //循環(huán)獲取父類(lèi)信息
            if (type.equals(tm.toString())) { //通過(guò)全路徑是否相等
                isSubType = true;
                break;
            }
            TypeElement superTypeElem = (TypeElement) types.asElement(tm);
            if (superTypeElem != null) {
                tm = superTypeElem.getSuperclass();
            } else { //如果為空, 說(shuō)明沒(méi)了父類(lèi), 所以直接退出
                break;
            }
        }
        return isSubType;
    }
    private void performParseAnnotations(Element element) {
        VariableElement variableElement = (VariableElement) element;
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        String className = typeElement.getSimpleName().toString();
        ViewClass viewClass = map.get(className);
        ViewField field = new ViewField(variableElement);
        if (viewClass == null) {
            viewClass = new ViewClass(elements.getPackageOf(variableElement).getQualifiedName().toString(),
                    className, typeElement);
            map.put(className, viewClass);
        }
        viewClass.addField(field);
    }
    private void generateCode() {
        for (ViewClass vc : map.values()) {
            try {
                vc.generateCode().writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
                logError("error in parse");
            }
        }
    }
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        return annotations;
    }
    private void logError(String msg) {
        log(Diagnostic.Kind.ERROR, msg);
    }
    private void logNote(String msg) {
        log(Diagnostic.Kind.NOTE, msg);
    }
    private void log(Diagnostic.Kind kind, String msg) {
        messager.printMessage(kind, msg);
    }
}

為了使代碼結(jié)構(gòu)更為清晰, 這里將注解標(biāo)注的字段的信息抽象為ViewField類(lèi), 字段所屬的類(lèi)的信息抽象為ViewClass.

public class ViewField {
    private String fieldName;
    private int id;
    public ViewField(VariableElement variableElement) {
        id = variableElement.getAnnotation(BindView.class).value();
        fieldName = variableElement.getSimpleName().toString();
    }
    public String getFieldName() {
        return fieldName;
    }
    public int getId() {
        return id;
    }
}
public class ViewClass {
    private String packName;
    private String className;
    private List<ViewField> fields = new ArrayList<>();
    private TypeElement typeElement;
    public ViewClass(String packName, String className, TypeElement typeElement) {
        this.packName = packName;
        this.className = className;
        this.typeElement = typeElement;
    }
    public void addField(ViewField viewField) {
        fields.add(viewField);
    }
    public JavaFile generateCode() { //為了方便, 這里生成代碼用了JavaPoet庫(kù)來(lái)生成代碼
        MethodSpec.Builder con = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(TypeName.get(typeElement.asType()), "activity")
                .addParameter(ClassName.get("android.view", "View"), "view")
                .beginControlFlow("if (activity == null)")
                .addStatement("return")
                .endControlFlow();
        for (ViewField f : fields) {
            con.addStatement("activity.$N = view.findViewById($L)", f.getFieldName(), f.getId());
        }
        FieldSpec.Builder fid = FieldSpec.builder(TypeName.get(typeElement.asType()), "activity");
        TypeSpec typeSpec = TypeSpec.classBuilder(className + "$ViewBinding")
                .addModifiers(Modifier.PUBLIC)
                .addField(fid.build())
                .addMethod(con.build())
                .build();
        return JavaFile.builder(packName, typeSpec).build();
    }
}

處理過(guò)程其實(shí)并不復(fù)雜, 只是需要我們熟悉API而已, 因此, 接下來(lái)總結(jié)一下獲取一些常用信息的方法:

常用的api

public class ViewBindingProcessor extends AbstractProcessor {
    private Types types;
    private Filer filer;
    private Messager messager;
    private Elements elements;
    private static final String TYPE_ACTIVITY = "android.app.Activity";
    private static final String TYPE_VIEW = "android.view.View";
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        types = processingEnvironment.getTypeUtils();
        filer = processingEnvironment.getFiler();
        messager = processingEnvironment.getMessager();
        elements = processingEnvironment.getElementUtils();
    }
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        for (Element e : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
            String packName = elements.getPackageOf(e).getQualifiedName().toString(); //獲取包名
            //2: 獲取類(lèi)名.
            //如果標(biāo)注的是類(lèi)的話(huà)
            TypeElement t = (TypeElement)e;
            String className = t.getSimpleName().toString();
            //如果被標(biāo)注的是字段或者是方法
            TypeElement t = (TypeElement)e.getEnclosingElement();
            String className = t.getSimpleName().toString();
            //3. 獲取方法名字, 或者字段名字
            String name = e.getSimpleName().toString();
            //4: 獲取標(biāo)注的注解
            BindView annotation = e.getAnnotation(BindView.class);
            int id = annotation.value();
            //5: 判斷被標(biāo)注的元素類(lèi)型, 以字段類(lèi)型為例子
            ElementKind kind = e.getKind();
            if (kind == ElementKind.FIELD) {
            }
            //6: 獲取元素的修飾符. 我們以被修飾的字段為例子.
            for (Modifier modifier : e.getModifiers()) {
                if (modifier == Modifier.PRIVATE || modifier == Modifier.STATIC ||
                modifier == Modifier.FINAL) {
                  logError("BindView不能標(biāo)注被static," +
                    "private或者final的字段");
                }
            }
            //7: 判讀被標(biāo)注的元素的是不是某個(gè)類(lèi)的子類(lèi), 以View類(lèi)為例子, 判讀被修飾的字段是否是View的子類(lèi)
            TypeMirror tm = e.asType();
            boolean isSubType = false;
            while (tm != null) {
                if ("android.view.View".equals(tm.toString())) {
                    isSubType = true;
                    break;
                }
                TypeElement t = (TypeElement)types.asElement(tm);
                if (t != null) {
                    tm = t.getSuperclass(); //獲取父類(lèi)信息
                } else {
                    //如果為空, 說(shuō)明沒(méi)了父類(lèi), 所以直接退出
                    break;
                }
            }
        }
        return true;
    }
}

viewbinding

模板代碼已經(jīng)生成了, 但是我們還需要實(shí)例化這些類(lèi). 這個(gè)工作交由viewbinding. 它提供一些綁定的API給app. 這樣我們就不需要手動(dòng)調(diào)用了.

實(shí)現(xiàn)的思路: 運(yùn)行時(shí)利用反射去實(shí)例化對(duì)應(yīng)的模板類(lèi). 為了降低反射的消耗, 內(nèi)部會(huì)做響應(yīng)的緩存操作.

public class ViewBinding {
    private static String LATTER_NAME = "$ViewBinding";
    private static Map<Class<?>, Constructor<?>> CACHE = new HashMap<>();
    public static <T extends Activity> void bind(T target) {
        View sourceView = target.getWindow().getDecorView();
        bind(target, sourceView);
    }
     public static <T> void bind(T target, View sourceView) {
        Class<? extends Activity> cl = target.getClass();
        try {
            Class<?> bindClass = findBindClass(cl);
            Constructor<?> constructor = findBindConstructor(bindClass, cl);
            constructor.newInstance(target, sourceView);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    private static Class<?> findBindClass(Class<?> targetClass) throws ClassNotFoundException {
        return targetClass.getClassLoader().loadClass(targetClass.getName() + LATTER_NAME);
    }
    private static Constructor<?> findBindConstructor(Class<?> bindClass, Class<? extends Activity> targetClass) throws NoSuchMethodException {
        Constructor<?> constructor = CACHE.get(bindClass);
        if (constructor == null) {
            constructor = bindClass.getConstructor(targetClass, View.class);
            CACHE.put(bindClass, constructor);
        }
        return constructor;
    }
}

使用

注解處理器現(xiàn)在算是開(kāi)發(fā)好了. 最后的步驟就是在app中添加依賴(lài), 然后實(shí)現(xiàn)綁定.

在app的build.gradle添加下面的依賴(lài):

implementation project(':viewbinding')
implementation project(':annotations')
annotationProcessor project(':processors')

然后在MainActivity中實(shí)現(xiàn)注入:

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.main_tv_hello)
    TextView mTvTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewBinding.bind(this);
        mTvTextView.setText("xuixi");
    }
}

完…

參考資料

  • http://jinchim.com/2017/08/23/JBind/
  • http://blog.csdn.net/dd864140130/article/details/53875814

到此這篇關(guān)于Java注解處理器實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Java注解處理器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解SpringBoot自定義配置與整合Druid

    詳解SpringBoot自定義配置與整合Druid

    Druid是alibaba開(kāi)源平臺(tái)上一個(gè)數(shù)據(jù)庫(kù)連接池實(shí)現(xiàn),結(jié)合了C3P0,DBCP等DB池的優(yōu)點(diǎn),同時(shí)也有Web監(jiān)控界面。這篇文章主要介紹了Java之SpringBoot自定義配置與整合Druid的相關(guān)知識(shí),需要的朋友可以參考下
    2021-09-09
  • 詳解Java實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)之并查集

    詳解Java實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)之并查集

    并查集這種數(shù)據(jù)結(jié)構(gòu),可能出現(xiàn)的頻率不是那么高,但是還會(huì)經(jīng)常性的見(jiàn)到,其理解學(xué)習(xí)起來(lái)非常容易,通過(guò)本文,一定能夠輕輕松松搞定并查集
    2021-06-06
  • SpringBoot整合GitLab-CI實(shí)現(xiàn)持續(xù)集成的過(guò)程

    SpringBoot整合GitLab-CI實(shí)現(xiàn)持續(xù)集成的過(guò)程

    這篇文章主要介紹了SpringBoot整合GitLab-CI實(shí)現(xiàn)持續(xù)集成,本文詳細(xì)講述了 GitLab-CI 持續(xù)集成的安裝、部署、以及配置,需要的朋友可以參考下
    2022-12-12
  • SpringBoot整合redis實(shí)現(xiàn)輸入密碼錯(cuò)誤限制登錄功能

    SpringBoot整合redis實(shí)現(xiàn)輸入密碼錯(cuò)誤限制登錄功能

    遇到這樣的需求需要實(shí)現(xiàn)一個(gè)登錄功能,并且2分鐘之內(nèi)只能輸入5次錯(cuò)誤密碼,若輸入五次之后還沒(méi)有輸入正確密碼,系統(tǒng)將會(huì)將該賬號(hào)鎖定1小時(shí),這篇文章主要介紹了SpringBoot整合redis并實(shí)現(xiàn)輸入密碼錯(cuò)誤限制登錄功能,需要的朋友可以參考下
    2024-02-02
  • java語(yǔ)法糖之jdk迭代的新特性匯總

    java語(yǔ)法糖之jdk迭代的新特性匯總

    什么是語(yǔ)法糖?泛型、自動(dòng)裝箱拆箱、變長(zhǎng)參數(shù)、增強(qiáng)for循環(huán)、switch字符類(lèi)型、lambda表達(dá)式等,這些其實(shí)都是語(yǔ)法糖。這篇文章主要給大家介紹了關(guān)于java語(yǔ)法糖之jdk迭代的新特性的相關(guān)資料,需要的朋友可以參考下
    2021-05-05
  • java后端合成圖片的實(shí)現(xiàn)示例

    java后端合成圖片的實(shí)現(xiàn)示例

    本文主要介紹了java后端合成圖片的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • SPRING FRAMEWORK BEAN作用域和生命周期原理解析

    SPRING FRAMEWORK BEAN作用域和生命周期原理解析

    這篇文章主要介紹了SPRING FRAMEWORK BEAN作用域和生命周期原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • IntelliJ IDEA中使用mybatis-generator的示例

    IntelliJ IDEA中使用mybatis-generator的示例

    這篇文章主要介紹了IntelliJ IDEA中使用mybatis-generator,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-04-04
  • 徹底解決java.lang.ClassNotFoundException: com.mysql.jdbc.Driver問(wèn)題

    徹底解決java.lang.ClassNotFoundException: com.mysql.jdbc.Dr

    這篇文章給大家介紹了如如何徹底解決java.lang.ClassNotFoundException: com.mysql.jdbc.Driver問(wèn)題,文中有詳細(xì)的解決思路以及解決方法,需要的朋友可以參考下
    2023-11-11
  • multi-catch和try-catch異常處理知識(shí)點(diǎn)詳解

    multi-catch和try-catch異常處理知識(shí)點(diǎn)詳解

    在本篇文章里我們給大家分享了一篇關(guān)于multi-catch和try-catch異常處理知識(shí)點(diǎn)內(nèi)容,有需要的朋友們可以參考學(xué)習(xí)下。
    2019-11-11

最新評(píng)論

海晏县| 抚远县| 宜兴市| 兴义市| 万全县| 资溪县| 宾川县| 宁强县| 凌海市| 肇州县| 中阳县| 启东市| 集安市| 临汾市| 宁晋县| 陇南市| 柯坪县| 读书| 祁门县| 青铜峡市| 策勒县| 临沭县| 扎兰屯市| 遵义县| 沙洋县| 辉南县| 工布江达县| 鸡西市| 威远县| 抚远县| 新宁县| 寿阳县| 仁布县| 长乐市| 应用必备| 孟津县| 都昌县| 平度市| 香港| 远安县| 木里|