從原理層面掌握@InitBinder的使用【享學Spring MVC】

来源:https://www.cnblogs.com/fangshixiang/archive/2019/09/12/11509660.html
-Advertisement-
Play Games

一個可以沉迷於技術的程式猿,wx加入加入技術群:fsx641385712 ...


每篇一句

大魔王張怡寧:女兒,這堆金牌你拿去玩吧,但我的銀牌不能給你玩。你要想玩銀牌就去找你王浩叔叔吧,他那銀牌多

前言

為了講述好Spring MVC最為複雜的數據綁定這塊,我前面可謂是做足了功課,對此部分知識此處給小伙伴留一個學習入口,有興趣可以點開看看:聊聊Spring中的數據綁定 --- WebDataBinder、ServletRequestDataBinder、WebBindingInitializer...【享學Spring】

@InitBinder這個註解是Spring 2.5後推出來,用於數據綁定、設置數據轉換器等,字面意思是“初始化綁定器”。

關於數據綁定器的概念,前面的功課中有重點詳細講解,此處預設小伙伴是熟悉了的~

Spring MVC的web項目中,相信小伙伴們經常會遇到一些前端給後端傳值比較棘手的問題:比如最經典的問題:

  • Date類型(或者LocalDate類型)前端如何傳?後端可以用Date類型接收嗎?
  • 字元串類型,如何保證前段傳入的值兩端沒有空格呢?(99.99%的情況下多餘的空格都是木有用的)

對於這些看似不太好弄的問題,看了這篇文章你就可以優雅的搞定了~

---

說明:關於Date類型的傳遞,業界也有兩個通用的解決方案

  1. 使用時間戳
  2. 使用String字元串(傳值的萬能方案)

使用者兩種方式總感覺不優雅,且不夠面向對象。那麼本文就介紹一個黑科技:使用@InitBinder來便捷的實現各種數據類型的數據綁定(咱們Java是強類型語言且面向對象的,如果啥都用字元串,是不是也太low了~)

> 一般的string, int, long會自動綁定到參數,但是自定義的格式spring就不知道如何綁定了 .所以要繼承PropertyEditorSupport,實現自己的屬性編輯器PropertyEditor,綁定到WebDataBinder ( binder.registerCustomEditor),覆蓋方法setAsText


@InitBinder原理

本文先原理,再案例的方式,讓你能夠徹頭徹尾的掌握到該註解的使用。

1、@InitBinder是什麼時候生效的?
這就是前面文章埋下的伏筆:Spring在綁定請求參數到HandlerMethod的時候(此處以RequestParamMethodArgumentResolver為例),會藉助WebDataBinder進行數據轉換:

// RequestParamMethodArgumentResolver的父類就是它,resolveArgument方法在父類上
// 子類僅僅只需要實現抽象方法resolveName,即:從request里根據name拿值
AbstractNamedValueMethodArgumentResolver:

    @Override
    @Nullable
    public final Object resolveArgument( ... ) {
        ...
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        ...
        if (binderFactory != null) {
            // 創建出一個WebDataBinder
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            // 完成數據轉換(比如String轉Date、String轉...等等)
            arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            ...
        }
        ...
        return arg;
    }

它從請求request拿值得方法便是:request.getParameterValues(name)

2、web環境使用的數據綁定工廠是:ServletRequestDataBinderFactory
雖然在前面功課中有講到,但此處為了連貫性還是有必要再簡單過一遍:

// @since 3.1 org.springframework.web.bind.support.DefaultDataBinderFactory 
public class DefaultDataBinderFactory implements WebDataBinderFactory {

    @Override
    @SuppressWarnings("deprecation")
    public final WebDataBinder createBinder(NativeWebRequest webRequest, @Nullable Object target, String objectName) throws Exception {
        WebDataBinder dataBinder = createBinderInstance(target, objectName, webRequest);
        
        // WebBindingInitializer initializer在此處解析完成了 全局生效
        if (this.initializer != null) {
            this.initializer.initBinder(dataBinder, webRequest);
        }
        // 解析@InitBinder註解,它是個protected空方法,交給子類覆寫實現
        // InitBinderDataBinderFactory對它有覆寫
        initBinder(dataBinder, webRequest);
        return dataBinder;
    }
}

public class InitBinderDataBinderFactory extends DefaultDataBinderFactory {
    // 保存所有的,
    private final List<InvocableHandlerMethod> binderMethods;
    ...
    @Override
    public void initBinder(WebDataBinder dataBinder, NativeWebRequest request) throws Exception {
        for (InvocableHandlerMethod binderMethod : this.binderMethods) {
            if (isBinderMethodApplicable(binderMethod, dataBinder)) {
                // invokeForRequest這個方法不用多說了,和調用普通控制器方法一樣
                // 方法入參上也可以寫格式各樣的參數~~~~
                Object returnValue = binderMethod.invokeForRequest(request, null, dataBinder);
            
                // 標註有@InitBinder註解方法必須返回void
                if (returnValue != null) {
                    throw new IllegalStateException("@InitBinder methods must not return a value (should be void): " + binderMethod);
                }
            }
        }
    }

    // dataBinder.getObjectName()在此處終於起效果了  通過這個名稱來匹配
    // 也就是說可以做到讓@InitBinder註解只作用在指定的入參名字的數據綁定上~~~~~
    // 而dataBinder的這個ObjectName,一般就是入參的名字(註解指定的value值~~)

    // 形參名字的在dataBinder,所以此處有個簡單的過濾~~~~~~~
    protected boolean isBinderMethodApplicable(HandlerMethod initBinderMethod, WebDataBinder dataBinder) {
        InitBinder ann = initBinderMethod.getMethodAnnotation(InitBinder.class);
        Assert.state(ann != null, "No InitBinder annotation");
        String[] names = ann.value();
        return (ObjectUtils.isEmpty(names) || ObjectUtils.containsElement(names, dataBinder.getObjectName()));
    }
}

WebBindingInitializer介面方式是優先於@InitBinder註解方式執行的(API方式是去全局的,註解方式可不一定,所以更加的靈活些)

子類ServletRequestDataBinderFactory就做了一件事:new ExtendedServletRequestDataBinder(target, objectName)
ExtendedServletRequestDataBinder只做了一件事:處理path變數。

binderMethods是通過構造函數進來的,它表示和本次請求有關的所有的標註有@InitBinder的方法,所以需要瞭解它的實例是如何被創建的,那就是接下來這步。

3、ServletRequestDataBinderFactory的創建
任何一個請求進來,最終交給了HandlerAdapter.handle()方法去處理,它的創建流程如下:

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {
    ...
    @Override
    protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        ...
        // 處理請求,最終其實就是執行控制器的方法,得到一個ModelAndView
        mav = invokeHandlerMethod(request, response, handlerMethod);
        ...
    }
    
    // 執行控制器的方法,挺複雜的。但本文我只關心WebDataBinderFactory的創建,方法第一句便是
    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        ...
    }

    // 創建一個WebDataBinderFactory 
    // Global methods first(放在前面最先執行) 然後再執行本類自己的
    private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
        // handlerType:方法所在的類(控制器方法所在的類,也就是xxxController)
        // 由此可見,此註解的作用範圍是類級別的。會用此作為key來緩存
        Class<?> handlerType = handlerMethod.getBeanType();
        Set<Method> methods = this.initBinderCache.get(handlerType);
        if (methods == null) { // 緩存沒命中,就去selectMethods找到所有標註有@InitBinder的方法們~~~~
            methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
            this.initBinderCache.put(handlerType, methods); // 緩存起來
        }
        
        // 此處註意:Method最終都被包裝成了InvocableHandlerMethod,從而具有執行的能力
        List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
        
        // 上面找了本類的,現在開始看看全局裡有木有@InitBinder
        // Global methods first(先把全局的放進去,再放個性化的~~~~ 所以小細節:有覆蓋的效果喲~~~)
        // initBinderAdviceCache它是一個緩存LinkedHashMap(有序哦~~~),緩存著作用於全局的類。
        // 如@ControllerAdvice,註意和`RequestBodyAdvice`、`ResponseBodyAdvice`區分開來

        // methodSet:說明一個類裡面是可以定義N多個標註有@InitBinder的方法~~~~~
        this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
            
            // 簡單的說就是`RestControllerAdvice`它可以指定:basePackages之類的屬性,看本類是否能被掃描到吧~~~~
            if (clazz.isApplicableToBeanType(handlerType)) {
            
                // 這個resolveBean() 有點意思:它持有的Bean若是個BeanName的話,會getBean()一下的
                // 大多數情況下都是BeanName,這在@ControllerAdvice的初始化時會講~~~
                Object bean = clazz.resolveBean();
                for (Method method : methodSet) {
                    // createInitBinderMethod:把Method適配為可執行的InvocableHandlerMethod
                    
                    // 特點是把本類的HandlerMethodArgumentResolverComposite傳進去了
                    // 當然還有DataBinderFactory和ParameterNameDiscoverer等
                    initBinderMethods.add(createInitBinderMethod(bean, method));
                }
            }
        });
        // 後一步:再條件標註有@InitBinder的方法
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            initBinderMethods.add(createInitBinderMethod(bean, method));
        }

        // protected方法,就一句代碼:new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer())
        return createDataBinderFactory(initBinderMethods);
    }
    ...
}

到這裡,整個@InitBinder的解析過程就算可以全部理解了。關於這個過程,我有如下幾點想說:

  • 對於binderMethods每次請求過來都會新new一個(具有第一次懲罰效果),它既可以來自於全局(Advice),也可以來自於Controller本類
  • 倘若Controller上的和Advice上標註有次註解的方法名一毛一樣,也是不會覆蓋的(因為類不一樣)
  • 關於註解有@InitBinder的方法的執行,它和執行控制器方法差不多,都是調用了InvocableHandlerMethod#invokeForRequest方法,因此可以自行類比

    目前方法執行的核心,無非就是對參數的解析、封裝,也就是對HandlerMethodArgumentResolver的理解。強烈推薦你可以參考 這個系列的所有文章~


有了這些基礎理論的支撐,接下來當然就是它的使用Demo Show

@InitBinder的使用案例

我拋出兩個需求,藉助@InitBinder來實現:

  1. 請求進來的所有字元串trim一下
  2. yyyy-MM-dd這種格式的字元串能直接用Date類型接收(不用先用String接收再自己轉換,不優雅)

為了實現如上兩個需求,我需要先自定義兩個屬性編輯器:

1、StringTrimmerEditor

public class StringTrimmerEditor extends PropertyEditorSupport {

    // 將屬性對象用一個字元串表示,以便外部的屬性編輯器能以可視化的方式顯示。預設返回null,表示該屬性不能以字元串表示
    //@Override
    //public String getAsText() {
    //    Object value = getValue();
    //    return (value != null ? value.toString() : null);
    //}

    // 用一個字元串去更新屬性的內部值,這個字元串一般從外部屬性編輯器傳入
    // 處理請求的入參:test就是你傳進來的值(並不是super.getValue()哦~)
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        text = text == null ? text : text.trim();
        setValue(text);
    }
}

說明:Spring內置有org.springframework.beans.propertyeditors.StringTrimmerEditor,預設情況下它並沒有裝配進來,若你有需要可以直接使用它的(此處為了演示,我就用自己的)。Spring內置註冊了哪些?參照PropertyEditorRegistrySupport#createDefaultEditors方法
Spring的屬性編輯器和傳統的用於IDE開發時的屬性編輯器不同,它們沒有UI界面,僅負責將配置文件中的文本配置值轉換為Bean屬性的對應值,所以Spring的屬性編輯器並非傳統意義上的JavaBean屬性編輯器

2、CustomDateEditor
關於這個屬性編輯器,你也可以像我一樣自己實現。本文就直接使用Spring提供了的,參見:org.springframework.beans.propertyeditors.CustomDateEditor

// @since 28.04.2003
// @see java.util.Date
public class CustomDateEditor extends PropertyEditorSupport {
    ...
    @Override
    public void setAsText(@Nullable String text) throws IllegalArgumentException {
        ...
        setValue(this.dateFormat.parse(text));
        ...
    }
    ...
    @Override
    public String getAsText() {
        Date value = (Date) getValue();
        return (value != null ? this.dateFormat.format(value) : "");
    }
}

定義好後,如何使用呢?有兩種方式:

  1. API方式WebBindingInitializer ,關於它的使用,請參閱這裡,本文略。
    1. 重寫initBinder註冊的屬性編輯器是全局的屬性編輯器,對所有的Controller都有效(全局的)
  2. @InitBinder註解方式

Controller本類上使用@InitBinder,形如這樣:

@Controller
@RequestMapping
public class HelloController {

    @InitBinder
    public void initBinder(WebDataBinder binder) {
        //binder.setDisallowedFields("name"); // 不綁定name屬性
        binder.registerCustomEditor(String.class, new StringTrimmerEditor());

        // 此處使用Spring內置的CustomDateEditor
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
    }

    @ResponseBody
    @GetMapping("/test/initbinder")
    public String testInitBinder(String param, Date date) {
        return param + ":" + date;
    }
}

請求:/test/initbinder?param= ds&date=2019-12-12。結果為:ds:Thu Dec 12 00: 00: 00 CST 2019,符合預期。

註意,若date為null返回值為ds: null(因為我設置了允許為null)
但若你不是yyyy-MM-dd格式,那就拋錯嘍(格式化異常)

本例的@InitBinder方法只對當前Controller生效。要想全局生效,可以使用@ControllerAdvice/WebBindingInitializer
通過@ControllerAdvice可以將對於控制器的全局配置放置在同一個位置,註解了@ControllerAdvice的類的方法可以使用@ExceptionHandler@InitBinder@ModelAttribute等註解到方法上,這對所有註解了@RequestMapping的控制器內的方法有效(關於全局的方式本文略,建議各位自己實踐~)。

@InitBinder的value屬性的作用

獲取你可能還不知道,它還有個value屬性呢,並且還是數組

public @interface InitBinder {
    // 用於限定次註解標註的方法作用於哪個模型key上
    String[] value() default {};
}

說人話:若指定了value值,那麼只有方法參數名(或者模型名)匹配上了此註解方法才會執行(若不指定,都執行)。

@Controller
@RequestMapping
public class HelloController {

    @InitBinder({"param", "user"})
    public void initBinder(WebDataBinder binder, HttpServletRequest request) {
        System.out.println("當前key:" + binder.getObjectName());
    }

    @ResponseBody
    @GetMapping("/test/initbinder")
    public String testInitBinder(String param, String date,
                                 @ModelAttribute("user") User user, @ModelAttribute("person") Person person) {
        return param + ":" + date;
    }
}

請求:/test/initbinder?param=fsx&date=2019&user.name=demoUser,控制台列印:

當前key:param
當前key:user

從列印結果中很清楚的看出了value屬性的作用~

需要說明一點:雖然此處有key是user.name,但是User對象可是不會封裝到此值的(因為request.getParameter('user')沒這個key嘛~)。如何解決???需要綁定首碼,原理可參考這裡

其它應用場景

上面例舉的場景是此註解最為常用的場景,大家務必掌握。它還有一些奇淫技巧的使用,心有餘力的小伙伴不妨也可以消化消化:

若你一次提交需要提交兩個"模型"數據,並且它們有重名的屬性。形如下麵例子:

@Controller
@RequestMapping
public class HelloController {

    @Getter
    @Setter
    @ToString
    public static class User {
        private String id;
        private String name;
    }

    @Getter
    @Setter
    @ToString
    public static class Addr {
        private String id;
        private String name;
    }

    @InitBinder("user")
    public void initBinderUser(WebDataBinder binder) {
        binder.setFieldDefaultPrefix("user.");
    }

    @InitBinder("addr")
    public void initBinderAddr(WebDataBinder binder) {
        binder.setFieldDefaultPrefix("addr.");
    }

    @ResponseBody
    @GetMapping("/test/initbinder")
    public String testInitBinder(@ModelAttribute("user") User user, @ModelAttribute("addr") Addr addr) {
        return user + ":" + addr;
    }
}

請求:/test/initbinder?user.id=1&user.name=demoUser&addr.id=10&addr.name=北京市海澱區,結果為:HelloController.User(id=1, name=demoUser):HelloController.Addr(id=10, name=北京市海澱區)

至於加了首碼為何能綁定上,這裡簡要說說:
1、ModelAttributeMethodProcessor#resolveArgument里依賴attribute = createAttribute(name, parameter, binderFactory, webRequest)方法完成數據的封裝、轉換
2、createAttributerequest.getParameter(attributeName)看請求域里是否有值(此處為null),若木有就反射創建一個空實例,回到resolveArgument方法。
3、繼續利用WebDataBinder來完成對這個空對象的數據值綁定,這個時候這些FieldDefaultPrefix就起作用了。執行方法是:bindRequestParameters(binder, webRequest),實際上是((WebRequestDataBinder) binder).bind(request);。對於bind方法的原理,就不陌生了~
4、完成Model數據的封裝後,再進行@Valid校驗...

參考解析類:ModelAttributeMethodProcessor對參數部分的處理

總結

本文花大篇幅從原理層面總結了@InitBinder這個註解的使用,雖然此註解在當下的環境中出鏡率並不是太高,但我還是期望小伙伴能理解它,特別是我本文舉例說明的例子的場景一定能做到運用自如。

最後,此註解的使用的註意事項我把它總結如下,供各位使用過程中參考:

  1. @InitBinder標註的方法執行是多次的,一次請求來就執行一次(第一次懲罰)
  2. Controller實例中的所有@InitBinder只對當前所在的Controller有效
  3. @InitBinder的value屬性控制的是模型Model里的key,而不是方法名(不寫代表對所有的生效)
  4. @InitBinder標註的方法不能有返回值(只能是void或者returnValue=null
  5. @InitBinder@RequestBody這種基於消息轉換器的請求參數無效
    1. 因為@InitBinder它用於初始化DataBinder數據綁定、類型轉換等功能,而@RequestBody它的數據解析、轉換時消息轉換器來完成的,所以即使你自定義了屬性編輯器,對它是不生效的(它的WebDataBinder只用於數據校驗,不用於數據綁定和數據轉換。它的數據綁定轉換若是json,一般都是交給了jackson來完成的
  6. 只有AbstractNamedValueMethodArgumentResolver才會調用binder.convertIfNecessary進行數據轉換,從而屬性編輯器才會生效

== 若對Spring、SpringBoot、MyBatis等源碼分析感興趣,可加我wx:fsx641385712,手動邀請你入群一起飛 ==
== 若對Spring、SpringBoot、MyBatis等源碼分析感興趣,可加我wx:fsx641385712,手動邀請你入群一起飛 ==


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.什麼是HTML語義化? 基本上都是圍繞著幾個主要的標簽,像標題(h1-h6),列表(li),強調(strong em)等。 根據內容的語義化(內容結構化),選擇合適的標簽(代碼語義化),便於開發者閱讀和寫出更優雅的代碼的同時,讓瀏覽器的爬蟲和機器很好的解析。 2.為什麼要語義化? 為了在沒有cs ...
  • 一、安裝node nodejs下載地址:https://nodejs.org/ nodejs自帶npm模塊管理器,安裝完成之後打開dos命令視窗輸入 node -v就能查看nodejs是否安裝成成功 由於npm安裝module太慢,建議使用淘寶鏡像:npm install cnpm -g --reg ...
  • 環境: 1、ionic 2、angular-cli 開發 1、CTRL C + CTRL V 2、圖片路徑的問題 使用‘assets/xxxxx.jpg’,而不使用‘../../assets/xxxxx.jpg’,因為打包後的目錄如下: 伺服器上圖片會報404錯誤。 3、打包問題 打包命令: ion ...
  • HTML5現在已經不是SGML的子集,主要是關於圖像,位置,存儲,多任務等功能的增加。 繪畫canvas; 用於媒介回放的video和audio元素; 本地離線存儲localStorage長期存儲數據,瀏覽器關閉後數據不丟失; sessionStorage的數據在瀏覽器關閉後自動刪除; 語義化更好的 ...
  • jQuery(五): Deferred 有啥用 通常來說,js請求數據,無論是非同步還是同步,都不會立即獲取到結果,通常而言,我們一般是是使用回調函數再執行,而 deferred就是解決jQuery的回調函數方案,總的來說,deferred對象就是為了將某個回調函數延遲到某個時機再執行. 1. aja ...
  • Node.js是一個Javascript運行環境(runtime environment),發佈於2009年5月,由Ryan Dahl開發,實質是對Chrome V8引擎進行了封裝。本文詳細介紹了Node.js的安裝和使用。 一、Node.js介紹 Node.js 不是一個 JavaScript 框 ...
  • 面向微服務的體繫結構如今風靡全球。這是因為更快的部署節奏和更低的成本是面向微服務的體繫結構的基本承諾。 然而,對於大多數試水的公司來說,開發活動更多的是將現有的單塊應用程式轉換為面向微服務的體繫結構,這可能是許多層面上阻礙和衝突的根源。 雖然 "Greenfield" (未開發的)面向微服務的體繫結 ...
  • 一、JVM包含三個記憶體區:棧記憶體、堆記憶體、方法區記憶體 二、註意點 (1)在MyEclipse中字體是紅色的是一個類的名字,並且這個類除了我們自定義的類是JavaSE類庫中自帶的 (2)其實JavaSE類庫中自帶的類,例如:String.class\System.class,這些類的類名也是標識符 ( ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...