欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Seata?AT模式啟動(dòng)過(guò)程圖文示例詳解

 更新時(shí)間:2022年09月30日 16:34:03   作者:夢(mèng)想實(shí)現(xiàn)家_Z  
這篇文章主要為大家介紹了Seata?AT模式啟動(dòng)過(guò)程圖文示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

背景

為了了解Seata AT模式的原理,我通過(guò)源碼解讀的方式畫(huà)出了Seata AT模式啟動(dòng)的圖示:

如果是基于Springboot項(xiàng)目的話(huà),項(xiàng)目啟動(dòng)的使用,一般約定會(huì)先查看spring.factories文件,配置了哪些類(lèi)是需要自動(dòng)裝配的。Seata也是利用了這個(gè)約定,在項(xiàng)目啟動(dòng)的時(shí)候,默認(rèn)會(huì)裝配指定的類(lèi),以完成Seata相關(guān)組件的初始化。

下面我們來(lái)一起根據(jù)源碼解讀Seata AT模式啟動(dòng)流程。

由上圖可知,Seata AT模式可大概分成以下三部分:

1.與底層數(shù)據(jù)庫(kù)打交道的DataSource,這部分功能處理交給了SeataDataSourceAutoConfiguration。

2.處理@GlobalTransactional注解,實(shí)現(xiàn)分布式事務(wù)管理功能,這部分交給SeataAutoConfiguration處理。

3.分支事務(wù)獲取、銷(xiāo)毀全局事務(wù)XID,這部分功能交給HttpAutoConfiguration。

SeataDataSourceAutoConfiguration

首先,我們來(lái)看看Seata是如何處理DataSource的。

// 依賴(lài)DataSource
@ConditionalOnBean(DataSource.class)
// 三個(gè)配置都要為true
@ConditionalOnExpression("${seata.enabled:true} && ${seata.enableAutoDataSourceProxy:true} && ${seata.enable-auto-data-source-proxy:true}")
@AutoConfigureAfter({SeataCoreAutoConfiguration.class})
public class SeataDataSourceAutoConfiguration {
    /**
     * The bean seataAutoDataSourceProxyCreator.
     */
    @Bean(BEAN_NAME_SEATA_AUTO_DATA_SOURCE_PROXY_CREATOR)
    // 可替換
    @ConditionalOnMissingBean(SeataAutoDataSourceProxyCreator.class)
    public SeataAutoDataSourceProxyCreator seataAutoDataSourceProxyCreator(SeataProperties seataProperties) {
        return new SeataAutoDataSourceProxyCreator(seataProperties.isUseJdkProxy(),
            seataProperties.getExcludesForAutoProxying(), seataProperties.getDataSourceProxyMode());
    }
}

1.@ConditionalOnBean(DataSource.class)意味著我們的項(xiàng)目中一定要有DataSource這個(gè)Bean。

2.@ConditionalOnExpression里面表示要滿(mǎn)足以下三個(gè)條件才會(huì)創(chuàng)建SeataDataSourceAutoConfiguration:

seata.enabled=true

seata.enableAutoDataSourceProxy=true

seata.enable-auto-data-source-proxy=true

3.@AutoConfigureAfter表示當(dāng)前Bean創(chuàng)建一定在指定的SeataCoreAutoConfiguration之后。

根據(jù)以上分析,我們?cè)谝隨eata AT模式的時(shí)候,一定要先創(chuàng)建項(xiàng)目的DataSource Bean對(duì)象,其次保證相關(guān)的配置滿(mǎn)足要求,那么才能正確地保證DataSource被Seata代理。

下面繼續(xù)看SeataAutoDataSourceProxyCreator的創(chuàng)建:

@ConditionalOnMissingBean表示這個(gè)Bean的創(chuàng)建其實(shí)是可以開(kāi)發(fā)人員自定義的,如果開(kāi)發(fā)人員沒(méi)有自定義,那么就由Seata自己創(chuàng)建。

SeataAutoDataSourceProxyCreator類(lèi)中,它繼承了AbstractAutoProxyCreator,也就是AOP功能的標(biāo)準(zhǔn)實(shí)現(xiàn)。這個(gè)時(shí)候,我們主要關(guān)注wrapIfNecessary方法的實(shí)現(xiàn):

public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator {
  @Override
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        // 不是DataSource對(duì)象不代理
        if (!(bean instanceof DataSource)) {
            return bean;
        }
        // 如果是DataSource對(duì)象,但是不是SeataDataSourceProxy對(duì)象
        if (!(bean instanceof SeataDataSourceProxy)) {
            // 先調(diào)用父類(lèi)包裝一層
            Object enhancer = super.wrapIfNecessary(bean, beanName, cacheKey);
            // 如果代理后的對(duì)象和代理前的對(duì)象是同一個(gè)對(duì)象
            // 說(shuō)明要么這個(gè)對(duì)象之前已經(jīng)被代理過(guò)
            // 要么SeataDataSourceProxy被開(kāi)發(fā)人員excluded
            if (bean == enhancer) {
                return bean;
            }
            // 如果是正常的DataSource對(duì)象的話(huà),那么就會(huì)被自動(dòng)構(gòu)建成SeataDataSourceProxy,并返回
            DataSource origin = (DataSource) bean;
            SeataDataSourceProxy proxy = buildProxy(origin, dataSourceProxyMode);
            DataSourceProxyHolder.put(origin, proxy);
            return enhancer;
        }
        /*
         * things get dangerous when you try to register SeataDataSourceProxy bean by yourself!
         * if you insist on doing so, you must make sure your method return type is DataSource,
         * because this processor will never return any subclass of SeataDataSourceProxy
         */
        // Seata是不建議用戶(hù)自己構(gòu)建SeataDataSourceProxy對(duì)象的,即使用戶(hù)自己構(gòu)建了SeataDataSourceProxy對(duì)象,Seata也會(huì)重新處理
        LOGGER.warn("Manually register SeataDataSourceProxy(or its subclass) bean is discouraged! bean name: {}", beanName);
        // 獲取用戶(hù)包裝好的代理對(duì)象
        SeataDataSourceProxy proxy = (SeataDataSourceProxy) bean;
        // 獲取原生DataSource
        DataSource origin = proxy.getTargetDataSource();
        // 重新包裝,并返回
        Object originEnhancer = super.wrapIfNecessary(origin, beanName, cacheKey);
        // this mean origin is either excluded by user or had been proxy before
        if (origin == originEnhancer) {
            return origin;
        }
        // else, put <origin, proxy> to holder and return originEnhancer
        DataSourceProxyHolder.put(origin, proxy);
        // 返回包裝好的代理對(duì)象SeataDataSourceProxy
        return originEnhancer;
    }
}

1.通過(guò)以上代碼解讀,有一個(gè)點(diǎn)我們需要注意,就是開(kāi)發(fā)人員不需要自己的構(gòu)建SeataDataSourceProxy對(duì)象,使用原生的DataSource即可,Seata會(huì)幫助我們構(gòu)建SeataDataSourceProxy對(duì)象。

SeatAutoConfiguration

SeatAutoConfiguration主要功能就是創(chuàng)建GlobalTransactionScanner對(duì)象,所以核心功能全部在GlobalTransactionScanner里面。

// 配置seata.enabled=true
@ConditionalOnProperty(prefix = SEATA_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)
// 裝配順序
@AutoConfigureAfter({SeataCoreAutoConfiguration.class})
public class SeataAutoConfiguration {
    private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoConfiguration.class);
    @Bean(BEAN_NAME_FAILURE_HANDLER)
    // 失敗處理器,可替換
    @ConditionalOnMissingBean(FailureHandler.class)
    public FailureHandler failureHandler() {
        return new DefaultFailureHandlerImpl();
    }
    @Bean
    @DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})
    // 開(kāi)發(fā)人員可自定義GlobalTransactionScanner
    @ConditionalOnMissingBean(GlobalTransactionScanner.class)
    public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler,
            ConfigurableListableBeanFactory beanFactory,
            @Autowired(required = false) List<ScannerChecker> scannerCheckers) {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Automatically configure Seata");
        }
        // set bean factory
        GlobalTransactionScanner.setBeanFactory(beanFactory);
        // add checkers
        // '/META-INF/services/io.seata.spring.annotation.ScannerChecker'
        GlobalTransactionScanner.addScannerCheckers(EnhancedServiceLoader.loadAll(ScannerChecker.class));
        // spring beans
        GlobalTransactionScanner.addScannerCheckers(scannerCheckers);
        // add scannable packages
        GlobalTransactionScanner.addScannablePackages(seataProperties.getScanPackages());
        // add excludeBeanNames
        GlobalTransactionScanner.addScannerExcludeBeanNames(seataProperties.getExcludesForScanning());
        //set accessKey and secretKey
        GlobalTransactionScanner.setAccessKey(seataProperties.getAccessKey());
        GlobalTransactionScanner.setSecretKey(seataProperties.getSecretKey());
        // create global transaction scanner
        return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup(), failureHandler);
    }
}

1.裝配SeataAutoConfiguration要求配置中seata.enabled=true;

2.我們可以自定義FailureHandler;這個(gè)失敗處理器是專(zhuān)門(mén)給TM使用的;

3.同樣我們也可以自定義GlobalTransactionScanner,不過(guò)基本上不會(huì)這么做,除非有特殊需求;

GlobalTransactionScanner里面基本上做兩個(gè)事情:

  • 代理所有被@GlobalTransactional@GlobalLock注解的方法;
  • 使用Neety初始化TM ClientRM Client,以便實(shí)現(xiàn)和TC通信;TC也就是我們的Seata Server
public class GlobalTransactionScanner extends AbstractAutoProxyCreator
        implements ConfigurationChangeListener, InitializingBean, ApplicationContextAware, DisposableBean {
  
}
  • AbstractAutoProxyCreator:通過(guò)wrapIfNecessary方法代理所有被@GlobalTransactional@GlobalLock注解的方法;
  • ConfigurationChangeListener:通過(guò)onChangeEvent方法監(jiān)聽(tīng)配置service.disableGlobalTransaction的變化;
  • InitializingBean:通過(guò)afterPropertiesSet方法初始化TM ClientRM Client;
  • ApplicationContextAware:通過(guò)setApplicationContext方法獲取IOC容器;
  • DisposableBean:當(dāng)GlobalTransactionScanner被銷(xiāo)毀時(shí),通過(guò)destroy方法來(lái)回收資源;

我們重點(diǎn)關(guān)注wrapIfNecessaryafterPropertiesSet方法:

@Override
    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        // 檢查Bean是否符合被代理的要求
        // 1. 不能是配置類(lèi),比如以Configuration、Properties、Config結(jié)尾的Bean名稱(chēng)
        // 2. Bean所在的包名在掃描范圍內(nèi)
        // 3. 不能被@Scope注解
        if (!doCheckers(bean, beanName)) {
            return bean;
        }
        try {
            synchronized (PROXYED_SET) {
                // 如果已經(jīng)被代理,就跳過(guò)
                if (PROXYED_SET.contains(beanName)) {
                    return bean;
                }
                interceptor = null;
                // 檢查是否被TCC注解
                if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
                    // 初始化TCC Fence Clean Task
                   TCCBeanParserUtils.initTccFenceCleanTask(TCCBeanParserUtils.getRemotingDesc(beanName), applicationContext);
                    // 創(chuàng)建TCC代理類(lèi)
                    interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
                    ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                            (ConfigurationChangeListener)interceptor);
                } else {
                    // 如果不是TCC代理,那么先獲取當(dāng)前類(lèi)和它實(shí)現(xiàn)的接口
                    Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
                    Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);
                    // 判斷當(dāng)前類(lèi)及相關(guān)方法是否被@GlobalTransactional或@GlobalLock注解
                    if (!existsAnnotation(new Class[]{serviceInterface})
                        && !existsAnnotation(interfacesIfJdk)) {
                        // 沒(méi)有被注解,不代理
                        return bean;
                    }
                  
                    // 準(zhǔn)備創(chuàng)建方法攔截器
                    if (globalTransactionalInterceptor == null) {
                        globalTransactionalInterceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
                        ConfigurationCache.addConfigListener(
                                ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                                (ConfigurationChangeListener)globalTransactionalInterceptor);
                    }
                    // 攔截器創(chuàng)建完畢
                    interceptor = globalTransactionalInterceptor;
                }
                LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]", bean.getClass().getName(), beanName, interceptor.getClass().getName());
                // 如果bean不是代理對(duì)象,那么不做方法攔截,直接返回
                if (!AopUtils.isAopProxy(bean)) {
                    bean = super.wrapIfNecessary(bean, beanName, cacheKey);
                } else {
                    // 準(zhǔn)備把方法攔截器插入進(jìn)去
                    AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
                    // 獲取所有的方法攔截器,包括GlobalTransactionalInterceptor
                    Advisor[] advisor = buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
                    int pos;
                    // 依次添加進(jìn)目標(biāo)對(duì)象中
                    for (Advisor avr : advisor) {
                        // Find the position based on the advisor's order, and add to advisors by pos
                        pos = findAddSeataAdvisorPosition(advised, avr);
                        advised.addAdvisor(pos, avr);
                    }
                }
                PROXYED_SET.add(beanName);
                // 返回被代理的bean
                return bean;
            }
        } catch (Exception exx) {
            throw new RuntimeException(exx);
        }
    }

通過(guò)上述源碼分析可知:Seata是根據(jù)類(lèi)、接口和方法上的@GlobalTransactional@GlobalLock注解來(lái)判斷是否需要針對(duì)目標(biāo)方法做攔截的。

@Override
    public void afterPropertiesSet() {
        // 如果不允許全局事務(wù)
        if (disableGlobalTransaction) {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info("Global transaction is disabled.");
            }
          // 添加監(jiān)聽(tīng)器,監(jiān)聽(tīng)配置的變化
            ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
                    (ConfigurationChangeListener)this);
            return;
        }
        
        if (initialized.compareAndSet(false, true)) {
          // 準(zhǔn)備初始化TM Client、RM Client
            initClient();
        }
    }
private void initClient() {
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Initializing Global Transaction Clients ... ");
        }
        if (DEFAULT_TX_GROUP_OLD.equals(txServiceGroup)) {
            LOGGER.warn("the default value of seata.tx-service-group: {} has already changed to {} since Seata 1.5, " +
                    "please change your default configuration as soon as possible " +
                    "and we don't recommend you to use default tx-service-group's value provided by seata",
                    DEFAULT_TX_GROUP_OLD, DEFAULT_TX_GROUP);
        }
        if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
            throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));
        }
        //初始化TM Client
        TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
        }
        //初始化RM Client
        RMClient.init(applicationId, txServiceGroup);
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
        }
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Global Transaction Clients are initialized. ");
        }
        registerSpringShutdownHook();
    }

至此,SeatAutoConfiguration的工作處理完畢;

HttpAutoConfiguration

HttpAutoConfiguration的工作比較簡(jiǎn)單,我們想象一下,RM如何知道它屬于哪一個(gè)分布式事務(wù)?這就需要一個(gè)統(tǒng)一的標(biāo)識(shí)來(lái)決定所有的分支事務(wù)都屬于同一個(gè)分布式事務(wù),這個(gè)標(biāo)識(shí)在Seata中叫做XID;

XID由TM開(kāi)啟分布式事務(wù)時(shí)生成,通過(guò)RPC的方式從一個(gè)分支事務(wù)傳遞到另一個(gè)分支事務(wù),所以我們?cè)赗M端需要一個(gè)從RPC中解析獲取XID的功能,以及在業(yè)務(wù)邏輯處理完畢后,銷(xiāo)毀當(dāng)前線(xiàn)程中XID的功能。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
public class HttpAutoConfiguration extends WebMvcConfigurerAdapter {
    // 注冊(cè)攔截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TransactionPropagationInterceptor());
    }
  
    // 添加異常解析處理器
    @Override
    public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        exceptionResolvers.add(new HttpHandlerExceptionResolver());
    }
}
public class TransactionPropagationInterceptor extends HandlerInterceptorAdapter {
    private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationInterceptor.class);
    // 前置處理邏輯
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 獲取當(dāng)前線(xiàn)程XID
        String xid = RootContext.getXID();
        // 從rpc中獲取XID
        String rpcXid = request.getHeader(RootContext.KEY_XID);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("xid in RootContext[{}] xid in HttpContext[{}]", xid, rpcXid);
        }
        // 如果線(xiàn)程中沒(méi)有XID,并且從請(qǐng)求中拿到了XID,那么把請(qǐng)求中的XID綁定到當(dāng)前線(xiàn)程
        if (StringUtils.isBlank(xid) && StringUtils.isNotBlank(rpcXid)) {
            RootContext.bind(rpcXid);
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("bind[{}] to RootContext", rpcXid);
            }
        }
        return true;
    }
    // 后置處理邏輯
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 業(yè)務(wù)邏輯處理完畢,從當(dāng)前線(xiàn)程中刪除XID
        if (RootContext.inGlobalTransaction()) {
            XidResource.cleanXid(request.getHeader(RootContext.KEY_XID));
        }
    }
}
public class HttpHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
    // 發(fā)生異常后,刪除當(dāng)前線(xiàn)程中的XID
    @Override
    protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse httpServletResponse, Object o, Exception e) {
        XidResource.cleanXid(request.getHeader(RootContext.KEY_XID));
        return null;
    }
}

小結(jié)

通過(guò)以上源碼分析和圖解Seata AT模式,我們可以了解到以下幾點(diǎn):

1.GlobalTransactionInterceptor屬于TM側(cè),它主要負(fù)責(zé)通過(guò)TM Client開(kāi)啟分布式事務(wù)、提交分布式事務(wù)以及回滾分布式事務(wù);屬于大總管。

2.SeataDataSourceProxy屬于RM側(cè),它主要負(fù)責(zé)分支事務(wù)的開(kāi)啟,提交以及回滾,屬于真正干活的小兵。

3.TM ClientRM Client純屬于兩個(gè)通信工具,負(fù)責(zé)與TC端建立通信。

4.TransactionPropagationInterceptorHttpHandlerExceptionResolver服務(wù)于分支事務(wù),負(fù)責(zé)全局事務(wù)XID的獲取以及業(yè)務(wù)邏輯處理完畢的善后事宜。

以上就是Seata AT模式啟動(dòng)過(guò)程圖文示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Seata AT模式啟動(dòng)過(guò)程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JDK與JRE的下載和安裝以及配置JDK環(huán)境變量圖文教程

    JDK與JRE的下載和安裝以及配置JDK環(huán)境變量圖文教程

    JRE也就是(Java?RuntimeEnvironment)Java運(yùn)行環(huán)境,是運(yùn)行JAVA程序所必須的環(huán)境的集合,包含各種類(lèi)庫(kù),下面這篇文章主要給大家介紹了關(guān)于JDK與JRE的下載和安裝以及配置JDK環(huán)境變量的相關(guān)資料,需要的朋友可以參考下
    2023-12-12
  • RabbitMQ交換機(jī)與Springboot整合的簡(jiǎn)單實(shí)現(xiàn)

    RabbitMQ交換機(jī)與Springboot整合的簡(jiǎn)單實(shí)現(xiàn)

    這篇文章主要介紹了RabbitMQ交換機(jī)與Springboot整合的簡(jiǎn)單實(shí)現(xiàn),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-07-07
  • Java數(shù)據(jù)結(jié)構(gòu)之線(xiàn)索化二叉樹(shù)的實(shí)現(xiàn)

    Java數(shù)據(jù)結(jié)構(gòu)之線(xiàn)索化二叉樹(shù)的實(shí)現(xiàn)

    在二叉樹(shù)的結(jié)點(diǎn)上加上線(xiàn)索的二叉樹(shù)稱(chēng)為線(xiàn)索二叉樹(shù),對(duì)二叉樹(shù)以某種遍歷方式進(jìn)行遍歷,使其變?yōu)榫€(xiàn)索二叉樹(shù)的過(guò)程稱(chēng)為對(duì)二叉樹(shù)進(jìn)行線(xiàn)索化。本文將詳解如何實(shí)現(xiàn)線(xiàn)索化二叉樹(shù),需要的可以參考一下
    2022-05-05
  • 使用MockMvc進(jìn)行controller層單元測(cè)試 事務(wù)自動(dòng)回滾的完整案例

    使用MockMvc進(jìn)行controller層單元測(cè)試 事務(wù)自動(dòng)回滾的完整案例

    這篇文章主要介紹了使用MockMvc進(jìn)行controller層單元測(cè)試 事務(wù)自動(dòng)回滾的完整案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Java中打jar包以及如何調(diào)用包方法演示

    Java中打jar包以及如何調(diào)用包方法演示

    這篇文章主要給大家介紹了關(guān)于Java中打jar包以及如何調(diào)用包的相關(guān)資料,jar包的全稱(chēng)是java archive,jar包本質(zhì)就是一種壓縮包,在Java開(kāi)發(fā)中一般是用來(lái)壓縮類(lèi)的一個(gè)包,需要的朋友可以參考下
    2023-09-09
  • myeclipse安裝Spring Tool Suite(STS)插件的方法步驟

    myeclipse安裝Spring Tool Suite(STS)插件的方法步驟

    這篇文章主要介紹了myeclipse安裝Spring Tool Suite(STS)插件的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • Java返回文件時(shí)為圖片或pdf等設(shè)置在線(xiàn)預(yù)覽或下載功能

    Java返回文件時(shí)為圖片或pdf等設(shè)置在線(xiàn)預(yù)覽或下載功能

    這篇文章主要介紹了Java返回文件時(shí)為圖片或pdf等設(shè)置在線(xiàn)預(yù)覽或下載功能,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-01-01
  • Spring AOP實(shí)現(xiàn)功能權(quán)限校驗(yàn)功能的示例代碼

    Spring AOP實(shí)現(xiàn)功能權(quán)限校驗(yàn)功能的示例代碼

    本篇文章主要介紹了Spring AOP實(shí)現(xiàn)功能權(quán)限校驗(yàn)功能的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12
  • SpringBoot中利用MyBatis進(jìn)行數(shù)據(jù)操作的示例

    SpringBoot中利用MyBatis進(jìn)行數(shù)據(jù)操作的示例

    這篇文章主要介紹了SpringBoot中利用MyBatis進(jìn)行數(shù)據(jù)操作,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • springboot中使用rabbitt的詳細(xì)方法

    springboot中使用rabbitt的詳細(xì)方法

    這篇文章主要介紹了springboot中使用rabbitt,通過(guò)本文學(xué)習(xí)讓我們了解如何在Spring Boot中使用RabbitMQ,并使用不同的交換機(jī)和隊(duì)列類(lèi)型以及消息確認(rèn)模式,需要的朋友可以參考下
    2023-05-05

最新評(píng)論