成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

自己動(dòng)手在Spring-Boot上加強(qiáng)國際化功能

gecko23 / 4024人閱讀

摘要:還設(shè)置一個(gè)攔截器來攔截國際化語言的變化。修改啟動(dòng)類攔截器現(xiàn)在我們?cè)龠\(yùn)行一下看看效果,看到每個(gè)鏈接都顯示的他們對(duì)應(yīng)的國際化信息里的內(nèi)容。

前言
公司將項(xiàng)目由Struts2轉(zhuǎn)到Springmvc了,由于公司業(yè)務(wù)是境外服務(wù),所以對(duì)國際化功能需求很高。Struts2自帶的國際化功能相對(duì)Springmvc來說更加完善,不過spring很大的特性就是可定定制化性強(qiáng),所以在公司項(xiàng)目移植的到Springmvc的時(shí)候增加了其國際化的功能。特此整理記錄并且完善了一下。

本文主要實(shí)現(xiàn)的功能:

從文件夾中直接加載多個(gè)國際化文件

后臺(tái)設(shè)置前端頁面顯示國際化信息的文件

利用攔截器和注解自動(dòng)設(shè)置前端頁面顯示國際化信息的文件

注:本文不詳細(xì)介紹怎么配置國際化,區(qū)域解析器等。
實(shí)現(xiàn) 國際化項(xiàng)目初始化

先創(chuàng)建一個(gè)基本的Spring-Boot+thymeleaf+國際化信息(message.properties)項(xiàng)目,如果有需要可以從我的Github下載。

簡(jiǎn)單看一下項(xiàng)目的目錄和文件

其中I18nApplication.java設(shè)置了一個(gè)CookieLocaleResolver,采用cookie來控制國際化的語言。還設(shè)置一個(gè)LocaleChangeInterceptor攔截器來攔截國際化語言的變化。

@SpringBootApplication
@Configuration
public class I18nApplication {
    public static void main(String[] args) {
        SpringApplication.run(I18nApplication.class, args);
    }

    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver slr = new CookieLocaleResolver();
        slr.setCookieMaxAge(3600);
        slr.setCookieName("Language");//設(shè)置存儲(chǔ)的Cookie的name為L(zhǎng)anguage
        return slr;
    }

    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            //攔截器
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new LocaleChangeInterceptor()).addPathPatterns("/**");
            }
        };
    }
}

我們?cè)倏匆幌耯ello.html中寫了什么:




    Hello World!


現(xiàn)在啟動(dòng)項(xiàng)目并且訪問http://localhost:9090/hello(我在application.properties)中設(shè)置了端口為9090。

由于瀏覽器默認(rèn)的語言是中文,所以他默認(rèn)會(huì)去messages_zh_CN.properties中找,如果沒有就會(huì)去messages.properties中找國際化詞。

然后我們?cè)跒g覽器中輸入http://localhost:9090/hello?locale=en_US,語言就會(huì)切到英文。同樣的如果url后參數(shù)設(shè)置為locale=zh_CH,語言就會(huì)切到中文。

從文件夾中直接加載多個(gè)國際化文件

在我們hello.html頁面中,只有"i18n_page"和"hello"兩個(gè)國際化信息,然而在實(shí)際項(xiàng)目中肯定不會(huì)只有幾個(gè)國際化信息那么少,通常都是成千上百個(gè)的,那我們肯定不能把這么多的國際化信息都放在messages.properties一個(gè)文件中,通常都是把國際化信息分類存放在幾個(gè)文件中。但是當(dāng)項(xiàng)目大了以后,這些國際化文件也會(huì)越來越多,這時(shí)候在application.properties文件中一個(gè)個(gè)的去配置這個(gè)文件也是不方便的,所以現(xiàn)在我們實(shí)現(xiàn)一個(gè)功能自動(dòng)加載制定目錄下所有的國際化文件。

繼承ResourceBundleMessageSource

在項(xiàng)目下創(chuàng)建一個(gè)類繼承ResourceBundleMessageSource或者ReloadableResourceBundleMessageSource,起名為MessageResourceExtension。并且注入到bean中起名為messageSource,這里我們繼承ResourceBundleMessageSource。

@Component("messageSource")
public class MessageResourceExtension extends ResourceBundleMessageSource {
}

注意這里我們的Component名字必須為"messageSource",因?yàn)樵诔跏蓟?b>ApplicationContext的時(shí)候,會(huì)查找bean名為"messageSource"的bean。這個(gè)過程在AbstractApplicationContext.java中,我們看一下源代碼

/**
* Initialize the MessageSource.
* Use parent"s if none defined in this context.
*/
protected void initMessageSource() {
    ConfigurableListableBeanFactory beanFactory = getBeanFactory();
    if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
        this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
    ...
    }
}
...

在這個(gè)初始化MessageSource的方法中,beanFactory查找注入名為MESSAGE_SOURCE_BEAN_NAME(messageSource)的bean,如果沒有找到,就會(huì)在其父類中查找是否有該名的bean。

實(shí)現(xiàn)文件加載

現(xiàn)在我們可以開始在剛才創(chuàng)建的MessageResourceExtension

中寫加載文件的方法了。

@Component("messageSource")
public class MessageResourceExtension extends ResourceBundleMessageSource {

    private final static Logger logger = LoggerFactory.getLogger(MessageResourceExtension.class);

    /**
     * 指定的國際化文件目錄
     */
    @Value(value = "${spring.messages.baseFolder:i18n}")
    private String baseFolder;

    /**
     * 父MessageSource指定的國際化文件
     */
    @Value(value = "${spring.messages.basename:message}")
    private String basename;

    @PostConstruct
    public void init() {
        logger.info("init MessageResourceExtension...");
        if (!StringUtils.isEmpty(baseFolder)) {
            try {
                this.setBasenames(getAllBaseNames(baseFolder));
            } catch (IOException e) {
                logger.error(e.getMessage());
            }
        }
        //設(shè)置父MessageSource
        
        ResourceBundleMessageSource parent = new ResourceBundleMessageSource();
        parent.setBasename(basename);
        this.setParentMessageSource(parent);
    }

    /**
     * 獲取文件夾下所有的國際化文件名
     *
     * @param folderName 文件名
     * @return
     * @throws IOException
     */
    private String[] getAllBaseNames(String folderName) throws IOException {
        Resource resource = new ClassPathResource(folderName);
        File file = resource.getFile();
        List baseNames = new ArrayList<>();
        if (file.exists() && file.isDirectory()) {
            this.getAllFile(baseNames, file, "");
        } else {
            logger.error("指定的baseFile不存在或者不是文件夾");
        }
        return baseNames.toArray(new String[baseNames.size()]);
    }

    /**
     * 遍歷所有文件
     *
     * @param basenames
     * @param folder
     * @param path
     */
    private void getAllFile(List basenames, File folder, String path) {
        if (folder.isDirectory()) {
            for (File file : folder.listFiles()) {
                this.getAllFile(basenames, file, path + folder.getName() + File.separator);
            }
        } else {
            String i18Name = this.getI18FileName(path + folder.getName());
            if (!basenames.contains(i18Name)) {
                basenames.add(i18Name);
            }

        }
    }

    /**
     * 把普通文件名轉(zhuǎn)換成國際化文件名
     *
     * @param filename
     * @return
     */
    private String getI18FileName(String filename) {
        filename = filename.replace(".properties", "");
        for (int i = 0; i < 2; i++) {
            int index = filename.lastIndexOf("_");
            if (index != -1) {
                filename = filename.substring(0, index);
            }
        }
        return filename;
    }
}

依次解釋一下幾個(gè)方法。

init()方法上有一個(gè)@PostConstruct注解,這會(huì)在MessageResourceExtension類被實(shí)例化之后自動(dòng)調(diào)用init()方法。這個(gè)方法獲取到baseFolder目錄下所有的國際化文件并設(shè)置到basenameSet中。并且設(shè)置一個(gè)ParentMessageSource,這會(huì)在找不到國際化信息的時(shí)候,調(diào)用父MessageSource來查找國際化信息。

getAllBaseNames()方法獲取到baseFolder的路徑,然后調(diào)用getAllFile()方法獲取到該目錄下所有的國際化文件的文件名。

getAllFile()遍歷目錄,如果是文件夾就繼續(xù)遍歷,如果是文件就調(diào)用getI18FileName()把文件名轉(zhuǎn)為’i18n/basename/‘格式的國際化資源名。

所以簡(jiǎn)單來說就是在MessageResourceExtension被實(shí)例化之后,把"i18n"文件夾下的資源文件的名字,加載到Basenames中?,F(xiàn)在來看一下效果。

首先我們?cè)赼pplication.properties文件中添加一個(gè)spring.messages.baseFolder=i18n,這會(huì)把"i18n"這個(gè)值賦值給MessageResourceExtension中的baseFolder。

在啟動(dòng)后看到控制臺(tái)里打印出了init信息,表示被@PostConstruct注解的init()方法已經(jīng)執(zhí)行。

然后我們?cè)賱?chuàng)建兩組國際化信息文件:"dashboard"和"merchant",里面分別只有一個(gè)國際化信息:"dashboard.hello"和"merchant.hello"。

之后再修改一下hello.html文件,然后訪問hello頁面。

...

國際化頁面!

...

可以看到網(wǎng)頁中加載了"message","dashboard"和"merchant"中的國際化信息,說明我們已經(jīng)成功一次性加載了"i18n"文件夾下的文件。

后臺(tái)設(shè)置前端頁面顯示國際化信息的文件

s剛才那一節(jié)我們成功加載了多個(gè)國際化文件并顯示出了他們的國際化信息。但是"dashboard.properties"中的國際化信息為"dashboard.hello"而"merchant.properties"中的是"merchant.hello",這樣每個(gè)都要寫一個(gè)前綴豈不是很麻煩,現(xiàn)在我想要在"dashboard"和"merchant"的國際化文件中都只寫"hello"但是顯示的是"dashboard"或"merchant"中的國際化信息。

MessageResourceExtension重寫resolveCodeWithoutArguments方法(如果有字符格式化的需求就重寫resolveCode方法)。

@Component("messageSource")
public class MessageResourceExtension extends ResourceBundleMessageSource {
    ...
    public static String I18N_ATTRIBUTE = "i18n_attribute";
    
    @Override
    protected String resolveCodeWithoutArguments(String code, Locale locale) {
        // 獲取request中設(shè)置的指定國際化文件名
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        final String i18File = (String) attr.getAttribute(I18N_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
        if (!StringUtils.isEmpty(i18File)) {
            //獲取在basenameSet中匹配的國際化文件名
            String basename = getBasenameSet().stream()
                    .filter(name -> StringUtils.endsWithIgnoreCase(name, i18File))
                    .findFirst().orElse(null);
            if (!StringUtils.isEmpty(basename)) {
                //得到指定的國際化文件資源
                ResourceBundle bundle = getResourceBundle(basename, locale);
                if (bundle != null) {
                    return getStringOrNull(bundle, code);
                }
            }
        }
        //如果指定i18文件夾中沒有該國際化字段,返回null會(huì)在ParentMessageSource中查找
        return null;
    }
    ...
}

在我們重寫的resolveCodeWithoutArguments方法中,從HttpServletRequest中獲取到‘I18N_ATTRIBUTE’(等下再說這個(gè)在哪里設(shè)置),這個(gè)對(duì)應(yīng)我們想要顯示的國際化文件名,然后我們?cè)?b>BasenameSet中查找該文件,再通過getResourceBundle獲取到資源,最后再getStringOrNull獲取到對(duì)應(yīng)的國際化信息。

現(xiàn)在我們到我們的HelloController里加兩個(gè)方法。

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String index(HttpServletRequest request) {
        request.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, "hello");
        return "system/hello";
    }

    @GetMapping("/dashboard")
    public String dashboard(HttpServletRequest request) {
        request.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, "dashboard");
        return "dashboard";
    }

    @GetMapping("/merchant")
    public String merchant(HttpServletRequest request) {
        request.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, "merchant");
        return "merchant";
    }
}

看到我們?cè)诿總€(gè)方法中都設(shè)置一個(gè)對(duì)應(yīng)的"I18N_ATTRIBUTE",這會(huì)在每次請(qǐng)求中設(shè)置對(duì)應(yīng)的國際化文件,然后在MessageResourceExtension中獲取。

這時(shí)我們看一下我們的國際化文件,我們可以看到所有關(guān)鍵字都是"hello",但是信息卻不同。

同時(shí)新增兩個(gè)html文件分別是"dashboard.html"和"merchant.html",里面只有一個(gè)"hello"的國際化信息和用于區(qū)分的標(biāo)題。



國際化頁面!



國際化頁面(dashboard)!



國際化頁面(merchant)!

這時(shí)我們啟動(dòng)項(xiàng)目看一下。

可以看到雖然在每個(gè)頁面的國際化詞都是"hello",但是我們?cè)趯?duì)應(yīng)的頁面顯示了我們想要顯示的信息。

利用攔截器和注解自動(dòng)設(shè)置前端頁面顯示國際化信息的文件

雖然已經(jīng)可以指定對(duì)應(yīng)的國際化信息,但是這樣要在每個(gè)controller里的HttpServletRequest中設(shè)置國際化文件實(shí)在太麻煩了,所以現(xiàn)在我們實(shí)現(xiàn)自動(dòng)判定來顯示對(duì)應(yīng)的文件。

首先我們創(chuàng)建一個(gè)注解,這個(gè)注解可以放在類上或者方法上。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface I18n {
    /**
     * 國際化文件名
     */
    String value();
}

然后我們把這個(gè)創(chuàng)建的I18n 注解放在剛才的Controller方法中,為了顯示他的效果,我們?cè)賱?chuàng)建一個(gè)ShopControllerUserController,同時(shí)也創(chuàng)建對(duì)應(yīng)的"shop"和"user"的國際化文件,內(nèi)容也都是一個(gè)"hello"。

@Controller
public class HelloController {
    @GetMapping("/hello")
    public String index() {
        return "system/hello";
    }

    @I18n("dashboard")
    @GetMapping("/dashboard")
    public String dashboard() {
        return "dashboard";
    }

    @I18n("merchant")
    @GetMapping("/merchant")
    public String merchant() {
        return "merchant";
    }
}
@I18n("shop")
@Controller
public class ShopController {
    @GetMapping("shop")
    public String shop() {
        return "shop";
    }
}
@Controller
public class UserController {
    @GetMapping("user")
    public String user() {
        return "user";
    }
}

我們把I18n注解分別放在HelloController下的dashboardmerchant方法下,和ShopController類上。并且去除了原來dashboardmerchant方法下設(shè)置‘I18N_ATTRIBUTE’的語句。

準(zhǔn)備工作都做好了,現(xiàn)在看看如何實(shí)現(xiàn)根據(jù)這些注解自動(dòng)的指定國際化文件。

public class MessageResourceInterceptor implements HandlerInterceptor {
    @Override
    public void postHandle(HttpServletRequest req, HttpServletResponse rep, Object handler, ModelAndView modelAndView) {

        // 在方法中設(shè)置i18路徑
        if (null != req.getAttribute(MessageResourceExtension.I18N_ATTRIBUTE)) {
            return;
        }

        HandlerMethod method = (HandlerMethod) handler;
        // 在method上注解了i18
        I18n i18nMethod = method.getMethodAnnotation(I18n.class);
        if (null != i18nMethod) {
            req.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, i18nMethod.value());
            return;
        }

        // 在Controller上注解了i18
        I18n i18nController = method.getBeanType().getAnnotation(I18n.class);
        if (null != i18nController) {
            req.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, i18nController.value());
            return;
        }

        // 根據(jù)Controller名字設(shè)置i18
        String controller = method.getBeanType().getName();
        int index = controller.lastIndexOf(".");
        if (index != -1) {
            controller = controller.substring(index + 1, controller.length());
        }
        index = controller.toUpperCase().indexOf("CONTROLLER");
        if (index != -1) {
            controller = controller.substring(0, index);
        }
        req.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, controller);
    }

    @Override
    public boolean preHandle(HttpServletRequest req, HttpServletResponse rep, Object handler) {
        // 在跳轉(zhuǎn)到該方法先清除request中的國際化信息
        req.removeAttribute(MessageResourceExtension.I18N_ATTRIBUTE);
        return true;
    }
}

簡(jiǎn)單講解一下這個(gè)攔截器。

首先,如果request中已經(jīng)有"I18N_ATTRIBUTE",說明在Controller的方法中指定設(shè)置了,就不再判斷。

然后判斷一下進(jìn)入攔截器的方法上有沒有I18n的注解,如果有就設(shè)置"I18N_ATTRIBUTE"到request中并退出攔截器,如果沒有就繼續(xù)。

再判斷進(jìn)入攔截的類上有沒有I18n的注解,如果有就設(shè)置"I18N_ATTRIBUTE"到request中并退出攔截器,如果沒有就繼續(xù)。

最后假如方法和類上都沒有I18n的注解,那我們可以根據(jù)Controller名自動(dòng)設(shè)置指定的國際化文件,比如"UserController"那么就會(huì)去找"user"的國際化文件。

攔截器完成了,現(xiàn)在把攔截器配置到系統(tǒng)中。修改I18nApplication啟動(dòng)類:

@SpringBootApplication
@Configuration
public class I18nApplication {
    ...
        
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
        return new WebMvcConfigurer() {
            //攔截器
            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new LocaleChangeInterceptor()).addPathPatterns("/**");
                registry.addInterceptor(new MessageResourceInterceptor()).addPathPatterns("/**");
            }
        };
    }
}

現(xiàn)在我們?cè)龠\(yùn)行一下看看效果,看到每個(gè)鏈接都顯示的他們對(duì)應(yīng)的國際化信息里的內(nèi)容。

最后

剛才完成了我們整個(gè)國際化增強(qiáng)的基本功能,最后我把全部代碼整理了一下,并且整合了bootstrap4來展示了一下功能的實(shí)現(xiàn)效果。

詳細(xì)的代碼可以看我Github上Spring-Boot-I18n-Pro的代碼

原文地址:http://zzzzbw.cn

2018/8/30更新

文章發(fā)布后,有人向我提到當(dāng)把項(xiàng)目打成jar包之后執(zhí)行java -jar i18n-0.0.1.jar的方式來運(yùn)行程序會(huì)報(bào)錯(cuò)。看到這樣的反饋我立刻就意識(shí)到,確實(shí)在讀取i18n的國際化文件的時(shí)候用的是File的形式來讀取文件名的,假如打包成jar包后所有文件都是在壓縮文件夾中,就不能簡(jiǎn)單的以File的形式來獲取到文件夾下的所有文件了。因?yàn)楣镜捻?xiàng)目是以war包的形式在Tomcat下運(yùn)行,所以沒有發(fā)現(xiàn)這個(gè)問題。

主要問題是在MessageResourceExtension類在spring-boot啟動(dòng)時(shí)讀取配置文件導(dǎo)致的,所以修改MessageResourceExtension

@Component("messageSource")
public class MessageResourceExtension extends ResourceBundleMessageSource {
    ...
        
    /**
     * 獲取文件夾下所有的國際化文件名
     */
    private String[] getAllBaseNames(final String folderName) throws IOException {
        URL url = Thread.currentThread().getContextClassLoader()
                .getResource(folderName);
        if (null == url) {
            throw new RuntimeException("無法獲取資源文件路徑");
        }

        List baseNames = new ArrayList<>();
        if (url.getProtocol().equalsIgnoreCase("file")) {
            // 文件夾形式,用File獲取資源路徑
            File file = new File(url.getFile());
            if (file.exists() && file.isDirectory()) {
                baseNames = Files.walk(file.toPath())
                        .filter(path -> path.toFile().isFile())
                        .map(Path::toString)
                        .map(path -> path.substring(path.indexOf(folderName)))
                        .map(this::getI18FileName)
                        .distinct()
                        .collect(Collectors.toList());
            } else {
                logger.error("指定的baseFile不存在或者不是文件夾");
            }
        } else if (url.getProtocol().equalsIgnoreCase("jar")) {
            // jar包形式,用JarEntry獲取資源路徑
            String jarPath = url.getFile().substring(url.getFile().indexOf(":") + 2, url.getFile().indexOf("!"));
            JarFile jarFile = new JarFile(new File(jarPath));
            List baseJars = jarFile.stream()
                    .map(ZipEntry::toString)
                    .filter(jar -> jar.endsWith(folderName + "/")).collect(Collectors.toList());
            if (baseJars.isEmpty()) {
                logger.info("不存在{}資源文件夾", folderName);
                return new String[0];
            }

            baseNames = jarFile.stream().map(ZipEntry::toString)
                    .filter(jar -> baseJars.stream().anyMatch(jar::startsWith))
                    .filter(jar -> jar.endsWith(".properties"))
                    .map(jar -> jar.substring(jar.indexOf(folderName)))
                    .map(this::getI18FileName)
                    .distinct()
                    .collect(Collectors.toList());

        }
        return baseNames.toArray(new String[0]);
    }

    /**
     * 把普通文件名轉(zhuǎn)換成國際化文件名
     */
    private String getI18FileName(String filename) {
        filename = filename.replace(".properties", "");
        for (int i = 0; i < 2; i++) {
            int index = filename.lastIndexOf("_");
            if (index != -1) {
                filename = filename.substring(0, index);
            }
        }
        return filename.replace("", "/");
    }
    
    ...
}

getAllBaseNames()方法中會(huì)先判斷項(xiàng)目的Url形式為文件形式還是jar包形式。

如果是文件形式則就以普通文件夾的方式讀取,這里還用了java8中的Files.walk()方法獲取到文件夾下的所有文件,比原來自己寫遞歸來讀取方便多了。

如果是jar包的形式,那么就要用JarEntry來處理文件了。

首先是獲取到項(xiàng)目jar包所在的的目錄,如E:/workspace/java/Spring-Boot-I18n-Pro/target/i18n-0.0.1.jar這種,然后根據(jù)該目錄new一個(gè)JarFile。

接著遍歷這個(gè)JarFile包下的資源,這會(huì)把我們項(xiàng)目jar包下的所有文件都讀取出來,所以我們要先找到我們i18n資源文件所在的目錄,通過.filter(jar -> jar.endsWith(folderName + "/"))獲取資源所在目錄。

接下來就是判斷JarFile包下的文件是否在i18n資源目錄了,如果是則調(diào)用getI18FileName()方法將其格式化成我們所需要的名字形式。

經(jīng)過這段操作就實(shí)現(xiàn)了獲取jar包下i18n的資源文件名了。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69189.html

相關(guān)文章

  • 從零開始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架

    摘要:不過仔細(xì)了解了一段時(shí)候發(fā)現(xiàn),其實(shí)他的原理是很簡(jiǎn)單的,所以想要自己也動(dòng)手實(shí)現(xiàn)一個(gè)功能類似的框架。原文地址從零開始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的框架 前言 最近在看spring-boot框架的源碼,看了源碼之后更是讓我感受到了spring-boot功能的強(qiáng)大。而且使用了很多的設(shè)計(jì)模式,讓人在看的時(shí)候覺得有點(diǎn)難以下手。 不過仔細(xì)了解了一段時(shí)候發(fā)現(xiàn),其實(shí)他的原理是很簡(jiǎn)單的,所以想要自己也動(dòng)手實(shí)現(xiàn)一個(gè)功能類似的...

    neuSnail 評(píng)論0 收藏0
  • spring-boot創(chuàng)建最簡(jiǎn)單的web應(yīng)用

    摘要:初衷看了一下相關(guān)的書籍,創(chuàng)建一個(gè)的應(yīng)用,是那么的簡(jiǎn)單。首先,我們只是創(chuàng)建一個(gè)簡(jiǎn)單的并不打算使用默認(rèn)的,而是使用傳統(tǒng)的。在下創(chuàng)建目錄并且在目錄下新建,內(nèi)容為頁面。如果是在內(nèi)置的的情況下,應(yīng)用會(huì)自動(dòng)重啟。 初衷 看了一下spring-boot相關(guān)的書籍,創(chuàng)建一個(gè)hello world!的應(yīng)用,是那么的簡(jiǎn)單。然而,自己動(dòng)手,卻很不一樣。 首先,我們只是創(chuàng)建一個(gè)簡(jiǎn)單的hello world!并...

    xiaolinbang 評(píng)論0 收藏0
  • 從零開始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(八)--制作Starter

    摘要:服務(wù)器相關(guān)配置啟動(dòng)類資源目錄目錄靜態(tài)文件目錄端口號(hào)目錄目錄實(shí)現(xiàn)內(nèi)嵌服務(wù)器在上一章文章從零開始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的框架七實(shí)現(xiàn)已經(jīng)在文件中引入了依賴,所以這里就不用引用了。 spring-boot的Starter 一個(gè)項(xiàng)目總是要有一個(gè)啟動(dòng)的地方,當(dāng)項(xiàng)目部署在tomcat中的時(shí)候,經(jīng)常就會(huì)用tomcat的startup.sh(startup.bat)的啟動(dòng)腳本來啟動(dòng)web項(xiàng)目 而在spring-b...

    AprilJ 評(píng)論0 收藏0
  • 蔡奇:把中關(guān)村率先建成具有全球影響力的科學(xué)城

    摘要:來源標(biāo)題把中關(guān)村率先建成具有全球影響力的科學(xué)城日前,北京市委書記蔡奇到海淀區(qū)就中關(guān)村科學(xué)城規(guī)劃建設(shè)調(diào)查研究。我們必須進(jìn)一步提高站位,努力把中關(guān)村打造成科學(xué)家發(fā)明家創(chuàng)業(yè)者的天堂,率先建成具有全球影響力的科學(xué)城。 來源標(biāo)題:把中關(guān)村率先建成具有全球影響力的科學(xué)城 日前,北京市委書記蔡奇到海淀區(qū)就中關(guān)村科學(xué)城規(guī)劃建設(shè)調(diào)查研究。他強(qiáng)調(diào),建設(shè)全國科技創(chuàng)新中心是首都城市戰(zhàn)略定位也是北京創(chuàng)新發(fā)展的主要...

    Olivia 評(píng)論0 收藏0
  • Thymeleaf 3學(xué)習(xí)筆記

    摘要:目前最新版本作為官方推薦模板引擎,而且支持純?yōu)g覽器展現(xiàn)模板表達(dá)式在脫離運(yùn)行環(huán)境下不污染結(jié)構(gòu)是時(shí)候了解一番了。當(dāng)前數(shù)目,從開始。 Thymeleaf 目前最新版本3.0Thymeleaf作為Spring-Boot官方推薦模板引擎,而且支持純HTML瀏覽器展現(xiàn)(模板表達(dá)式在脫離運(yùn)行環(huán)境下不污染html結(jié)構(gòu)).是時(shí)候了解一番了。 安裝與初始化配置 與Spring集成 org.thyme...

    stonezhu 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<