1 Starter

在开发 SpringBoot 项目的时候,我们常常通过 Maven 导入自动各种依赖,其中很多依赖都是以 xxx-starter 命名的。

像这种 starter 依赖是怎么工作的呢?

2 了解 spring.factories机制

导入一个依赖,我们就可以调用包内的公共类,这是因为公共类可以被异包调用。很多时候我们添加依赖它会自动往我们的主程序注入一些对象或者监听器,这个是怎么做到的?

2.1 不同包路径下的依赖注入

SpringBoot 默认只扫描启动类所在目录里面的对象

而我们导入的依赖是在另外一个包里,SpringBoot 是扫描不到的!

如何让主项目注入(加载)异包对象呢?通常有两种方法:

  • 在启动类上加上@SpringBootApplication注解,配置scanBasePackages属性,指定扫描路径。
  • resources/META-INF目录下创建spring.factories配置文件,在里面配置需要加载的类

2.2 spring.factories 机制

spring.factories机制是springboot的核心基础之一,这可以描述为一种 可插拔结构,模仿自java中的SPI扩展机制。

spring.factories 实现例子

1.在任意一个项目中新建一个starter模块(springboot项目)

导入 springboot 的自动配置依赖,这里我们主要用到它的@Configuration@Bean注解和ApplicationListener监听器接口

<!-- SpringBoot 自动配置 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
</dependency>

2.随便创建一个bean

public class User {
    private String name;
    public void setName(String name) { this.name = name; }
    public String getName() { return name; }
}

3.创建一个初始化监听器

一般的starter会在容器启动时做一些初始化的操作,这里作为演示只打印一句话。

public class ApplicationInitialize implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent applicationEvent) {
        System.out.println("应用初始化");
    }
}

4.创建配置类

这个配置类就是主项目注入starter模块依赖的入口,当它扫描到这个配置类的时候就会加载里面的Bean对象

@Configuration
public class StarterGenericConfig {
    @Bean
    public User getUser() {
        User user = new User();
        user.setName("我来自starter模块");
        return user;
    }
    @Bean
    public ApplicationInitialize getAppli() {
        return new ApplicationInitialize();
    }
}

5.创建spring.factories配置文件

配置类有了,但是因为和主项目不同包启动类它扫描不到,这时我们就要通过spring.factories机制让它能扫描到这个配置类,完成依赖注入。

先在resource资源目录下创建META-INF文件夹,然后创建一个名为spring.factories的文件

内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig

这里需要配置StarterGenericConfig配置类的全限定名。

把这个项目导入主项目(添加到主项目的pom依赖中),运行主项目,看看是否注入成功

这就是starter依赖注入的基本思路,实际可能复杂得多,需要继续摸索。

3 spring.factories 机制的实现源码分析

springframework 框架中有这样一个类

package org.springframework.core.io.support;
public final class SpringFactoriesLoader {
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap();
	...
	/** SpringFactories 
	  * 静态方法, 加载spring.factories文件
	  * */
	private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
       		...
            try {
            	// 通过类加载器加载资源目录下的"META-INF/spring.factories"文件
                Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
                LinkedMultiValueMap result = new LinkedMultiValueMap();
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    while(var6.hasNext()) {
                        Entry<?, ?> entry = (Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] var9 = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        int var10 = var9.length;
                        for(int var11 = 0; var11 < var10;   var11) {
                            String factoryImplementationName = var9[var11];
                            result.add(factoryTypeName, factoryImplementationName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            } catch (IOException var13) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var13);
            }
        }
    }
 	...
}

SpringFactoriesLoaderSpring容器初始化时会加载的一个类,而它的静态方法loadSpringFactories()里会用类加载器去加载资源文件resourece/META-INF/spring.factories,然后读取里面的配置参数(应该都是待加载Bean类的映射数据),用集合封装返回Spring容器,后面应该就是Spring容器加载对应的Bean类。

4 程序运行入口run()

前面知道了Spring容器会加载加载资源文件resourece/META-INF/spring.factories然后加载里面对应的类,那为什么对应的keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration?

先说结论:Spring容器初始化会加载org.springframework.boot.autoconfigure.EnableAutoConfiguration这个类,完了还会去扫描resourece/META-INF/spring.factories加载里面的Bean类,配置文件是键值对形式的,那keyorg.springframework.boot.autoconfigure.EnableAutoConfiguration是因为这个是一个注解,本身就是为了注入拓展类用的,它会在容器初始化或刷新的适当时机注入对应的类。因为扫描不到异包配置类上的@Configuration注解,所以创建了一个@EnableAutoConfiguration注解配合spring.factories配置文件的形式来注入配置类

public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch(); // 创建stopWatch对象
        stopWatch.start(); // 开始计算时间
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();
        this.configureHeadlessProperty(); // 是否使用模拟输入输出设备(默认是,因为服务器不一定有鼠标键盘显示器)
        SpringApplicationRunListeners listeners = this.getRunListeners(args); // 获取并启动监听器
        listeners.starting(); // 获取的监听器为 Event PublishingRunListener,监听并发布启动事件
        Collection exceptionReporters;
        try {
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备应用环境
            ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
            this.configureIgnoreBeanInfo(environment);
            Banner printedBanner = this.printBanner(environment); // 打印 banner
            context = this.createApplicationContext(); 创建容器
            // 加载 SpringFactories 实例(返回的是实例加载的记录、报告)
            exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);
            // 准备上下文环境
            this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            this.refreshContext(context); // 刷新容器
            this.afterRefresh(context, applicationArguments); // 容器刷新后的动作,这里默认没有做任何实现
            stopWatch.stop(); // 停止计算时间
            if (this.logStartupInfo) { 
                (new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);
            }
            listeners.started(context);
            this.callRunners(context, applicationArguments);
        } catch (Throwable var10) {
           ...
        }
        ...
    }

里面有个创建应用容器的方法createApplicationContext(),深入进去发现他是根据webApplicationType类型去决定创建那种容器,而webApplicationType类型在SpringApplication初始化的时候指定。

    protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch(this.webApplicationType) {
                case SERVLET:
                    contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
                    break;
                case REACTIVE:
                    contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
                    break;
                default:
                    contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
                }
            } catch (ClassNotFoundException var3) {
                throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);
            }
        }
		// 反射创建容器实例
        return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
    }

这里一共有三种类型的容器

  • SERVLET 类型创建 AnnotationConfigServletWebServerApplicationContextServlet容器)
  • REACTIVE类型创建AnnotationConfigReactiveWebServerApplicationContextReactive容器)
  • 默认创建AnnotationConfigApplicationContextApplication容器)

SpringApplication的构造器中对webApplicationType类型进行了初始化,默认返回SERVLET 类型。

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        ...
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        ...
    }

也就是说默认创建AnnotationConfigServletWebServerApplicationContextServlet容器)

在上面入口方法run()方法中,有一个refreshContext()方式,这个刷新容器的方法里面

跟踪这个refreshContext()底层是一个refresh()方法,三种容器都分别实现了这个方法

这里着重看ServletWebServerApplicationContext.refresh()

    public final void refresh() throws BeansException, IllegalStateException {
        try { super.refresh(); } catch (RuntimeException var3) { ... }
    }

发现它调用的是父类AbstractApplicationContextrefresh()函数

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) { // 上锁,防止并发
            this.prepareRefresh();                  // 刷新准备工作,记录开始时间,校验配置文件
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory(); // 获取Bean工厂
            this.prepareBeanFactory(beanFactory);   // Bean工厂准备工作,不详谈
            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory); // Spring拓展点之一
                ...
            } catch (BeansException var9) {
                ...
            } finally {
                this.resetCommonCaches();
            }
        }
    }

重点在invokeBeanFactoryPostProcessors(beanFactory)方法上,这是SpringBoot实现Spring拓展的关键节点,这个方法执行时会调用实现了BeanFactoryPostProcessors接口的实现类的postProcessBeanFactory(factory)方法

(也会调用BeanDefinitionRegistryPostProcessor接口的各个实现类的postProcessBeanDefinitionRegistry(registry)方法)

进入invokeBeanFactoryPostProcessors(beanFactory)方法

    public static void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
   			...
            currentRegistryProcessors = new ArrayList();
            // 获取所有 BeanDefinitionRegistryPostProcessor 接口实现类的全限定名集合
            postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); 
            String[] var16 = postProcessorNames;
            var9 = postProcessorNames.length;
            int var10;
            String ppName;
            for(var10 = 0; var10 < var9;   var10) {
                ppName = var16[var10];
                if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                	// 根据 Bean 名获取 Bean 对象放入 currentRegistryProcessors
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory); // 排序,暂时没看,不知道排啥
            registryProcessors.addAll(currentRegistryProcessors);
            // 调用 Bean 定义注册后处理器
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();
			...
	}

每一个Bean类的定义注册是在Spring容器中完成的,在上面invokeBeanFactoryPostProcessors()方法中,通过 Bean 工厂获取 了所有BeanDefinitionRegistryPostProcessor接口的实现类名,然后再通过 invokeBeanDefinitionRegistryPostProcessors()方法调用所有实现类的postProcessBeanDefinitionRegistry()方法去做 Bean 类注册后的相关处理动作。

BeanDefinitionRegistryPostProcessor接口:

public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
	void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry var1) throws BeansException;
}

知道这个有什么用呢?前面我们知道了Springboot容器中的AnnotationConfigServletWebServerApplicationContextServlet容器)是通过反射获取AnnotationConfigServletWebServerApplicationContext的构造器创建实例的,所以我们看看AnnotationConfigServletWebServerApplicationContext的构造器长什么样儿。

    public AnnotationConfigServletWebServerApplicationContext(DefaultListableBeanFactory beanFactory) {
        super(beanFactory);
        this.annotatedClasses = new LinkedHashSet();
        this.reader = new AnnotatedBeanDefinitionReader(this); // 注解 Bean 定义读取器
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

进入AnnotatedBeanDefinitionReader看它的构造器

    public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
        ...
        // 通过 AnnotationConfigUtils 工具注册 注解配置处理器
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

再进入AnnotationConfigUtils 工具的registerAnnotationConfigProcessors()看看它是如何注册的

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
	...
	Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet(8); // 存储 BeanDefinitionHolder 对象的集合
    RootBeanDefinition def;
	if (!registry.containsBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor")) {
		// 创建一个 ConfigurationClassPostProcessor 的 RootBeanDefinition 对象
		def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        // 把 ConfigurationClassPostProcessor 的 RootBeanDefinition 对象装入一个 BeanDefinitionHolder (容器)
        // 并映射名字为 org.springframework.context.annotation.internalConfigurationAnnotationProcessor
        beanDefs.add(registerPostProcessor(registry, def, "org.springframework.context.annotation.internalConfigurationAnnotationProcessor"));
    }
	...
}

在它的registerAnnotationConfigProcessors()方法中看到了它用注册后处理器registerPostProcessor去注册org.springframework.context.annotation.internalConfigurationAnnotationProcessorBeanDefinition(Bean定义描述对象)和BeanDefinitionHolder(Bean定义描述对象容器),然后返回这个BeanDefinitionHolder(Bean定义描述对象容器)存储到beanDefs(Bean定义描述对象容器集合)里面。

到这里就是说明在初始化AnnotationConfigServletWebServerApplicationContextServlet容器)时,会用org.springframework.context.annotation.internalConfigurationAnnotationProcessor这个名字注册ConfigurationClassPostProcessor这个 Bean对象,然后就能根据它的BeanDefinitionHolder(Bean定义描述对象容器)去创建ConfigurationClassPostProcessor对象。

现在问题就来到了ConfigurationAnnotationProcessor对象身上了,为啥要创建它?因为它就是加载spring.factories配置文件的关键。

进入它的postProcessBeanDefinitionRegistry()方法

    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        int registryId = System.identityHashCode(registry);
        if (this.registriesPostProcessed.contains(registryId)) {       // 判断是否有对应的注册记录
            throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against "   registry);
        } else if (this.factoriesPostProcessed.contains(registryId)) { // 是否有对应的 Bean 工厂
            throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against "   registry);
        } else { // 都没有,说明这个 BeanDefinition 没有注册加载过
            this.registriesPostProcessed.add(registryId); // 添加注册记录
            this.processConfigBeanDefinitions(registry);  // 处理这个 BeanDefinition 的配置
        }
    }

深入processConfigBeanDefinitions()看它怎么处理这个 BeanDefinition 的配置

    public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
        List<BeanDefinitionHolder> configCandidates = new ArrayList(); // 待处理配置集合
        String[] candidateNames = registry.getBeanDefinitionNames();
        String[] var4 = candidateNames;
        int var5 = candidateNames.length;
        for(int var6 = 0; var6 < var5;   var6) {
            String beanName = var4[var6];
            BeanDefinition beanDef = registry.getBeanDefinition(beanName); // 根据名称获取 BeanDefinition
            // 判断这个 BeanDefinition 的配置属性是不是空
            if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
                if (this.logger.isDebugEnabled()) {
                	// 如果不是空就说明这个 BeanDefinition 已经被当作配置类处理过了
                    this.logger.debug("Bean definition has already been processed as a configuration class: "   beanDef);
                }
            } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
            	// 如果为空,放入待处理配置集合里等待后续处理
                configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
            }
        }
		// 指令排序相关,不深究
        if (!configCandidates.isEmpty()) {
            configCandidates.sort((bd1, bd2) -> {
                int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
                int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
                return Integer.compare(i1, i2);
            });
            ...
            // 根据环境创建了一个配置类解析器
            ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry);
            Set<BeanDefinitionHolder> candidates = new LinkedHashSet(configCandidates);
            HashSet alreadyParsed = new HashSet(configCandidates.size());

            do {
                parser.parse(candidates); // 解析配置类
                parser.validate();
                Set<ConfigurationClass> configClasses = new LinkedHashSet(parser.getConfigurationClasses());
                configClasses.removeAll(alreadyParsed);
                if (this.reader == null) {
                    this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment, this.importBeanNameGenerator, parser.getImportRegistry());
                }
				...
            } while(!candidates.isEmpty())
            ...
        }
    }

进入ConfigurationClassParser.parse()方法,看看它怎么解析配置类

	public void parse(Set<BeanDefinitionHolder> configCandidates) {
		for (BeanDefinitionHolder holder : configCandidates) {
			BeanDefinition bd = holder.getBeanDefinition();
			try { // 对 BeanDefinition 做解析操作
				if (bd instanceof AnnotatedBeanDefinition) {
					parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
				}
				else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
					parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
				}
				else {
					parse(bd.getBeanClassName(), holder.getBeanName());
				}
			}
			catch (BeanDefinitionStoreException ex) {
				throw ex;
			}
			catch (Throwable ex) {
				throw new BeanDefinitionStoreException(
						"Failed to parse configuration class ["   bd.getBeanClassName()   "]", ex);
			}
		}
		// 延迟导入选择器
		this.deferredImportSelectorHandler.process();
	}

解析发现不了什么线索,进入this.deferredImportSelectorHandler.process()延迟导入选择处理器看看

层层追踪:

this.deferredImportSelectorHandler.process()

DeferredImportSelectorGroupingHandler.processGroupImports()

grouping.getImports()

grouping.getImports()

this.group.selectImports()

追踪到public interface ImportSelector {...}接口

在找到它的实现类AutoConfigurationImportSelector

在实现类AutoConfigurationImportSelector里层层追踪

selectImports()

getAutoConfigurationEntry()

getCandidateConfigurations()

getSpringFactoriesLoaderFactoryClass()

最后追踪到 getSpringFactoriesLoaderFactoryClass()方法

    protected Class<?> getSpringFactoriesLoaderFactoryClass() {
        return EnableAutoConfiguration.class;
    }

里面返回的是一个EnableAutoConfiguration.class类,这个类就是我们在spring.factories配置文件里面配置的org.springframework.boot.autoconfigure.EnableAutoConfiguration

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.echoo.cloud.nacos.starter.config.StarterGenericConfig

到此这篇关于SpringBoot Starter依赖原理与实例详解的文章就介绍到这了,更多相关SpringBoot Starter依赖内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

SpringBoot Starter依赖原理与实例详解的更多相关文章

  1. VUE响应式原理的实现详解

    这篇文章主要为大家详细介绍了VUE响应式原理的实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

  2. SpringBoot本地磁盘映射问题

    这篇文章主要介绍了SpringBoot本地磁盘映射问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源)

    这篇文章主要介绍了java SpringBoot 分布式事务的解决方案(JTA+Atomic+多数据源),文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的小伙伴可以参考一下

  4. 一文搞懂PHP中的DI依赖注入

    依赖注入DI 其实本质上是指对类的依赖通过构造器完成 自动注入。本文将通过一些示例带大家深入了解一下PHP中的DI依赖注入,需要的可以参考一下

  5. SpringBoot整合Javamail实现邮件发送的详细过程

    日常开发过程中,我们经常需要使用到邮件发送任务,比方说验证码的发送、日常信息的通知等,下面这篇文章主要给大家介绍了关于SpringBoot整合Javamail实现邮件发送的详细过程,需要的朋友可以参考下

  6. 五分钟理解keep alive用法及原理

    这篇文章主要为大家介绍了keep alive用法及原理示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  7. SpringBoot详细讲解视图整合引擎thymeleaf

    这篇文章主要分享了Spring Boot整合使用Thymeleaf,Thymeleaf是新一代的Java模板引擎,类似于Velocity、FreeMarker等传统引擎,关于其更多相关内容,需要的小伙伴可以参考一下

  8. Springboot集成mybatis实现多数据源配置详解流程

    在日常开发中,若遇到多个数据源的需求,怎么办呢?通过springboot集成mybatis实现多数据源配置,简单尝试一下,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  9. SpringBoot使用Minio进行文件存储的实现

    本文主要介绍了SpringBoot使用Minio进行文件存储的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  10. 解析SpringBoot中使用LoadTimeWeaving技术实现AOP功能

    这篇文章主要介绍了SpringBoot中使用LoadTimeWeaving技术实现AOP功能,AOP面向切面编程,通过为目标类织入切面的方式,实现对目标类功能的增强,本文给大家介绍的非常详细,需要的朋友可以参考下

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部