Hefery 的个人网站

Hefery's Personal Website

Contact:hefery@126.com
  menu
73 文章
0 浏览
1 当前访客
ღゝ◡╹)ノ❤️

Spring基础—必知必会

Spring简介

Spring理解

Spring:轻量级的开源框架,是核心容器Core、数据访问与集成、AOP、Web、消息Message、测试Test 模块的集成,主要是为简化企业级应用的后台开发,降低耦合性。平时接触到最多的还是 IoC 和 AOP 两个特性

IoC指的是控制反转,把对象的创建和依赖关系的维护交给 Spring 容器去管理。Spring 通过工厂模式、反射机制等技术管理对象 Bean 的作用域和生命周期

AOP 被称为面向切面编程,面向对象的一种补充,将程序中独立于其他功能的方法抽取出来,使 Java 开发模块化,仅需专注于主业务即可

Spring设计

  • 用于构造 JAVA 应用程序的轻量级框架
  • 可以采用 Spring 来构造任何程序,而不局限于Web程序
  • 轻量级:最少的侵入,与应用程序低耦合,接入成本低
  • 最直观的感受:基于 POJO,构建出稳健而强大的应用

设计模式:

  • 工厂模式:在各种 BeanFactory 以及 ApplicationContext 创建中都用到了
  • 模版模式:在各种 BeanFactory 以及 ApplicationContext 实现中也都用到了(空方法-实现定制化)
  • 代理模式:AOP利用了AspectyAOP实现的!AspectJAOP的底层用了动态代理
  • 策略模式:加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的接口Resource;在Aop的实现中,采用了两种不同的方式,JDK动态代理和CGLIB代理
  • 单例模式:创建 Bean
  • 观察者模式:Spring 中的 ApplicationEvent,ApplicationListener,ApplicationEventPublisher
  • 适配器模式:MethodBeforeAdviceAdapter,ThrowsAdviceAdapter,AfterReturningAdapter
  • 装饰者模式:源码中类型带 Wrapper 或 Decorator

Spring优势

  • 独立于各种应用服务器,基于 Spring 框架(loC 和 AOP 的容器框架)应用
  • Spring 的 loC(控制反转) 降低了业务对象替换的复杂性,提高了组件之间的解耦(通过依赖注入和面向接口实现松耦合;基于 POJO 的轻量级和最小侵入性编程)
  • Spring 的 AOP(面向切面编程) 支持将一些通用任务如安全、事务、日志等进行集中式处理,提供更好的复用(基于切面和惯例进行声明式编程;通过切面和模板减少样板式代码)
  • Spring 的 ORM 和 DAO 提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问
  • Spring 的高度开放性,并不强制应用完全依赖于 Spring,开发者可自由选用 Spring 框架的部分或全部

项目开放快速建立社区

  • 详尽的文档
  • 快速方便地集成项目用到的技术
  • 为各大技术领域提供支持:微服务、社交 API 集成、云计算、快捷开发、安全管理

Spring架构

Spring模块组成(框架组成、整体架构、体系架构、体系结构)_ThinkWon的博客-CSDN博客_spring组件有哪些

  • spring-core
    包含框架基本的核心工具类,其它组件要都要使用到这个包里的类,主要提供 IoC 依赖注入功能
    作用:定义并提供资源的访问方式
  • spring-beans(BeanFactory)
    Spring主要面向Bean编程(BOP)
    作用:Bean的定义、解析、创建
  • spring-context(ApplicationContext)
    作用:为 Spring 提供运行时环境,保存对象的状态
  • spring-aop
    提供了面向方面的编程实现
  • spring web
    提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext
  • spring test
    主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试

Spring

spring-core-5.3.15

控制反转—loC

IoC相关概念

loC:Inversion of Control,一种设计思想,将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理,loC是一种编程思想,由主动的编程变成被动的接收。DI依赖注入是其最重要的实现方式之一

IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个Map(key,valu),Map 中存放的是各种对象。将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注入。这样可以很大程度上简化应用的开发,把应用从复杂的依赖关系中解放出。IoC 容器就像是一个工厂一样,当需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是loC容器,其实现方法是依赖注入(Dependency Injection,DI)

作用:

  • 将对象的创建、依赖关系的管理以及生命周期交由 loC 容器管理
  • 降低系统在实现上的复杂性和耦合度,易于扩展,满足开闭原则

实现:(实现原理:工厂模式+反射机制)

  • DI,Dependency Inversion:依赖注入
  • DL,Dependency Lookup:依赖查找

优势:

  • 避免在各处使用 new 来创建类,并且可以做到统一维护
  • 创建实例的时候不需要了解其中的细节
  • 反射+工厂模式的合体,满足开闭原则

在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。其实就是把对象交由 Spring 创建、管理、装配

IoC实现方式

依赖注入—DI

依赖注入(Dependency Inversion,DI)实现控制反转

  • 依赖:Bean对象的创建依赖容器
  • 注入:Bean对象的所有属性由容器注入

image.png

改变传统的上层建筑依赖下层建筑,而是把底层类作为参数传递给上层类,实现上层对下层的“控制"

loC注入方式实现:

  • Setter注入,Setter Injection(<property>)
  • 接口注入,Interface Injection
  • 构造器注入,Constructor Injection(<constructor-arg>)
  • 注解注入,Annotation Injection

推荐:构造器参数实现强制依赖, Setter方法实现可选依赖

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <bean id="user" class="com.hefery.kafkademo.spring.User" name="user1,user2" scope="singleton">
        <!-- 构造器注入 -->
        <constructor-arg name="name" value="Hefery"/>
        <constructor-arg name="age" value="12"/>
        <!-- Setter注入 -->
        <property name="name" value="Hefery"/>
        <property name="age" value="12"/>
    </bean>

</beans>

依赖倒置原则、loC、DI、loC容器的关系

未命名文件.png

依赖查找—DL

IoC容器初始化

IoC容器初始化过程

applicationContext.xml -ApplicationContext-> BeanDefinition Map -BeanFactory-> Beans

IoC容器初始化方式
  • BeanFactory接口
  • ApplicationContext应用上下文:除 BeanFactory 功能外,还包括 ClassPathXmlApplicationContextFileSystemXmlApplicationContextXmlWebApplicationContext

loC容器的初始化:创建BeanFactoryApplicationContext的实现类

BeanFactory和ApplicationContext区别:

  • BeanFactory:Spring 框架的基础设施,面向 Spring 本身
    Spring 最低层接口,提供最简单的容器的功能,而且只提供了实例化对象和拿对象的功能
    装载 Bean 时,延迟加载,在启动的时不去实例化 Bean,当有从容器拿 Bean 的时候才会去实例化
  • ApplicationContext:面向使用Spring 框架的开发者
    应用上下文,继承 BeanFactory 接口,Spring 更高级的容器,提供更多有用的功能,包括继承 MessageSource 实现国际化访问资源、统一的资源文件访问方式、提供在监听器中注册Bean 的事件、同时加载多个配置文件
    ApplicationContext 在启动时就把所有的 Bean 全部实例化,还可通过配置来实现 Bean 的延迟实例化

实际开发中推荐使用 ApplicationContext。相对于基本的 BeanFactory,Applicationcontext 唯一的不足是占用内存空间。当应用程序配置 Bean 较多时,程序启动较慢

ApplicationContext
public interface ApplicationContext 
    extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, 
    MessageSource,  ApplicationEventPublisher, ResourcePatternResolver {
    ...
}

核心接口:

  • ListableBeanFactory:支持获取 bean 工厂的所有 bean 实例
  • HierarchicalBeanFactory:支持继承关系

拓展接口:

  • MessageSource:提供国际化支持
  • ApplicationEventPublisher:支持事件驱动模型中的事件发布器,这些事件和 Bean 的生命周期的结合为Bean 的管理提供便利
  • ResourcePatternResolver:资源解析器

常见实现类:

  • FileSystemXmlApplicationContext:从指定文件地址的加载 xml 定义的 Bean
  • ClassPathXmlApplicationContext:从类路径下载入 xml 定义的 Bean
  • XmlWebApplicationContext:Web 应用程序的范围内载入 xml 定义的 Bean

重点关注 ApplicationContextClassPathXmlApplicationContext

ClassPathXmlApplicationContext类图

ClassPathXmlApplicationContext:从类路径下载入xml定义的Bean

AbstractXmlApplicationContext:支持解析Bean定义文件

AbstractRefreshableConfigApplicationContext:保存配置文件路径

AbstractRefreshableApplicationContext:支持刷新BeanFactory

AbstractApplicationContext:完成大部分的 loC 容器初始化工作,同时也提供了扩展接口留给子类去重载。该类的refresh()函数是核心初始化操作

DefaultResourceLoader:设置 classLoader,并将配置文件封装为 Resource 文件

从类路径下读取 Bean 的配置文件 beans.xml,就可以实现 IoC 容器的初始化

  • 定位:通过 Resource 定位 BeanDefinition
  • 载入 BeanDefinition
  • BeanDefinition解析和注册

ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");

public class ClassPathXmlApplicationContext extends AbstractXmlApplicationContext {

    public ClassPathXmlApplicationContext(String configLocation) throws BeansException {
        this(new String[]{configLocation}, true, (ApplicationContext)null);
    }

    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
        // 把 ApplicationContext 作为父容器,实际传 null
        super(parent);
        // 替换${}后设置配置路径
        this.setConfigLocations(configLocations);
        if (refresh) {
            // 核心方法
            this.refresh();
        }

    }

}

1 super(parent);

2 this.setConfigLocations(configLocations);

3 调用ClassPathXmlApplicationContext(String configLocation),初始化 ApplicationContext,设置好 Bean 的配置文件 beans.xml,调用核心方法 refresh()

这个 refresh() 实际是 AbstractApplicationContext 的 refresh(),为上下文环境刷新 Bean

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            // 准备刷新的上下文环境,例如对系统属性或者环境变量进行准备及验证
            StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
            this.prepareRefresh();
            // 启动子类的 refreshBeanFactory 方法,解析xml
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            // 为 BeanFactory 配置容器特性,例如类加载器、事件处理器等
            this.prepareBeanFactory(beanFactory);

            try {
                // 设置BeanFactory的后置处理. 空方法,留给子类拓展用
                this.postProcessBeanFactory(beanFactory);
                StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
                // 调用 BeanFactory 的后处理器, 这些后处理器是在Bean定义中向容器注册的
                this.invokeBeanFactoryPostProcessors(beanFactory);
                // 注册 Bean 的后处理器, 在Bean创建过程中调用
                this.registerBeanPostProcessors(beanFactory);
                beanPostProcess.end();
                // 初始化上下文中的消息源,即不同语言的消息体进行国际化处理 
                this.initMessageSource();
                // 初始化 ApplicationEventMulticaster bean,应用事件广播器
                this.initApplicationEventMulticaster();
                // 初始化其它特殊的Bean, 空方法,留给子类拓展用
                this.onRefresh();
                // 检查并向容器注册监听器Bean
                this.registerListeners();
                // 实例化所有剩余的(non-lazy-init) ,里面的 preInstantiateSingletons 会完成单例对象的创建
                this.finishBeanFactoryInitialization(beanFactory);
                // 发布容器事件, 结束refresh过程
                this.finishRefresh();
            } catch (BeansException var10) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var10);
                }
                // 销毁已经创建的单例 Bean, 以避免资源占用
                this.destroyBeans();
                // 取消 refresh 操作, 重置active标志
                this.cancelRefresh(var10);
                throw var10;
            } finally {
                // 重置 Spring 的核心缓存
                this.resetCommonCaches();
                contextRefresh.end();
            }

        }
    }

}

3.1 其中启动子类的 refreshBeanFactory 方法,解析xml:ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();,实际上 obtainFreshBeanFactory 方法调用了 DefaultListableBeanFactory 定位 resources 资源,创建bean工厂,如果已经有则销毁,没有则创建里面。实现对 BeanDefinition 的装载

public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        this.refreshBeanFactory();   // protected abstract void refreshBeanFactory();
        return this.getBeanFactory();  // public abstract ConfigurableListableBeanFactory getBeanFactory();
    }

}

3.1.1 向下找 refreshBeanFactory() 的具体实现

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {

    protected final void refreshBeanFactory() throws BeansException {
        if (this.hasBeanFactory()) {
            this.destroyBeans();
            this.closeBeanFactory();
        }

        try {
        // 创建并设置DefaultListableBeanFactory同时调用loadBeanDefinitions载入loadBeanDefinition
            DefaultListableBeanFactory beanFactory = this.createBeanFactory();
            beanFactory.setSerializationId(this.getId());
            this.customizeBeanFactory(beanFactory);
        // 核心方法
            this.loadBeanDefinitions(beanFactory);  // protected abstract void loadBeanDefinitions(DefaultListableBeanFactory beanFactory)
            this.beanFactory = beanFactory;
        } catch (IOException var2) {
            throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var2);
        }
    }

}

3.1.1.1 载入 BeanDefinition 的核心方法 loadBeanDefinitions(beanFactory)

载入方式:

  • 模板匹配多资源,生成Resource[]
  • 载入单个资源url绝对地址,生成一个Resource
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader, EnvironmentCapable {

    public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
        // 取ResourceLoader资源加载器
        ResourceLoader resourceLoader = this.getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException("Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
        } else {
            int count;
            // 匹配模板解析 ClassPathXmlApplicationContext是ResourcePatternResolver接口的实例
            if (resourceLoader instanceof ResourcePatternResolver) {  // 模板匹配多资源,生成Resource[]
                try {
                    Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
                    count = this.loadBeanDefinitions(resources);
                    if (actualResources != null) {
                        Collections.addAll(actualResources, resources);
                    }

                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
                    }

                    return count;
                } catch (IOException var6) {
                    throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var6);
                }
            } else { // 载入单个资源url绝对地址
                Resource resource = resourceLoader.getResource(location);
                count = this.loadBeanDefinitions((Resource)resource);
                if (actualResources != null) {
                    actualResources.add(resource);
                }

                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
                }

                return count;
            }
        }
    }

}

定位:通过 Resource 定位 BeanDefinition

具体实现在 AbstractXmlApplicationContext 中,定义 Reader 作为入参执行载入过 BeanDefinition 载入

public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {

    protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
        // 为给定的bean工厂创建一个reader
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
        // 使用此上下文的资源加载环境配置 beanDefinitionReader
        beanDefinitionReader.setEnvironment(this.getEnvironment());
        beanDefinitionReader.setResourceLoader(this);
        beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
        // 允许子类提供 Reader 的自定义初始化,然后继续实际加载 bean 定义
        this.initBeanDefinitionReader(beanDefinitionReader);
    // 核心方法
        this.loadBeanDefinitions(beanDefinitionReader);
    }

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    // 模板方法设计模式,具体的实现由子类完成,实际上这里getConfigResources调用的就是子类ClassPathXmlApplicationContext的getConfigResources方法
        // ClassPathXmlApplicationContext继承了DefaultResourceLoader,具备了Resource加载资源的功能
    Resource[] configResources = this.getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }

        String[] configLocations = this.getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }

    }

}

loadBeanDefinitions最终调用 XmlBeanDefinitionReader.doLoadBeanDefinitions()

  1. 调用 xml 解析器得到的 document 对象,但是这个对象并没有按照 Spring 的 bean 规则进行解析
  2. DefaultBeanDefinitionDocumentReader 的 registerBeanDefinitions 按 Spring 的 Bean 规则进行解析
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) throws BeanDefinitionStoreException {
        try {
            // 取得xml文件的Document,解析过程是由DocumentLoader完成,默认为DefaultDocumentLoader
            Document doc = this.doLoadDocument(inputSource, resource);
            // 启动对BeanDefinition的详细解析过程,这个解析会使用到 Spring 的 Bean 配置规则
            int count = this.registerBeanDefinitions(doc, resource);
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Loaded " + count + " bean definitions from " + resource);
            }

            return count;
        } catch (BeanDefinitionStoreException var5) {
            throw var5;
        } catch (SAXParseException var6) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "Line " + var6.getLineNumber() + " in XML document from " + resource + " is invalid", var6);
        } catch (SAXException var7) {
            throw new XmlBeanDefinitionStoreException(resource.getDescription(), "XML document from " + resource + " is invalid", var7);
        } catch (ParserConfigurationException var8) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Parser configuration exception parsing XML from " + resource, var8);
        } catch (IOException var9) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "IOException parsing XML document from " + resource, var9);
        } catch (Throwable var10) {
            throw new BeanDefinitionStoreException(resource.getDescription(), "Unexpected exception parsing XML document from " + resource, var10);
        }
    }

    public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
        BeanDefinitionDocumentReader documentReader = this.createBeanDefinitionDocumentReader();
        int countBefore = this.getRegistry().getBeanDefinitionCount();
    // 核心方法
        documentReader.registerBeanDefinitions(doc, this.createReaderContext(resource));
        return this.getRegistry().getBeanDefinitionCount() - countBefore;
    }

}

BeanDefinition解析和注册

registerBeanDefinitions() 方法调用了 doRegisterBeanDefinitions()

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {

    protected void doRegisterBeanDefinitions(Element root) {
        BeanDefinitionParserDelegate parent = this.delegate;
        this.delegate = this.createDelegate(this.getReaderContext(), root, parent);
        if (this.delegate.isDefaultNamespace(root)) {
            String profileSpec = root.getAttribute("profile");
            if (StringUtils.hasText(profileSpec)) {
                String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, ",; ");
                if (!this.getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {
                    if (this.logger.isDebugEnabled()) {
                        this.logger.debug("Skipped XML bean definition file due to specified profiles [" + profileSpec + "] not matching: " + this.getReaderContext().getResource());
                    }

                    return;
                }
            }
        }

        this.preProcessXml(root);
        // 从Document的根元素开始进行Bean定义的Document对象
        this.parseBeanDefinitions(root, this.delegate);
        this.postProcessXml(root);
        this.delegate = parent;
    }

    protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            NodeList nl = root.getChildNodes();

            for(int i = 0; i < nl.getLength(); ++i) {
                Node node = nl.item(i);
                if (node instanceof Element) {
                    Element ele = (Element)node;
                    if (delegate.isDefaultNamespace(ele)) {
                        // 核心方法
                        this.parseDefaultElement(ele, delegate);  
                    } else {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        } else {
            delegate.parseCustomElement(root);
        }

    }

    private void parseDefaultElement(Element ele, BeanDefinitionParserDelegate delegate) {
        if (delegate.nodeNameEquals(ele, "import")) {
            this.importBeanDefinitionResource(ele);
        } else if (delegate.nodeNameEquals(ele, "alias")) {
            this.processAliasRegistration(ele);
        } else if (delegate.nodeNameEquals(ele, "bean")) {
            // 对 bean 标签的解析和注册
            this.processBeanDefinition(ele, delegate);
        } else if (delegate.nodeNameEquals(ele, "beans")) {
            this.doRegisterBeanDefinitions(ele);
        }

    }

    protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate delegate) {
        // 1. 解析
        BeanDefinitionHolder bdHolder = delegate.parseBeanDefinitionElement(ele);
        if (bdHolder != null) {
            // 代理去装饰:典型的装饰器模式
            bdHolder = delegate.decorateBeanDefinitionIfRequired(ele, bdHolder);

            try {
                // 2. 向IoC容器注册Bean定义 + bean工厂
                BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, this.getReaderContext().getRegistry());
            } catch (BeanDefinitionStoreException var5) {
                this.getReaderContext().error("Failed to register bean definition with name '" + bdHolder.getBeanName() + "'", ele, var5);
            }
            // 3. 触发注册事件:Spring只提供了EmptyReaderEventListener空实现,如果需要你可以自定义
            this.getReaderContext().fireComponentRegistered(new BeanComponentDefinition(bdHolder));
        }

    }

}

解析 :parseBeanDefinitionElement方法就是具体的解析入口。解析elemnent->BeanDefinitionHolder,追踪parseBeanDefinitionElement

public class DefaultBeanDefinitionDocumentReader implements BeanDefinitionDocumentReader {

    public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, @Nullable BeanDefinition containingBean) {
        // 获取id
        String id = ele.getAttribute("id");
        // 获取name
        String nameAttr = ele.getAttribute("name");
        // 获取别名
        List<String> aliases = new ArrayList();
        if (StringUtils.hasLength(nameAttr)) {
            String[] nameArr = StringUtils.tokenizeToStringArray(nameAttr, ",; ");
            aliases.addAll(Arrays.asList(nameArr));
        }

        String beanName = id;
        if (!StringUtils.hasText(id) && !aliases.isEmpty()) {
            beanName = (String)aliases.remove(0);
            if (this.logger.isTraceEnabled()) {
                this.logger.trace("No XML 'id' specified - using '" + beanName + "' as bean name and " + aliases + " as aliases");
            }
        }

        if (containingBean == null) {
            this.checkNameUniqueness(beanName, aliases, ele);
        }

        // 核心方法
        AbstractBeanDefinition beanDefinition = this.parseBeanDefinitionElement(ele, beanName, containingBean);
        if (beanDefinition != null) {
            if (!StringUtils.hasText(beanName)) {
                try {
                    if (containingBean != null) {
                        beanName = BeanDefinitionReaderUtils.generateBeanName(beanDefinition, this.readerContext.getRegistry(), true);
                    } else {
                        beanName = this.readerContext.generateBeanName(beanDefinition);
                        String beanClassName = beanDefinition.getBeanClassName();
                        if (beanClassName != null && beanName.startsWith(beanClassName) && beanName.length() > beanClassName.length() && !this.readerContext.getRegistry().isBeanNameInUse(beanClassName)) {
                            aliases.add(beanClassName);
                        }
                    }

                    if (this.logger.isTraceEnabled()) {
                        this.logger.trace("Neither XML 'id' nor 'name' specified - using generated bean name [" + beanName + "]");
                    }
                } catch (Exception var9) {
                    this.error(var9.getMessage(), ele);
                    return null;
                }
            }

            String[] aliasesArray = StringUtils.toStringArray(aliases);
            return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
        } else {
            return null;
        }
    }

     public AbstractBeanDefinition parseBeanDefinitionElement(Element ele, String beanName, @Nullable BeanDefinition containingBean) {
        this.parseState.push(new BeanEntry(beanName));
        String className = null;
        if (ele.hasAttribute("class")) {
            className = ele.getAttribute("class").trim();
        }

        String parent = null;
        if (ele.hasAttribute("parent")) {
            parent = ele.getAttribute("parent");
        }

        try {
            // 生成需要的BeanDefinition对象,为Bean定义信息的载入做准备
            AbstractBeanDefinition bd = this.createBeanDefinition(className, parent);
            // 1.解析<bean>元素属性
            this.parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
            // 2.解析description
            bd.setDescription(DomUtils.getChildElementValueByTagName(ele, "description"));
            // 对各种BEAN元素信息进行解析
            // 3. 解析<meta>子元素
            this.parseMetaElements(ele, bd);
            // 4.解析<lookup-method>子元素
            this.parseLookupOverrideSubElements(ele, bd.getMethodOverrides());
            // 5.解析<replaced-method>子元素
            this.parseReplacedMethodSubElements(ele, bd.getMethodOverrides());
            // 6.解析<constructor-arg>
            this.parseConstructorArgElements(ele, bd);
            // 7.解析<property>
            this.parsePropertyElements(ele, bd);
            // 8.解析<qualifier>
            this.parseQualifierElements(ele, bd);
            bd.setResource(this.readerContext.getResource());
            bd.setSource(this.extractSource(ele));
            AbstractBeanDefinition var7 = bd;
            return var7;
        } catch (ClassNotFoundException var13) {
            this.error("Bean class [" + className + "] not found", ele, var13);
        } catch (NoClassDefFoundError var14) {
            this.error("Class that bean class [" + className + "] depends on not found", ele, var14);
        } catch (Throwable var15) {
            this.error("Unexpected failure during bean definition parsing", ele, var15);
        } finally {
            this.parseState.pop();
        }

        return null;
    }

}

经过这样逐层的分析,在 xml 文件中定义的 BeanDefinition 就被整个载入到 IoC 容器中,并在容器中建立了数据映射。这些数据结构可以以 AbstractBeanDefinition 为入口让 loC 容器执行索引,查询和操作

3.1.2 向下找 getBeanFactory() 的具体实现

public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {

    public final ConfigurableListableBeanFactory getBeanFactory() {
        DefaultListableBeanFactory beanFactory = this.beanFactory;
        if (beanFactory == null) {
            throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");
        } else {
            return beanFactory;
        }
    }

}

完成了 BeanDefinition 的注册,就完成了 IoC 容器的初始化过程。容器的作用就是对这些信息进行处理和维护,这些信息就是容器建立依赖反转的基础

Spring管理Bean

Spring管理Bean概念
  • 创建对象
  • 注入属性
Spring管理Bean方式
基于 XML 方式
  • 创建对象:在 Spring 配置文件中配置<bean>标签
  • 注入属性:

定义 Bean

// 人类
@Data
public class Person {

    private String name;
    private String wife;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String, String> cards;
    private Set<String> games;
    private Properties info;

}


// 地址
@Data
public class Address {

    private String address;

}

配置 resources/beans.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <bean id="person" class="com.hefery.kafkademo.spring.Person">
        <!-- 普通值 -->
        <property name="name" value="Hefery"/>
        <!-- Bean -->
        <property name="address" ref="address"/>
        <!-- 数组 -->
        <property name="books">
            <array>
                <value>哈哈哈</value>
                <value>嘿嘿嘿</value>
                <value>啦啦啦</value>
            </array>
        </property>
        <!-- List -->
        <property name="hobbys">
            <list>
                <value>听歌</value>
                <value>刷剧</value>
                <value>游戏</value>
            </list>
        </property>
        <!-- Map -->
        <property name="cards">
            <map>
                <entry key="first-card" value="first-key-value"/>
                <entry key="second-card" value="second-key-value"/>
            </map>
        </property>
        <!-- Set -->
        <property name="games">
            <set>
                <value>王者荣耀</value>
                <value>都市天际线</value>
            </set>
        </property>
        <!-- 特殊 -->
        <!-- 空字符串:单个-value="" 集合-<null> -->
        <property name="wife" value=""/>
        <!-- Properties -->
        <property name="info">
            <props>
                <prop key="手机号">123456789</prop>
                <prop key="邮箱">123456789@126.com</prop>
            </props>
        </property>
    </bean>

    <bean id="address" class="com.hefery.kafkademo.spring.Address">
        <property name="address" value="深圳"/>
    </bean>

</beans>

测试从 IoC 容器获取 Bean

public class SpringDemo {

    public static void main(String[] args) {
        // 源码入口,从类路径下读取xml
        ApplicationContext context = new ClassPathXmlApplicationContext("beans1.xml");
        // 根据名称获取Bean
        Person person = (Person) context.getBean("person");
        // 执行Bean实例方法
        System.out.println(person.toString());
        /*
            Person(
                name=Hefery,
                wife=,
                address=Address(address=深圳),
                books=[哈哈哈, 嘿嘿嘿, 啦啦啦],
                hobbys=[听歌, 刷剧, 游戏],
                cards={first-card=first-key-value, second-card=second-key-value}, g
                ames=[王者荣耀, 都市天际线],
                info={手机号=123456789, 邮箱=123456789@126.com}
            )
         */

    }

}

Spring的配置

  • <beans>:整个配置文件的根节点,包含一个或多个 Bean 元素
  • <context>:
    • 自动扫描包 ,将注解类纳入 Spring 容器管理<context:component-scan base-package="Package路径"/>
  • <bean>
    • id:Bean的唯一标识(对象名)
    • class:Bean的全类名
    • name:别名
    • scope:Bean的作用域(singleton、prototype、request、session、global-session)
    • autowire:自动装配方式(byName、byType、constructor、autodetect)
  • <import>:导入其他 Spring 配置文件,使用总的即可

Spring配置文件详解

基于注解方式
Bean的作用域

在 Spring 的配置文件中,定义<bean>标签的属性有 scope 就是用于定义 Bean 的作用域

  • singleton
    IoC 容器仅创建一个 Bean 实例,loC容器每次返回的是同一 Bean 实例,Spring 中的 bean 默认都是单例
  • prototype
    IoC 容器可以创建多个 bean 实例,每次返回的都是一个新的 bean 实例
  • request
    该属性仅对 HTTP 请求产生作用,使用该属性定义 Bean 时,每次HTTP请求都会创建一个新的 bean 实例
  • session
    该属性仅用于HTTP Session,同一个 Session 共享一个 bean 实例。不同 Session 使用不同的 bean 实例
  • global-session
    该属性仅用于HTTP Session,全局 Session 作用域,所有的 Session 共享一个 bean 实例
Bean生命周期

生命周期:从 Bean 对象创建到销毁的过程

  1. 创建 Bean 实例:通过反射创建对象,此时的创建只是在堆空间中申请空间,属性都是默认值
  2. 注入 Bean 属性:给对象中的属性进行值的设置
  3. 将 Bean 实例传递 BeanPostProcessor 的前置处理器,对生成的 Bean 对象进行前置的处理工作
  4. 调用 Bean 初始化方法
  5. 将 Bean 实例传递 BeanPostProcessor 的后置处理器,对生成的 Bean 对象进行后置的处理工作
  6. Bean 可以使用
  7. IoC 容器关闭,调用 DisposableBean中destory() 注销的回调接口

Spring Bean的生命周期

Bean自动装配

自动装配:Bean 的属性值在进行注入的时候通过某种特定的规则和方式去容器中查找,并设置到具体的对象属性中。Spring 容器能够自动装配相互合作的 Bean,这意味着容器不需要配置,能通过 Bean 工厂自动处理 Bean 之间的协作

在 XML 中显示配置:在 Spring 配置文件中,<bean>标签有 autowire 属性

  • no:缺省情况下,自动配置是通过“ref" 属性手动设定,在项目中最常用
  • byName:根据属性名称自动装配。自动在容器上下文中查找,Setter 方法后面的值对应的 Bean id
  • byType:根据数据类型自动装配。自动在容器上下文中查找,和对象属性类型相同的 Bean
  • constructor:在构造函数参数的byType方式
  • autodetect:如果找到默认的构造函数,使用“自动装配用构造”;否则,使用“按类型自动装配"

在代码中显式配置:注解,导入约束 context:annotation-config,配置<context:annotation-config>

  • @Autowired:根据数据类型 byType 自动装配,要求依赖对象必须存在
  • @Resource:先根据属性名称 byName 自动装配,只有找不到与名称匹配的 Bean 再根据数据类型 byType 自动装配
Bean线程安全
Spring单例Bean线程安全吗?

Spring 中的 Bean 对象默认是单例,并没有对 Bean 进行多线程的封装处理。单例 Bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题

如果 Bean 是有状态的,那么就需要开发人员自己来保证线程安全的保证,最简单的办法就是改变 Bean 的作用域把 singleton 改成 prototype,这样每次请求 Bean 对象就相当于是创建新的对象来保证线程的安全。有状态就是由数据存储的功能,无状态就是不会存储数据

Controller,Service 和 Dao 本身并不是线程安全,只是调用里面的方法,而多线程调用一个实例方法,会在内存中复制遍历,这是自己线程的工作内存,是最安全。因此在进行使用时,不要在 Bean 中声明任何有状态的实例变量或者类变量

如果必须如此,也推荐使用 ThreadLocal 把变量变成线程私有,在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal。如果 Bean 的实例变量或者类变量需要在多个线程之间共享,那么就只能使用synchronized,Lock,CAS等这些实现线程同步的方法

面向切面—AOP

AOP相关概念

AOP:Aspect Oriented Programming,面向切面编程,将程序中的通用、与业务无关的逻辑(权限、安全,日志,事务等)封装成一个切面类,然后注入到目标对象(具体业务逻辑)中,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性

优势:在不修改源码的情况下,对程序行为进行扩展

AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用 JDK 动态代理,创建代理对象,而对于没有实现接口的对象,就无法使用 JDK 动态代理,这时 AOP 会使用 Cglib 动态代理生成一个被代理对象的子类来作为代理

  • 横切关注点:跨越应用程序多个模块的方法或功能。与业务逻辑无关,但需要关注的部分,如日志,安全,缓存,事务
  • 切面(Aspect):横切关注点的模块化出来的特殊对象(Log类),这个横切关注点可能会横切多个对象。切面可以使用通用类或在普通类中以 @Aspect 注解来实现
  • 通知(Advice):切面必须要完成的工作(Log类的方法)。通知有各种类型,包括around、before和after等通知。许多 AOP 框架都以拦截器做通知模型,并维护以连接点为中心的拦截器链
    • 前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)
    • 后置通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回
    • 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知
    • 环绕通知(Around Advice):
    • 后通知(After (finally) advice):当某连接点退出时执行的通知(不论是正常返回还是异常退出)
  • 切入点(Pointcut):切面通知执行的“地点”的定义,使用 execution 表达式描述切面的作用范围
  • 连接点(JoinPoint):与切入点匹配的执行点。一个连接点总是代表一个方法的执行。声明 JoinPoint 类的参数可以使通知(Advice)的主体部分获得连接点(JoinPoint)信息,可以获取目标对象方法信息
  • 引入(Introduction):允许向现有类添加新方法或属性,声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象
  • 目标对象(Target Object): 被一个或者多个切面所通知的对象或方法
  • 织入(Weaving):将切面应用到目标对象并导致代理对象创建的过程
    • 编译期:切面在目标类编译时被织入
    • 类加载期:切面在目标类加载到JVM时被织入
    • 运行期:切面在应用运行的某个时刻被织入

注解基于AOP

过滤器、拦截器和AOP区别
  • 过滤器只是过滤请求,把不符合条件的请求过滤掉,不处理业务
  • 拦截器对 action 负责(preHandle、afterCompletion、postHandle),作用 Controller 层
  • AOP 主要是拦截对 Spring 管理的 Bean 的访问,作用 Service 层
AOP的理解,在项目中的实际应用
AOP统一打印请求和返回信息
/**
 * @Author:Hefery
 * @Version:1.0.0
 * @Date:2022/3/22 20:25
 * @Description:打印请求和响应信息
 */
@Aspect     // 切面
@Component
public class WebLogAspect {

    // 初始化Logger类:使用指定的类初始化 LoggerFactory,输出日志所在类的信息
    private static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    @Pointcut("execution(public * com.hefery.kafkademo.spring.controller.*.*(..))")  // 切入点
    public void webLog() {

    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) {
        // 收到请求,记录请求信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        logger.info("URL:" + request.getRequestURL().toString());
        logger.info("HTTP_METHOD:" + request.getMethod());
        logger.info("IP:" + request.getRemoteAddr());
        logger.info("CLASS_METHOD:" + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("ARGS:" + Arrays.toString(joinPoint.getArgs()));
    }

    @AfterReturning(returning = "res", pointcut = "webLog()")
    public void doAfterReturning(Object res) throws JsonProcessingException {
        // 处理完请求,返回内容
        logger.info("RESPONSE:" + new ObjectMapper().writeValueAsString(res));
    }

}

AOP实现原理

代理模式:通知目标对象后创建的对象

  • 静态代理
  • 动态代理:
    • JDK动态代理:基于接口。通过实现 InvocationHandlet 接口,并重写里面的 invoke 方法;通过为Pproxy 类指定 ClassLoader 对象和一组 interfaces 来创建动态代理
    • CGLIB动态代理:基于类。Code Generation Library,是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展 Java 类与实现 Java 接口,CGLib 封装了 asm,可以再运行期动态生成新的 class。和 JDK 动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过 CGLib 创建动态代理
原生接口实现AOP

定义目标对象

public interface UserService {
    public void addUser();
    public void deleteUser();
    public void updateUser();
    public void queryUser();
}

public class UserServiceImpl implements UserService{
    @Override
    public void addUser() {
        System.out.println("add a user");
    }

    @Override
    public void deleteUser() {
        System.out.println("delete a user");
    }

    @Override
    public void updateUser() {
        System.out.println("update a user");
    }

    @Override
    public void queryUser() {
        System.out.println("query a user");
    }
}

定义切面BeforeLog、AfterLog,分别实现前置通知 before、后置通知 afterReturning

public class BeforeLog implements MethodBeforeAdvice {
    /**
     * 前置方法
     * @param method 要执行的目标对象的方法
     * @param args   目标对象的方法参数
     * @param target 目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行");
    }
}

public class AfterLog implements AfterReturningAdvice {
    /**
     * 后置方法
     * @param returnValue 执行目标对象方法的返回值
     * @param method      要执行的目标对象的方法
     * @param args        目标对象的方法参数
     * @param target      目标对象
     * @throws Throwable
     */
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行,并返回了" + returnValue);
    }
}

定义 Spring 配置文件:resources/applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 注册 Bean -->
    <bean id="userServiceImpl" class="com.hefery.kafkademo.spring.aop.UserServiceImpl"/>
    <bean id="beforeLog" class="com.hefery.kafkademo.spring.aop.log.BeforeLog"/>
    <bean id="afterLog" class="com.hefery.kafkademo.spring.aop.log.AfterLog"/>

    <!-- 配置 AOP -->
    <aop:config>
        <!-- 切入点:expression() -->
        <aop:pointcut id="pointcut" expression="execution(* com.hefery.kafkademo.spring.aop.UserServiceImpl.*(..) )"/>
        <!-- 执行环绕通知 -->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>

</beans>

织入

public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userServiceImpl");

        userService.addUser();
        /**
         * com.hefery.kafkademo.spring.aop.UserServiceImpl的addUser被执行
         * add a user
         * com.hefery.kafkademo.spring.aop.UserServiceImpl的addUser被执行,并返回了null
         */
    }

}
自定义类实现AOP

定义切入点

public class HeferyPointCut {

    public void before() {
        System.out.println("brefore  ...");
    }

    public void after() {
        System.out.println("after  ...");
    }

}

Spring 配置文件:resources/applicationContext.xml,配置切面

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 自定义AOP -->
    <bean id="hefery" class="com.hefery.kafkademo.spring.aop.log.HeferyPointCut"/>

    <aop:config>
        <!-- 自定义切面:ref指定要引用的类 -->
        <aop:aspect ref="hefery">
            <!-- 切入点 -->
            <aop:pointcut id="opint" expression="execution(* com.hefery.kafkademo.spring.aop.UserServiceImpl.*(..) )"/>
            <!-- 通知 -->
            <aop:before method="before" pointcut-ref="opint"/>
            <aop:after method="after" pointcut-ref="opint"/>
        </aop:aspect>
    </aop:config>

</beans>

测试

public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContent.xml");
        UserService userService = (UserService) context.getBean("userServiceImpl");

        userService.addUser();
        /**
         * brefore  ...
         * add a user
         * after  ...
         */
    }

}
使用注解实现AOP

自定义切面

@Aspect  // 标注该类为切面
public class AnnotationPointCut {

    @Before("execution(* com.hefery.kafkademo.spring.aop.UserServiceImpl.*(..) )")  // 切入点
    public void before() {
        System.out.println("brefore  ...");
    }

    @After("execution(* com.hefery.kafkademo.spring.aop.UserServiceImpl.*(..) )")
    public void after() {
        System.out.println("after  ...");
    }

    @Around("execution(* com.hefery.kafkademo.spring.aop.UserServiceImpl.*(..) )")
    public void around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("before around  ...");
        Signature signature = joinPoint.getSignature();  // 获取签名
        System.out.println(signature);
        Object proceed = joinPoint.proceed();
        System.out.println("after around  ...");
        System.out.println(proceed);
    }

}

Spring 配置文件:resources/applicationContext.xml,配置注解开发

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 注册 Bean -->
    <bean id="userServiceImpl" class="com.hefery.kafkademo.spring.aop.UserServiceImpl"/>
    <bean id="beforeLog" class="com.hefery.kafkademo.spring.aop.log.BeforeLog"/>
    <bean id="afterLog" class="com.hefery.kafkademo.spring.aop.log.AfterLog"/>

    <bean id="annotationPointCut" class="com.hefery.kafkademo.spring.aop.log.AnnotationPointCut"/>
    <!-- 开启注解支持 -->
    <aop:aspectj-autoproxy/>

</beans>

测试

public class AOPTest {

    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContent.xml");
        UserService userService = (UserService) context.getBean("userServiceImpl");

        userService.addUser();
        /**
         * before around  ...
         * void com.hefery.kafkademo.spring.aop.UserService.addUser()
         * brefore  ...
         * add a user
         * after  ...
         * after around  ...
         * null
         */
    }

}

Spring注解

声明Bean

  • @Component:泛指各种组件
    • @Controller:控制层 controller,主要用户接受用户请求并调用 Service 层返回数据给前端页面
    • @Service:业务层 service,主要涉及一些复杂的逻辑,需要用到 Dao 层
    • @Repository:数据访问层 dao,主要用于数据库相关操作
    • @RestController:@Controller + @ResponseBody
  • @Import(要导入到容器中的组件)
@Component和@Bean区别
  • 作用对象: @Component 注解作用于类;@Bean 注解作用于方法
  • 原理:@Component 通过类路径扫描来自动侦测装配到 Spring 容器(使用 @ComponentScan 定义要扫描路径从中找出标识了需要装配的类自动装配到 Spring 的 Bean 容器中); @Bean 注解通常是我们在标有该注解的方法中定义产生这个 Bean,@Bean 告诉了 Spring 这是某个类的示例,当我需要用它的时候还给我

注入Bean

  • @Resource
    先按照名称匹配(byName),只有找不到了再按照类型匹配的(byType)
  • @Autowired
    按照类型匹配的(byType),如果需要按照名称匹配 @Resource 需要和 @Qualifier 一起使用
    • require:对象可以为 null,但不能为空
  • @Qualifier
    按照名称匹配的(byName)
  • @Value:注入配置文件 yml 属性;配合 Bean 的声明注解(@Componet)完成属性注入;注入文件资源;注入其它 Bean 属性;注入操作系统属性;注入普通字符、表达式结果

配置类相关

  • @Configuration:声明当前类为配置类,相当于<beans>
  • @Bean:注解在方法上,声明当前方法的返回值为一个 Bean,替代 xml 中的方式。设置 Spring 容器如何新建 Bean 实例(Singleton、Protetype、Request、Session、GlobalSession),相当于<bean>
  • @ComponentScan:用于对 Component 进行扫描

初始化和销毁

  • @PostConstruct:在 Bean 被 Spring 初始化之前需要执行的方法
  • @PreDestory:销毁方法

AOP切面

  • @Aspect 声明一个切面
  • @After 在方法执行之后执行(方法上)
  • @Before 在方法执行之前执行(方法上)
  • @Around 在方法执行之前与之后执行(方法上)
  • @PointCut 声明切点

异步相关

  • @EnableAsync
    配置类中通过此注解开启对异步任务的支持
  • @Async
    在实际执行的bean方法使用该注解来声明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync开启异步任务)

测试相关

  • @RunWith
    运行器,Spring中通常用于对JUnit的支持 @RunWith(SpringJUnit4ClassRunner.class)
  • @ContextConfiguration
    用来加载配置配置文件,其中classes属性用来加载配置类@ContextConfiguration(locations = {"classpath*:/*.xml"})
  • @SuppressWarnings:对被批注的代码元素内部的某些警告保持静默

开启支持

  • @EnableAspectJAutoProxy :开启对AspectJ自动代理的支持
  • @EnableAsync :开启异步方法的支持
  • @EnableScheduling :开启计划任务的支持
  • @EnableWebMvc :开启Web MVC的配置支持
  • @EnableConfigurationProperties :开启对@ConfigurationProperties注解配置Bean的支持
  • @EnableJpaRepositories :开启对SpringData JPA Repository的支持
  • @EnableTransactionManagement :开启注解式事务的支持
  • @EnableCaching :开启注解式的缓存支持

环境切换

  • @Profile:通过设定 Environment 的 ActiveProfiles 来设定当前 context 需要使用的配置环境。(类或方法上)
  • @Conditional:通过实现 Condition 接口,并重写 matches 方法,从而决定该Bean 是否被实例化。(方法上)

Spring事务

Spring事务简介

Spring事务依赖数据库对事务的支持,没有数据库的事务支持,Spring 是无法提供事务功能

在 Spring 进行声明式事务管理,底层使用 AOP 原理

对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:

  • @Transactional
  • 获取连接 Connection con = DriverManager.getConnection()
  • 关闭事务自动回滚 con.setAutoCommit(true/false);
  • 执行CRUD
  • 提交事务/回滚事务 con.commit() / con.rollback();
  • 关闭连接 conn.close();
事务实现方式
  • 编程式事务:通过代码来控制事务的处理逻辑(不推荐)
  • 声明式事务:在配置文件中配置
    • 基于XML的声明式事务
    • 基于注解的声明式事务(@Transactional注解)
事务实现原理

事务的操作本来应该是由数据库来进行控制,但为了方便用户进行业务逻辑的操作,Spring 对事务功能进行扩展,一般很少会用编程式事务,更多的是通过添加 @Transactional 注解来进行实现,当添加此注解之后事务的自动提交功能就会关闭,有 Spring 框架来帮助进行控制

事务操作的底层是 AOP 的一个核心体现:当一个方法添加 @Transactional 注解后,Spring 会基于这个类生成一个代理对象,并将这个代理对象作为 Bean。当使用这个代理对象的方法时,如果有事务处理,那么会先把事务的自动提交,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交,如果出现任何异常情况,那么直接进行回滚操作,当然用户可以控制对哪些异常进行回滚操作

Spring事务属性

Spring事务传播行为

多个事务方法相互调用时,事务如何在这些方法之间进行传播,Spring中提供不同的传播特性,来保证事务的正常执行:

支持当前事务的情况:

  • REQUIRED: 默认的传播特性,如果当前没有事务,则新建一个事务,如果当前存在事务,则加入这个事务
  • SUPPORTS: 当前存在事务,则加入当前事务,如果当前没有事务,则以非事务的方式执行
  • MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常

不支持当前事务的情况:

  • REQUIRES_NEW创建一个新的事务,如果当前存在事务,则把当前事务挂起
  • NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起
  • NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常

其他情况:

  • NESTED:如果当前事务存在,则在嵌套事务中执行,否则 REQUIRED的操作一样

设置事务的传播行为:@Transactional(propagation = Propagation.REQUIRED)

Spring事务隔离级别

设置事务隔离级别:@Transactional(isolation = Isolation.DEFAULT)

  • Isolation.DEFAULT:Spring事务默认的隔离级别,MySQL默认采用的 REPEATABLE_READ;Oracle 默认采用的 READ_COMMITTED
  • Isolation.READ_UNCOMMITTED:未提交读,最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • Isolation.READ_COMMITTED:已提交读,允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • Isolation.REPEATABLE_READ:可重复读,对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  • Isolation.SERIALIZABLE:可串行化,最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别

在进行配置的时候,如果数据库和Spring代码中的隔离级别不同,那么以Spring的配置为主

Spring事务其他属性
  • 超时时间:timeout,事务需要在一定时间内进行提交,如果不提交进行回滚。默认值是-1,单位为秒@Transactional(timeout = 10)
  • 是否只读:readonly,默认false,设置为true,就只能查询
    @Transactional(readOnly = true)
  • 回滚:rollbackFor,设置查询哪些异常进行事务回滚
    @Transactional(rollbackFor = Exception.class)
  • 不回滚:noRollbackFor,设置查询哪些异常不进行事务回滚
    @Transactional(noRollbackFor = Exception.class)
Spring事务失效情况
  • 事务方法访问修饰符不是 public,被 static 或 final 修饰
  • @Transactional 注解的方法抛出的异常不是 Spring 事务支持的异常
    Spring事务只支持未检查异常(unchecked),不支持已检查异常(checked)
    • 使用@Transactional(rollbackFor = Exception.class)指定 Spring 支持的异常
    • 抛出 RuntimeException 异常或 Spring 支持的异常throw new RuntimeException("...")
  • 数据表本身是不支持事务,导致事务失效:MySQL 中的表使用的是不支持事务的 MyISAM 存储引擎
  • @Transactional 注解所在的类没有被 Spring 容器管理:XXXServiceImpl 没有标注 @Service 注解
  • try...catch捕获异常,catch语句没有抛出 RuntimeException 异常或 Spring 支持的异常
  • 方法自身(this)调用问题,导致事务失效:非事务方法 insert() 中调用的自身类的事务方法 insertUser()
    由于@Transactional的实现原理是AOP,而AOP的实现原理是动态代理,而自己调用自己的过程,并不存在代理对象的调用,这样就不会产生AOP去为我们设置@Transactional配置的参数,这样就出现了自调用注解失效的问题
  • 数据源没有配置事务管理器(很少遇到)
  • 传播类型不支持事务
  • 多线程调用:两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。我们说的同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务

Error错误:一定会回滚

Exception异常

  • 可查的异常(checked exceptions):Exception下除 RuntimeException 外的异常
  • 不可查的异常(unchecked exceptions):RuntimeException及其子类和错误(Error)

让checked例外也回滚:在整个方法前加上 @Transactional(rollbackFor=Exception.class)
让unchecked例外不回滚:@Transactional(notRollbackFor=RunTimeException.class)

Spring缓存

Spring一级缓存

单例池:singletonObjects,存放完全实例化属性赋值完成的Bean,直接可以使用

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

Spring二级缓存

earlySingletonObjects,存放早期Bean的引用,尚未属性装配的Bean

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

Spring三级缓存

singletonFactories,存放实例化完成的Bean工厂

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
Spring使用三级缓存解决循环依赖

循环依赖:创建 A 这个 Bean 时需要先创建 B 这个 Bean,同样的,创建 B 这个 Bean 时需要先创建 A 这个 Bean,A、B相互依赖,形成“死锁”

/** 一级缓存,用于存放完全初始化好的bean,从该缓存中取出的 bean 可以直接使用 */
private final Map<String,Object> singletonObjects =new ConcurrentHashMap<>(256);

/** 三级缓存存放 bean 工厂对象,用于解决循环依赖 */
private final Map<String,ObjectFactory<?>> singletonFactories =new HashMap<>(16);

/** 二级缓存存放原始的bean 对象(尚未填充属性),用于解决循环依赖*/
private final Map<String,Object> earlySingietonobjects =new HashMap(16);

Spring 在 Bean 的创建过程中,首先就会从尝试从缓存中获取,如果没有获取到,就会走到 Bean 的创建过程。如果存在循环依赖,则会利用三级缓存的策略逐步递进式的初始化各个 Bean,最终完成整体的初始化。但是,需要注意,对于构造函数注入方式造成的循环依赖,Spring 是解决不了的


标题:Spring基础—必知必会
作者:Hefery
地址:http://hefery.icu/articles/2022/03/18/1647539755012.html