Spring之完整版学习笔记

文章目录

Spring 概述(Spring文章整合)

1. Spring 简介

Spring 是分层的 full-stack(全栈) 轻量级开源框架,以 IoC 和 AOP 为内核,提供了展现层 SpringMVC 和业务层事务管理等众多的企业级应⽤技术,还能整合开源世界众多著名的第三⽅框架和类库,已经成为使⽤最多的 Java EE 企业应⽤开源框架。

Spring 官⽅⽹址:http://spring.io/。我们经常说的 Spring 其实指的是Spring Framework(spring 框架)。

2. Spring 优势

  • ⽅便解耦,简化开发
    通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进⾏控制,避免硬编码所造成的过度程序耦合。⽤户也不必再为单例模式类、属性⽂件解析等这些很底层的需求编写代码,可以更专注于上层的应⽤。

  • AOP编程的⽀持
    通过Spring的AOP功能,⽅便进⾏⾯向切⾯的编程,许多不容易⽤传统OOP实现的功能可以通过AOP轻松应付。

  • 声明式事务的⽀持
    @Transactional
    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式⽅式灵活的进⾏事务的管理,提⾼开发效率和质量。⽅便程序的测试可以⽤⾮容器依赖的编程⽅式进⾏⼏乎所有的测试⼯作,测试不再是昂贵的操作,⽽是随⼿可做的事情。

  • ⽅便集成各种优秀框架
    Spring可以降低各种框架的使⽤难度,提供了对种优秀框架(Struts、Hibernate、Hessian、 Quartz等)的直接⽀持。

  • 降低JavaEE API的使⽤难度
    Spring对JavaEE API(如JDBC、JavaMail、远程调⽤等)进⾏了薄薄的封装层,使这些API的使⽤难度⼤为降低。

  • 源码是经典的 Java 学习范例
    Spring的源代码设计精妙、结构清晰、匠⼼独⽤,处处体现着⼤师对Java设计模式灵活运⽤以及对Java技术的⾼深造诣。它的源代码⽆意是Java技术的最佳实践的范例。

3. Spring 的核⼼结构

Spring是⼀个分层⾮常清晰并且依赖关系、职责定位⾮常明确的轻量级框架,主要包括⼏个⼤模块:数据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、Core Container模块和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决⽅案的零侵⼊的轻量级框架。

image-20210325102305103

  • Spring核⼼容器(Core Container) 容器是Spring框架最核⼼的部分,它管理着Spring应⽤中bean的创建、配置和管理。在该模块中,包括了Spring bean⼯⼚,它为Spring提供了DI的功能。基于bean⼯⼚,我们还会发现有多种Spring应⽤上下⽂的实现。所有的Spring模块都构建于核⼼容器之上。
  • ⾯向切⾯编程(AOP)/Aspects Spring对⾯向切⾯编程提供了丰富的⽀持。这个模块是Spring应⽤系统中开发切⾯的基础,与DI⼀样,AOP可以帮助应⽤对象解耦。
  • 数据访问与集成(Data Access/Integration)
    Spring的JDBC和DAO模块封装了⼤量样板代码,这样可以使得数据库代码变得简洁,也可以更专注于我们的业务,还可以避免数据库资源释放失败⽽引起的问题。 另外,Spring AOP为数据访问提供了事务管理服务,同时Spring还对ORM进⾏了集成,如Hibernate、MyBatis等。该模块由JDBC Transactions、ORM、OXM 和 JMS 等模块组成。
  • Web 该模块提供了SpringMVC框架给Web应⽤,还提供了多种构建和其它应⽤交互的远程调⽤⽅案。 SpringMVC框架在Web层提升了应⽤的松耦合⽔平。
    Test 为了使得开发者能够很⽅便的进⾏测试,Spring提供了测试模块以致⼒于Spring应⽤的测试。 通过该模块,Spring为使⽤Servlet、JNDI等编写单元测试提供了⼀系列的mock对象实现。

Spring 核心思想

Spring IOC

1. IOC是什么

IoC Inversion of Control (控制反转/反转控制),注意它是⼀个技术思想,不是⼀个技术实现

描述的事情:Java开发领域对象的创建,管理的问题

传统开发⽅式:⽐如类A依赖于类B,往往会在类A中new⼀个B的对象

IoC思想下开发⽅式:我们不⽤⾃⼰去new对象了,⽽是由IoC容器(Spring框架)去帮助我们实例化对

象并且管理它,我们需要使⽤哪个对象,去问IoC容器要即可。我们丧失了⼀个权利(创建、管理对象的权利),得到了⼀个福利(不⽤考虑对象的创建、管理等⼀系列事情)

为什么叫做控制反转?

控制:指的是对象创建(实例化、管理)的权利

反转:控制权交给外部环境了(spring框架、IoC容器

图示传统和IOC

image-20210323103643850

2. IOC解决了什么问题

IOC解决了传统方式到处new对象的情况,降低了对象之间的耦合问题。

image-20210323104031250

3. IOC和DI有什么关系

DI:Dependancy Injection 依赖注⼊ 类A依赖于类B,通过IOC我们可以直接从IOC容器中取出类B注入进类A当中。而不是自己去new一个类B。

IOC和DI区别在于一个是我们从容器中取出对象,一个是IOC容器去取出对象。

image-20210323104719798

Spring AOP

1. 什么是AOP

AOP: Aspect oriented Programming ⾯向切⾯编程/⾯向⽅⾯编程AOP是OOP的延续,从OOP说起OOP三⼤特征:封装、继承和多态oop是⼀种垂直继承体系

image-20210325103716593

OOP编程思想可以解决⼤多数的代码重复问题,但是有⼀些情况是处理不了的,⽐如下⾯的在顶级⽗类。Animal中的多个⽅法中相同位置出现了重复代码,OOP就解决不了

image-20210325103802589

横切逻辑代码

image-20210325103815537

**横切逻辑代码存在什么问题:**横切代码重复问题,横切逻辑代码和业务代码混杂在⼀起,代码臃肿,维护不⽅便。

AOP出场,AOP独辟蹊径提出横向抽取机制,将横切逻辑代码和业务逻辑代码分析

image-20210325104030102

2. AOP在解决什么问题

在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复

3. 为什么叫做⾯向切⾯编程

「切」:指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以⾯向横切逻辑

「⾯」:横切逻辑代码往往要影响的是很多个⽅法,每⼀个⽅法都如同⼀个点,多个点构成⾯,有⼀个⾯的概念在⾥⾯

Spring IOC 的具体应用

1. BeanFactory与ApplicationContext区别

BeanFactory是Spring框架中IoC容器的顶层接⼝,它只是⽤来定义⼀些基础功能,定义⼀些基础规范,⽽ApplicationContext是它的⼀个⼦接⼝,所以ApplicationContext是具备BeanFactory提供的全部功能的。通常,我们称BeanFactory为SpringIOC的基础容器,ApplicationContext是容器的⾼级接⼝⽐BeanFactory要拥有更多的功能,⽐如说国际化⽀持和资源访问(xml,java配置类)等等

image-20210325111104947

2. IOC 容器的启动

Java SE环境下启动IoC容器

ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤)

FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件

AnnotationConfigApplicationContext:纯注解模式下启动Spring容器

  ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        TransferService transferService = (TransferService)applicationContext.getBean("transferService");

Web环境下启动IoC容器

Tomcat 启动会执行xml 文件 执行监听器 启动Spring的ioc容器 加载applicationContextxml文件

  • 从xml启动容器

    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--配置Spring ioc容器的配置⽂件-->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
      </context-param>
     <!-- 使用监听器配置Spring的ioc容器-->
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    </web-app>
    

    调用 WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());

  • 纯注解模式下从配置类启动容器

    <!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd" >
    <web-app>
    <display-name>Archetype Created Web Application</display-name>
    <!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
    <context-param>
    <param-name>contextClass</param-name>
    <paramvalue>
    org.springframework.web.context.support.AnnotationConfigWebAppli
    cationContext</param-value>
    </context-param>
    <!--配置启动类的全限定类名-->
    <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>com.lagou.edu.SpringConfig</param-value>
    </context-param>
    <!--使⽤监听器启动Spring的IOC容器-->
    <listener>
    <listenerclass>
    org.springframework.web.context.ContextLoaderListener</listenerclass>
    </listener>
    </web-app>
    

    image-20210325145543874

3. xml实例化bean的三种方式

  • 调用无参构造器(常用推荐)

    在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象。如果类中没有⽆参构造函数,将创建失败。

    <bean id="userService" class="com.lagou.service.impl.TransferServiceImpl">
    </bean>
    
  • 使⽤静态⽅法创建

    我们自己使用工厂模式创建出一个static 方法 里面 new 一个对象, 此时并不会创建该class类的对象而是我们指定的

    该静态方法的返回的对象放入ioc的容器当中。

      //静态方法创建对象
        public static User getAdmin(){
             
              return new User(1,"admin");
        }
    
    <!--使⽤静态⽅法创建对象的配置⽅式-->
    <bean id="userAdmin" class="com.lagou.factory.BeanFactory"
    factory-method="getAdmin"></bean>
    
  • 使⽤实例化⽅法创建

    此种⽅式和上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀个普通⽅法。此种⽅式⽐静态⽅法创建的使⽤⼏率要⾼⼀些。

    <!--使⽤实例⽅法创建对象的配置⽅式-->
    <bean id="beanFactory"
    class="com.lagou.factory.instancemethod.BeanFactory"></bean>
    <bean id="transferService" factory-bean="beanFactory" factorymethod="
    getTransferService"></bean>
    

4. bean的作用范围和生命周期

  • 作用范围

    在spring框架管理Bean对象的创建时,Bean对象默认都是单例的,但是它⽀持配置的⽅式改变作⽤范围。作⽤范围官⽅提供的说明如下图:

    image-20210326104453911

    在上图中提供的这些选项中,我们实际开发中⽤到最多的作⽤范围就是singleton(单例模式)和prototype(原型模式,也叫多例模式)。配置⽅式参考下⾯的代码:

    <!--配置service对象-->
    <bean id="transferService"
    class="com.lagou.service.impl.TransferServiceImpl" scope="singleton">
    </bean>
    
  • 生命周期

    单例模式:singleton

    对象出⽣:当创建容器时,对象就被创建了。

    对象活着:只要容器在,对象⼀直活着。

    对象死亡:当销毁容器时,对象就被销毁了。

    ⼀句话总结:单例模式的bean对象⽣命周期与容器相同。

    多例模式:prototype

    对象出⽣:当使⽤对象时,创建新的对象实例。

    对象活着:只要对象在使⽤中,就⼀直活着。

    对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。

    ⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁。

5. Bean标签属性

在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的⼀个对象。换句话说,如果⼀个对象想让spring管理,在XML的配置中都需要使⽤此标签配置,Bean标签的属性如下:

id属性: ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。

class属性:⽤于指定创建Bean对象的全限定类名。

name属性:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。

factory-bean属性:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。

factory-method属性:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。

scope属性:⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。

init-method属性:⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。

destory-method属性:⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。

6. DI 依赖注⼊的xml配置

1. 按照注⼊的⽅式分类

构造函数注⼊:顾名思义,就是利⽤带参构造函数实现对类成员的数据赋值。

set⽅法注⼊:它是通过类成员的set⽅法实现数据的注⼊。(使⽤最多的)

2. 按照注⼊的数据类型分类

基本类型和String

注⼊的数据类型是基本类型或者是字符串类型的数据。

其他Bean类型

注⼊的数据类型是对象类型,称为其他Bean的原因是,这个对象是要求出现在IoC容器中的。那么针对当前Bean来说,就是其他Bean了。

复杂类型(集合类型)

注⼊的数据类型是Aarry,List,Set,Map,Properties中的⼀种类型。

3. 依赖注⼊的配置具体实现

构造函数注⼊

就是利⽤构造函数实现对类成员的赋值。它的使⽤要求是,类中提供的构造函数参数个数必须和配置的参数个数⼀致,且数据类型匹配。同时需要注意的是,当没有⽆参构造时,则必须提供构造函数参数的注⼊,否则Spring框架会报错。该注入方式无法解决Spring的循环依赖问题

/**
 * @author lane
 * @date 2021年03月26日 上午10:23
 */
public class User {

    private int id;
    private String username;
    private Account account;

    public User(int id, String username, Account account) {
        this.id = id;
        this.username = username;
        this.account = account;
    }
}
 <!-- 构造方法注入-->
    <bean id="user" class="com.lagou.edu.pojo.User" >
        <constructor-arg name="id" value="1"></constructor-arg>
        <constructor-arg name="username" value="lane"></constructor-arg>
        <constructor-arg name="account" ref="account"></constructor-arg>
    </bean>

在使⽤构造函数注⼊时,涉及的标签是constructor-arg ,该标签有如下属性:

name:⽤于给构造函数中指定名称的参数赋值。

index:⽤于给构造函数中指定索引位置的参数赋值。

value:⽤于指定基本类型或者String类型的数据。

ref:⽤于指定其他Bean类型的数据。写的是其他bean的唯⼀标识。

set⽅法注⼊

利⽤字段的set⽅法实现赋值的注⼊⽅式。此种⽅式在实际开发中是使⽤最多的注⼊⽅式。

在使⽤set⽅法注⼊时,需要使⽤property 标签,该标签属性如下:

name:指定注⼊时调⽤的set⽅法名称。(注:不包含set这三个字⺟,druid连接池指定属性名称)

value:指定注⼊的数据。它⽀持基本类型和String类型。

ref:指定注⼊的数据。它⽀持其他bean类型。写的是其他bean的唯⼀标识。

复杂数据类型注⼊ ⾸先,解释⼀下复杂类型数据,它指的是集合类型数据。集合分为两类,⼀类是List结构(数组结构),⼀类是Map接⼝(键值对) 。

/**
 * @author lane
 * @date 2021年03月26日 上午10:23
 */
public class User {

    private int id;
    private String username;
    private Account account;
    private String[] myArray;
    private List<String> myList ;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProperties;
    public void setMyArray(String[] myArray) {
        this.myArray = myArray;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProperties(Properties myProperties) {
        this.myProperties = myProperties;
    }

   public void setId(int id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setAccount(Account account) {
        this.account = account;
    }
   
    <!--set方法注入-->
    <bean id="user3" class="com.lagou.edu.pojo.User">
        <!--set方法注入简单类型-->
        <property name="id" value="1"></property>
        <property name="username" value="lane"></property>
        <property name="account" ref="account"></property>
        <!--set方法注入复杂类型-->
        <property name="myArray">
            <array>
                <value>good</value>
                <value>good</value>
                <value>study</value>
            </array>
        </property>
        <property name="myList">
            <list>
                <value>day</value>
                <value>day</value>
                <value>up</value>
            </list>
        </property>
        <property name="mySet">
            <set>
                <value>start</value>
                <value>study</value>
            </set>
        </property>
        <property name="myMap">
            <map>
                <entry key="i" value="study"></entry>
                <entry key="you" value="study"></entry>
            </map>
        </property>
        <property name="myProperties">
            <props>
                <prop key="we">study</prop>
            </props>
        </property>
    </bean>
User{id=1, username='lane', account=Account{cardNo='6227', name='建设银行', money=10000}, myArray=[good, good, study], myList=[day, day, up], mySet=[start, study], myMap={i=study, you=study}, myProperties={we=study}}

注意

在List结构的集合数据注⼊时, array , list , set 这三个标签通⽤,另外注值的value 标签内部可以直接写值,也可以使⽤bean 标签配置⼀个对象,或者⽤ref 标签引⽤⼀个已经配合的bean的唯⼀标识。

在Map结构的集合数据注⼊时, map 标签使⽤entry ⼦标签实现数据注⼊, entry 标签可以使⽤key和value属性指定存⼊map中的数据。使⽤value-ref属性指定已经配置好的bean的引⽤。同时entry 标签中也可以使⽤ref 标签,但是不能使⽤bean 标签。⽽property 标签中不能使⽤ref 或者bean 标签引⽤对象

7. xml与注解相结合模式

1. 具体情况

1)实际企业开发中,纯xml模式使⽤已经很少了

2)引⼊注解功能,不需要引⼊额外的jar

3)xml+注解结合xml⽂件依然存在,所以,spring IOC容器的启动仍然从加载xml开始

4)一般原则 第三⽅jar中的bean定义在xml,⽐如德鲁伊数据库连接池。⾃⼰开发的bean定义使⽤注解模式。

2. xml中标签与注解的对应(IoC)

标签@Component(“accountDao”)注解加在类上bean的id属性内容直接配置在注解后⾯如果不配置,默认定义个这个bean的id为类

的类名⾸字⺟⼩写;另外,针对分层代码开发提供了@Componenet的三种别名@Controller、@Service、@Repository分别⽤于控制层类、服务层类、dao层类的bean定义,这四个注解的⽤法完全⼀样,只是为了更清晰的区分⽽已

标签的scope属性@Scope(“prototype”),默认单例,注解加在类上

标签的initmethod属性@PostConstruct,注解加在⽅法上,该⽅法就是初始化后调⽤的⽅法

标签的destorymethod属性@PreDestory,注解加在⽅法上,该⽅法就是销毁前调⽤的⽅法

3. DI 依赖注⼊的注解实现⽅式

@Autowired按照类型注⼊(推荐使⽤)

@Autowired为Spring提供的注解,需要导⼊包org.springframework.beans.factory.annotation.Autowired。

@Autowired采取的策略为按照类型注⼊。spring容器中找到类型为Account的类,然后将其注⼊进来。这样会产⽣⼀个问题,当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,这个时候我们需要配合着@Qualifier使⽤。@Qualifier告诉Spring具体去装配哪个对象。

 
public class User {
		private int id;
    private String username;
    @Autowired
    @Qualifier("account")
    private Account account;
}

@Resource 默认按照 ByName ⾃动注⼊

@Resource 注解由 J2EE 提供,需要导⼊包 javax.annotation.Resource。

如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。

如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。

如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。

如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配;

@Resource 在 Jdk 11中已经移除,如果要使⽤,需要单独引⼊jar包javax.annotation-api.不建议使用。

public class TransferService {
@Resource
private AccountDao accountDao;
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(type="TeacherDao")
private TeacherDao teacherDao;
@Resource(name="manDao",type="ManDao")
private ManDao manDao;
}
4.开启注解包扫描

引入spring 的xmlns:context

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           https://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           https://www.springframework.org/schema/context/spring-context.xsd" >

配置扫描包路径

<!-- 开启注解扫描,指定扫描的包路径-->
    <context:component-scan base-package="com.lagou.edu"></context:component-scan>
<!-- 引入外部资源文件,一般数据库连接信息放在jdbc.properties文件里-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

8. 纯注解模式

改造xm+注解模式,将xml中遗留的内容全部以注解的形式迁移出去,最终删除xml,从Java配置类启动。

1. 转移xml文件内容到Spring配置类里面,删除xml

@Configuration 注解,表名当前类是⼀个配置类

@ComponentScan 注解,替代 context:component-scan

@PropertySource,引⼊外部属性配置⽂件

@Import 引⼊其他配置类

@Value 对变量赋值,可以直接赋值,也可以使⽤ ${} 读取资源配置⽂件中的信息

@Bean 将⽅法返回对象加⼊ SpringIOC 容器


/**
 * 纯注解模式需要配置一个Spring的配置类,这样applicationContext.xml就可以删除了
 * @author lane
 * @date 2021年03月26日 下午2:43
 */
@Configuration //指定该类是一个Spring 的配置类
@ComponentScan({"com.lagou.edu"}) //配置ioc的注解扫描 参数为数组
@PropertySource({"classpath:jdbc.properties"}) //引入外部的properties文件
//@Import() //可以把其它配置类加载到这个里面,只需要启动这一个就可以了
public class SpringConfig {
    // 通过jdbc.properties 赋值
    @Value("${jdbc.driver}")
    private String driverClassName;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("$(jdbc.password)")
    private String password;

    //自定义bean放入ioc容器中
    @Bean
    public DataSource creatAnnotationDataSource(){
        DruidDataSource druidDataSource =  new DruidDataSource();
        druidDataSource.setDriverClassName(driverClassName);
        druidDataSource.setUrl(url);
        druidDataSource.setUsername(username);
        druidDataSource.setPassword(password);
        return   druidDataSource;
    }


}

2. 启动spring ioc 容器

Java SE模式下

 ApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(SpringConfig.class);
        User user2 = (User) applicationContext2.getBean("user");

Java web 下

<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
  
<!--告诉ContextloaderListener知道我们使⽤注解的⽅式启动ioc容器-->
<context-param>
<param-name>contextClass</param-name>
<paramvalue>
org.springframework.web.context.support.AnnotationConfigWebAppli
cationContext</param-value>
</context-param>
  
<!--指定配置启动类的全限定类名-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>com.lagou.edu.SpringConfig</param-value>
</context-param>
  
<!--使⽤监听器启动Spring的IOC容器-->
<listener>
<listenerclass>
org.springframework.web.context.ContextLoaderListener</listenerclass>
</listener>
</web-app>

Spring IOC bean的生命周期

1. 解析xml bean封装成beanDefination对象

2. ⼯⼚初始化(BeanFactory)

3. bean的生命周期

基本图示意

image-20210326163118599

image-20210327194402026

基本步骤
  • 如果有bean实现了InstantiationAwareBeanPostProcessor 会调用Bean实例化之前扩展点
  • Spring容器对bean进行实例化,默认bean是单例
    Bean实例化之后扩展点
  • InstantiationAwareBeanPostProcessor中方法的属性处理
    Spring对bean进行依赖注入
  • 如果bean实现了BeanNameAware接口,Spring将bean的id传给setBeanName()方法;
    如果bean实现了BeanFactoryAware接口,spring将调用setBeanFactory方法,将BeanFactory实例传进来;
  • 如果有bean实现了BeanPostProcessor接口,它的postProcessBeforeInitialization方法将被调用
  • 如果bean实现了InitializingBean接口,Spring将调用它的afterPropertiesSet接口方法,如果有init-method方法则被调用
  • 如果bean实现了BeanPostProcessor接口,它的postProcessAfterInitialization接口方法将被调用
  • 注册销毁回调函数
  • bean已经准备就绪,可以被应用程序使用
  • 若bean实现了DisposableBean接口,Spring将调用它的distroy()接口方法。同样的,如果bean使用了destroy-method属性声明了销毁方法,则该方法被调用
代码实现Spring生命周期的接口方法执行顺序的演示

普通类实现Spring生命周期中接口

/**
 * @author lane
 * @date 2021年03月26日 下午3:35
 */
public class Apple implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean  {

    private int id;
    private String color;

    public void setId(int id) {
        this.id = id;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "id=" + id +
                ", color='" + color + '\'' +
                '}';
    }
    @Override
    public void setBeanName(String s) {
        System.out.println("我的名字是"+s);
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("我是在哪个工厂创建的"+beanFactory);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("高级容器接口"+applicationContext);
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行生命周期中的afterPropertiesSet");
    }
    public void initMethod(){
        System.out.println("执行了init-method 方法");
    }
    @PostConstruct
    public void postConstruct(){
        System.out.println("执行postConstruct 注解的方法");
    }
    @PreDestroy
    public void preDestory(){
        System.out.println("执行preDestroy方法");
    }
}

Spring的后置处理器之BeanPostProcessor

/**
 * 拦截实例化之后的对象(实例化了且属性注入了)
 * @author lane
 * @date 2021年03月26日 下午4:49
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      //默认拦截所有的,指定某一个bean
        if ("apple".equalsIgnoreCase(beanName)){
            System.out.println("MyBeanPostProcessor 的before方法执行了"+beanName+"被拦截了,挺急的");
        }

        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if ("apple".equalsIgnoreCase(beanName)){
            System.out.println("MyBeanPostProcessor 的after方法执行了"+beanName+"被拦截了,不用急");
        }

        return bean;
    }

}

具体调用bean

 Apple apple2= (Apple) applicationContext.getBean("apple");
        System.out.println(apple2);

打印结果

我的名字是apple
我是在哪个工厂创建的org.springframework.beans.factory.support.DefaultListableBeanFactory@563f38c4:
高级容器接口org.springframework.context.support.ClassPathXmlApplicationContext@dd3b207, started on Fri Mar 26 16:26:26 CST 2021

MyBeanPostProcessor 的before方法执行了apple被拦截了,挺急的
执行postConstruct 注解的方法
执行生命周期中的afterPropertiesSet
执行了init-method 方法
MyBeanPostProcessor 的after方法执行了apple被拦截了,不用急

Spring IOC 的高级特性

1. lazy-Init 延迟加载

Bean的延迟加载(延迟创建)

ApplicationContext 容器的默认⾏为是在启动服务器时将所有 singleton bean 提前进⾏实例化。提前实例化意味着作为初始化过程的⼀部分,ApplicationContext 实例会创建并配置所有的singleton bean。

<bean id="testBean" class="cn.lagou.LazyBean" />
该bean默认的设置为:
<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="false" />

lazy-init=“false”,⽴即加载,表示在spring启动时,⽴刻进⾏实例化。如果不想让⼀个singleton bean 在 ApplicationContext实现初始化时被提前实例化,那么可以将bean设置为延迟实例化。设置 lazy-init 为 true 的 bean 将不会在 ApplicationContext 启动时提前被实例化,⽽是第⼀次向容器通过 getBean 索取 bean 时实例化的。

<bean id="testBean" calss="cn.lagou.LazyBean" lazy-init="true" />

如果⼀个设置了⽴即加载的 bean1,引⽤了⼀个延迟加载的 bean2 ,那么 bean1 在容器启动时被实例化,⽽ bean2 由于被 bean1 引⽤,所以也被实例化,这种情况也符合延时加载的 bean 在第⼀次调⽤时才被实例化的规则。

也可以在容器层次中通过在 元素上使⽤ “default-lazy-init” 属性来控制延时初始化。如下⾯配置:

<beans default-lazy-init="true">
<!-- no beans will be eagerly pre-instantiated... -->
</beans>

如果⼀个 bean 的 scope 属性为 scope=“pototype” 时,即使设置了 lazy-init=“false”,容器启动时也不会实例化bean,⽽是调⽤ getBean ⽅法实例化的。

延迟加载应用场景

  • 开启延迟加载⼀定程度提⾼容器启动和运转性能
  • 对于不常使⽤的 Bean 设置延迟加载,这样偶尔使⽤的时候再加载,不必要从⼀开始该 Bean 就占⽤资源

2. FactoryBean 和 BeanFactory

简单介绍工厂bean(FactoryBean)

BeanFactory接⼝是容器的顶级接⼝,定义了容器的⼀些基础⾏为,负责⽣产和管理Bean的⼀个⼯⼚,具体使⽤它下⾯的⼦接⼝类型,⽐如ApplicationContext;此处我们重点分析FactoryBean

Spring中Bean有两种,⼀种是普通Bean,⼀种是⼯⼚Bean(FactoryBean),FactoryBean可以⽣成某⼀个类型的Bean实例(返回给我们),也就是说我们可以借助于它⾃定义Bean的创建过程。

Bean创建的三种⽅式中的静态⽅法和实例化⽅法和FactoryBean作⽤类似,FactoryBean使⽤较多,尤其在Spring框架⼀些组件中会使⽤,还有其他框架和Spring框架整合时使⽤。

获取FactoryBean,而不是其生产的对象则需要在id之前添加“&”

// 可以让我们⾃定义Bean的创建过程(完成复杂Bean的定义)
public interface FactoryBean<T> {
@Nullable
// 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map
T getObject() throws Exception;
@Nullable
// 返回FactoryBean创建的Bean类型
Class<?> getObjectType();
// 返回作⽤域是否单例
default boolean isSingleton() {
return true;
}
}

具体代码实现

/**
 * 苹果类
 * @author lane
 * @date 2021年03月26日 下午3:35
 */
public class Apple {

    private int id;
    private String color;

    public void setId(int id) {
        this.id = id;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "id=" + id +
                ", color='" + color + '\'' +
                '}';
    }
}


/**
 * 苹果bean的加工厂既是 factoryBean
 * @author lane
 * @date 2021年03月26日 下午3:38
 */
public class AppleFactoryBean implements FactoryBean<Apple> {

    private String appleInfo;

    public void setAppleInfo(String appleInfo) {
        this.appleInfo = appleInfo;
    }

    @Override
    public Apple getObject() throws Exception {

        String[] split = appleInfo.split("-");
        Apple apple = new Apple();
        apple.setId(Integer.valueOf(split[0]));
        apple.setColor(split[1]);
        return apple;
    }

    @Override
    public Class<?> getObjectType() {
        return Apple.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public String toString() {
        return  "这是apple的factory 不是apple了";
    }
}
<!-- xml配置-->
<bean id="apple" class="com.lagou.edu.pojo.Apple"></bean>
<!--    工厂bean-->
    <bean id="appleBean" class="com.lagou.edu.factory.AppleFactoryBean">
        <property name="appleInfo" value="1-红色"></property>
    </bean>
 				//从工厂生产出的苹果
        Apple apple = (Apple) applicationContext.getBean("appleBean");
        System.out.println(apple);
        //加上&符号就把工厂放入Spring容器中了
        AppleFactoryBean appleFactoryBean = (AppleFactoryBean) applicationContext.getBean("&appleBean");
        System.out.println(appleFactoryBean);
				//打印的效果
				//Apple{id=1, color='红色'} 这是apple的factory 不是apple了

3. Spring 的后置处理器

​ Spring提供了两种后处理bean的扩展接⼝,分别为 BeanPostProcessor 和BeanFactoryPostProcessor,两者在使⽤上是有所区别的。

在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情(比如替换数据库配置文件的占位符)。

在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情

注意:对象不⼀定是springbean,⽽springbean⼀定是个对象

SpringBean在工厂初始化之后的生命周期

image-20210326163118599

image-20210326163234844

BeanFactoryPostProcessor

BeanDefinition对象:我们在 XML 中定义的 bean标签,Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefinition。调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefinition对象。

BeanFactory级别的处理 是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfigurer 此接⼝只提供了⼀个⽅法,⽅法参数为ConfigurableListableBeanFactory,该参数类型定义了⼀些⽅法

image-20210326163502147

image-20210326163535210

其中有个⽅法名为getBeanDefinition的⽅法,我们可以根据此⽅法,找到我们定义bean 的BeanDefinition对象。然后我们可以对定义的属性进⾏修改,以下是BeanDefinition中的⽅法,⽅法名字类似我们bean标签的属性,setBeanClassName对应bean标签中的class属性,所以当我们拿到BeanDefinition对象时,我们可以⼿动修改bean标签中所定义的属性值。

image-20210326163610835

BeanPostProcessor

BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean.

image-20210326163943752

该接⼝提供了两个⽅法,分别在Bean的初始化⽅法前和初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法

定义⼀个类实现了BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String,第⼀个参数是每个bean的实例,第⼆个参数是每个bean的name或者id属性的值。所以我们可以通过第⼆个参数,来判断我们将要处理的具体的bean。注意:处理是发⽣在Spring容器的实例化和依赖注⼊之后。在Spring内部可用于事务和aop相关生成代理类。

代码实现Spring生命周期的接口方法(同上)

普通类实现Spring生命周期中接口

/**
 * @author lane
 * @date 2021年03月26日 下午3:35
 */
public class Apple implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean  {

    private int id;
    private String color;

    public void setId(int id) {
        this.id = id;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "id=" + id +
                ", color='" + color + '\'' +
                '}';
    }
    @Override
    public void setBeanName(String s) {
        System.out.println("我的名字是"+s);
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("我是在哪个工厂创建的"+beanFactory);
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("高级容器接口"+applicationContext);
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("执行生命周期中的afterPropertiesSet");
    }
    public void initMethod(){
        System.out.println("执行了init-method 方法");
    }
    @PostConstruct
    public void postConstruct(){
        System.out.println("执行postConstruct 注解的方法");
    }
    @PreDestroy
    public void preDestory(){
        System.out.println("执行preDestroy方法");
    }
}

Spring的后置处理器之BeanPostProcessor

/**
 * 拦截实例化之后的对象(实例化了且属性注入了)
 * @author lane
 * @date 2021年03月26日 下午4:49
 */
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      //默认拦截所有的,指定某一个bean
        if ("apple".equalsIgnoreCase(beanName)){
            System.out.println("MyBeanPostProcessor 的before方法执行了"+beanName+"被拦截了,挺急的");
        }

        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {

        if ("apple".equalsIgnoreCase(beanName)){
            System.out.println("MyBeanPostProcessor 的after方法执行了"+beanName+"被拦截了,不用急");
        }

        return bean;
    }

}

具体调用bean

 Apple apple2= (Apple) applicationContext.getBean("apple");
        System.out.println(apple2);

打印结果

我的名字是apple
我是在哪个工厂创建的org.springframework.beans.factory.support.DefaultListableBeanFactory@563f38c4:
高级容器接口org.springframework.context.support.ClassPathXmlApplicationContext@dd3b207, started on Fri Mar 26 16:26:26 CST 2021

MyBeanPostProcessor 的before方法执行了apple被拦截了,挺急的
执行postConstruct 注解的方法
执行生命周期中的afterPropertiesSet
执行了init-method 方法
MyBeanPostProcessor 的after方法执行了apple被拦截了,不用急

Spring IoC循环依赖问题

1. 什么是循环依赖

循环依赖其实就是循环引⽤,也就是两个或者两个以上的 Bean 互相持有对⽅,最终形成闭环。⽐如A依赖于B,B又依赖于A。套娃模式无限循环。这⾥不是函数的循环调⽤,是对象的相互依赖关系。循环调⽤其实就是⼀个死循环,除⾮有终结条件。

image-20210327173647881

2. Spring中循环依赖场景有哪些

1. 构造器的循环依赖(构造器注⼊)

2. Field 属性的循环依赖(set注⼊)

3. Spring是如何处理循环依赖的

1. 构造器的循环依赖无法解决

​ 因为构造器要调用构造函数new 一个对象出来,而参数又依赖于另一个对象。创建类A依赖于类B,new 的时候去创建类B发现类B不存在就会出错拋出 BeanCurrentlyInCreationException 异常。

2. prototype 原型bean循环依赖⽆法解决

对于原型bean的初始化过程中不论是通过构造器参数循环依赖还是通过setXxx⽅法产⽣循环依赖,Spring都 会直接报错处理。

if (isPrototypeCurrentlyInCreation(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}


protected boolean isPrototypeCurrentlyInCreation(String beanName) {
Object curVal = this.prototypesCurrentlyInCreation.get();
return (curVal != null &&
(curVal.equals(beanName) || (curVal instanceof Set && ((Set<?>)
curVal).contains(beanName))));
}

在获取bean之前如果这个原型bean正在被创建则直接抛出异常。原型bean在创建之前会进⾏标记这个beanName正在被创建,等创建结束之后会删除标记

try {
//创建原型bean之前添加标记
beforePrototypeCreation(beanName);
//创建原型bean
prototypeInstance = createBean(beanName, mbd, args);
}
finally {
//创建原型bean之后删除标记
afterPrototypeCreation(beanName);
}
3. singleton bean 可以通过三级缓存的方式解决循环依赖
  1. 类A开始调用set方法注入类B,在缓存池中找不到类B,就把类A放到三级缓存池singletonFactorys,转而开始创建类B。
  2. 创建类B发现要set方法注入类A,在一级缓存容器中singletonObjects发现类A不存在,继续找,发现在三级缓存池中有半成品的类A的对象。
  3. 根据在三级缓存池中的半成品类A,成功完成类B的创建。然后从三级缓存池放入二级缓存池earlySingletonObjects再放入一级缓存池。
  4. 类A从一级缓存池singletonObjects成功获取类B,完成对象的创建。成功的解决了循环依赖的问题。

image-20210327180207741

4. spring 源码看循环依赖解决
  1. 创建类A后开始填充其属性类B

    image-20210327172107127

  2. 半成品类A对象放入三级缓存中

    image-20210327171847756

  3. 类B根据三级缓存池中的半成品类A对象完成创建放入二级缓存当中

    image-20210327172921280

  4. 把二级缓存池中的完成的对象类B放入一级缓存池

    image-20210327173210673

Spring AOP 的具体应用

1. AOP的本质

在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、⽇志代码、事务控制代码、性能监控代码。在原有业务的上下层添加增强额外的功能。下图描述的就是未采⽤AOP思想设计的程序,当我们红⾊框中圈定的⽅法时,会带来⼤量的重复劳动。程序中充斥着⼤量的重复代码,使我们程序的独⽴性很差。

image-20210328125138664

⽽下图中是采⽤了AOP思想设计的程序,它把红框部分的代码抽取出来的同时,运⽤动态代理技术,统一在运⾏期对需要使⽤的业务逻辑⽅法进⾏增

image-20210328125430076

2. AOP的常用术语

名词解释
Joinpoint(连接点)它指的是那些可以⽤于把增强代码加⼊到业务主线中的点,那么由上图中我们可 以看出,这些点指的就是⽅法。在⽅法执⾏的前后通过动态代理技术加⼊增强的 代码。在Spring框架AOP思想的技术实现中,也只⽀持⽅法类型的连接点。
Pointcut(切⼊点)它指的是那些已经把增强代码加⼊到业务主线进来之后的连接点。由上图中,我 们看出表现层transfer ⽅法就只是连接点,因为判断访问权限的功能并没有对其增强。
Advice(通知/增强)它指的是切⾯类中⽤于提供增强功能的⽅法。并且不同的⽅法增强的时机是不⼀ 样的。⽐如,开启事务肯定要在业务⽅法执⾏之前执⾏;提交事务要在业务⽅法 正常执⾏之后执⾏,⽽回滚事务要在业务⽅法执⾏产⽣异常之后执⾏等等。那么 这些就是通知的类型。其分类有:前置通知 后置通知 异常通知 最终通知 环绕通知
Target(⽬标对象)它指的是代理的⽬标对象。即被代理对象。
Proxy(代理)它指的是⼀个类被AOP织⼊增强后,产⽣的代理类。即代理对象。
Weaving(织入)它指的是把增强应⽤到⽬标对象来创建新的代理对象的过程。spring采⽤动态代 理织⼊,⽽AspectJ采⽤编译期织⼊和类装载期织⼊。
Aspect(切⾯)它指定是增强的代码所关注的⽅⾯,把这些相关的增强代码定义到⼀个类中,这 个类就是切⾯类。例如,事务切⾯,它⾥⾯定义的⽅法就是和事务相关的,像开 启事务,提交事务,回滚事务等等,不会定义其他与事务⽆关的⽅法。我们前⾯ 的案例中TrasnactionManager 就是⼀个切⾯。

连接点:⽅法开始时、结束时、正常运⾏完毕时、⽅法异常时等这些特殊的时机点,我们称之为连接点,项⽬中每个⽅法都有连接点,连接点是⼀种候选点

切⼊点:指定AOP思想想要影响的具体⽅法是哪些,描述感兴趣的⽅法

Advice增强

第⼀个层次:指的是横切逻辑

第⼆个层次:⽅位点(在某⼀些连接点上加⼊横切逻辑,那么这些连接点就叫做⽅位点,描述的是具体的特殊时机)

Aspect切⾯:切⾯概念是对上述概念的⼀个综合 Aspect切⾯ = 切⼊点+增强 = 切⼊点(锁定⽅法) + ⽅位点(锁定⽅法中的特殊时机)+ 横切逻辑众多的概念,⽬的就是为了锁定要在哪个地⽅插⼊什么横切逻辑代码

3. Spring中AOP的代理选择

Spring 实现AOP思想使⽤的是动态代理技术

默认情况下,Spring会根据被代理对象是否实现接⼝来选择使⽤JDK还是CGLIB。当被代理对象没有实现任何接⼝时,Spring会选择CGLIB。当被代理对象实现了接⼝,Spring会选择JDK官⽅的代理技术,不过我们可以通过配置的⽅式,让Spring强制使⽤CGLIB。

4. AOP的具体使用xml模式下

  1. 首先引入spring aop相关的jar
 <!--spring aop的jar包支持-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>

    <!--第三方的aop框架aspectj的jar-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.13</version>
    </dependency>
  </dependencies>
  1. 横切面的创建

/**
 * aop横切面
 * @author lane
 * @date 2021年03月28日 下午1:23
 */
public class AopLogUtils {

    /*
     * aop之业务方法之前执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public void beforeMethod(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println("aop之业务方法之前执行......获取参数0为"+args[0]);
    }

    /*
     * aop之业务方法之后执行(无论异常与否)
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public void afterMethod(){
        System.out.println("aop之业务方法之后执行(无论异常与否)......");
    }

    /*
     * aop之业务方法异常时执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public void exceptionMethod(){
        System.out.println("aop之业务方法之异常时执行......");
    }
    /*
     * aop之业务方法成功时执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public void successMethod(){
        System.out.println("aop之业务方法之成功时执行......");
    }
    /*
     * aop之业务方法之环绕通知1v5
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint )throws Throwable {
        Object result = null;
        System.out.println("环绕通知方法之前执行类似beforeMethod......");
        try{
            //此方法类似method.invoke(),不执行的话原方法就不会执行,所以可以加前置后置通知,也可以控制原有方法是否执行
            result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        }catch (Exception e){
            System.out.println("环绕通知方法之后执行类似exceptionMethod......");

        }  finally {
            System.out.println("环绕通知方法之后执行类似afterMethod......");
        }
        System.out.println("环绕通知方法成功之后执行类似successMethod......");
        return result;
    }
}
  1. xml核心配置
 <!--
在Spring的配置⽂件中加⼊aop的约束 xmlns:aop
-->
<beans  xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
">
<!-- Spring基于XML的AOP配置步骤
第⼀步:把通知Bean交给Spring管理
第⼆步:使⽤aop:config开始aop的配置
第三步:使⽤aop:aspect配置切⾯
第四步:使⽤对应的标签配置通知的类型-->
  
<!--进行aop相关的xml配置,配置aop的过程其实就是把aop相关术语落地-->
    <!--配置横切逻辑bean-->
    <bean id="aopLogUtils" class="com.lagou.edu.utils.AopLogUtils"></bean>
    <!--使用aop config 标签表明aop开始配置,在内部配置切面aspect-->
    <!--aspect = 切入点(锁定方法) + 方位点(锁定方法中的特殊时机) +横切逻辑-->
    <aop:config>
        <aop:aspect id="logAspect" ref="aopLogUtils">
            <!--切入点锁定我们感兴趣的方法,使用aspectj语法表达式 -->
            <aop:pointcut id="pt1" expression="execution(public void  com.lagou.edu.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int))"/>
            <!--方位点 pointcut-ref引用切入点,pointcut则是直接指定具的方法中表达式内容-->
            <aop:before method="beforeMethod" pointcut-ref="pt1"/>
            <aop:after method="afterMethod" pointcut-ref="pt1"  />
            <aop:after-returning method="successMethod" pointcut-ref="pt1"/>
            <aop:after-throwing method="exceptionMethod" pointcut-ref="pt1"/>
            <!--环绕通知1v5尽量不要和其它方法一起执行-->
            <aop:around method="aroundMethod" pointcut-ref="pt1"/>
        </aop:aspect>
    </aop:config>
  1. 测试代码
/**
     * 测试bean的aop xml配置
     */
    @Test
    public void testAopXml() throws Exception {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        TransferService transferService = (TransferService) applicationContext.getBean("transferService");
        transferService.transfer("6029621011000","6029621011001",100);

    }
  1. 测试结果

aop之业务方法之前执行…获取参数0为6029621011000
环绕通知方法之前执行类似beforeMethod…

执行转账业务逻辑

环绕通知方法之后执行类似afterMethod…
环绕通知方法成功之后执行类似successMethod…

aop之业务方法之成功时执行…
aop之业务方法之后执行(无论异常与否)…

  1. aspectj表达式用法

    表达式介绍

全限定⽅法名 访问修饰符 返回值 包名.包名.包名.类名.⽅法名(参数列表)
全匹配⽅式:
public void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
访问修饰符可以省略
void com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
返回值可以使⽤*,表示任意返回值
* com.lagou.service.impl.TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
 包名可以使⽤.表示任意包,但是有⼏级包,必须写⼏个
* ....TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
包名可以使⽤..表示当前包及其⼦包
* ..TransferServiceImpl.updateAccountByCardNo(com.lagou.pojo.Account)
类名和⽅法名,都可以使⽤.表示任意类,任意⽅法
* ...(com.lagou.pojo.Account)
参数列表,可以使⽤具体类型
基本类型直接写类型名称 : int
引⽤类型必须写全限定类名:java.lang.String
参数列表可以使⽤*,表示任意参数类型,但是必须有参数
* *..*.*(*)
参数列表可以使⽤..,表示有⽆参数均可。有参数可以是任意类型
* *..*.*(..)
全通配⽅式:
* *..*.*(..)

举个栗子从单个方法到全部拦截

拦截表达式
<aop:pointcut id="pt1" expression="execution(public void  com.lagou.edu.service.impl.TransferServiceImpl.transfer(java.lang.String,java.lang.String,int))"/>
一级变换每一个包、类、方法、返回值都变成*替换
<aop:pointcut id="pt1" expression="execution(* *.*.*.*.*.*.*(java.lang.String,java.lang.String,int))"/> 
二级变换所有的包变成*.. 类变成*.
<aop:pointcut id="pt1" expression="execution(* *..*.*(java.lang.String,java.lang.String,int))"/>  
三级变换参数变成*,必须有参数的 
<aop:pointcut id="pt1" expression="execution(* *..*.*(*))"/>  
四级变换,参数可有可无的,不建议这样配哟
<aop:pointcut id="pt1" expression="execution(* *..*.*(..))"/> 
  1. 改变代理⽅式的配置

在前⾯我们已经说了,Spring在选择创建代理对象时,会根据被代理对象的实际情况来选择的。被代理对象实现了接⼝,则采⽤基于接⼝的动态代理。当被代理对象没有实现任何接⼝的时候,Spring会⾃动切换到基于⼦类的动态代理⽅式。但是我们都知道,⽆论被代理对象是否实现接⼝,只要不是final修饰的类都可以采⽤cglib提供的⽅式创建代理对象。所以Spring也考虑到了这个情况,提供了配置的⽅式实现强制使⽤基于⼦类的动态代理(即cglib的⽅式),配置的⽅式有两种

  • 使⽤aop:config标签配置
<aop:config proxy-target-class="true">
  • 使⽤aop:aspectj-autoproxy标签配置
<!--此标签是基于XML和注解组合配置AOP时的必备标签,表示Spring开启注解配置AOP的⽀持-->
<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectjautoproxy>

5. AOP的具体使用注解模式下

1.依赖同上
2.切面类配置信息

/**
 * aop横切面注解模式
 * @author lane
 * @date 2021年03月28日 下午1:23
 */
@Component
@Aspect
//@EnableAspectJAutoProxy //纯注解模式下在配置类上加上这个,只是在这做示范
public class AopLogUtilsAnno {
    //指定切入点
    @Pointcut("execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(..))")
    public void pt1(){

    }
    /*
     * aop之业务方法之前执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @Before("pt1()")
    public void beforeMethod(JoinPoint joinPoint){
        Object[] args = joinPoint.getArgs();
        System.out.println("aop之业务方法之前执行......获取参数0为"+args[0]);
    }

    /*
     * aop之业务方法之后执行(无论异常与否)
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @After("pt1()")
    public void afterMethod(){
        System.out.println("aop之业务方法之后执行(无论异常与否)......");
    }

    /*
     * aop之业务方法异常时执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @AfterThrowing("pt1()")
    public void exceptionMethod(){
        System.out.println("aop之业务方法之异常时执行......");
    }
    /*
     * aop之业务方法成功时执行
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @AfterReturning("pt1()")
    public void successMethod(){
        System.out.println("aop之业务方法之成功时执行......");
    }
    /*
     * aop之业务方法之环绕通知1v5
     * @author lane
     * @date 2021/3/28 下午1:24
     */
    @Around("pt1()")
    public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint )throws Throwable {
        Object result = null;
        System.out.println("环绕通知方法之前执行类似beforeMethod......");
        try{
            //此方法类似method.invoke(),不执行的话原方法就不会执行,所以可以加前置后置通知,也可以控制原有方法是否执行
            result = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
        }catch (Exception e){
            System.out.println("环绕通知方法之后执行类似exceptionMethod......");

        }  finally {
            System.out.println("环绕通知方法之后执行类似afterMethod......");
        }
        System.out.println("环绕通知方法成功之后执行类似successMethod......");
        return result;
    }
}

3. xml配置
  • 有xml模式下的配置
<!--开启aop注解驱动,proxy-target-class = true则强制使用cglib动态代理-->
    <aop:aspectj-autoproxy proxy-target-class="true"/>
<!--开启aop注解驱动spring自己选择动态代理-->
    <aop:aspectj-autoproxy/>
  • 纯注解模式下开起aop注解驱动需要在配置类上加如下注解

    @EnableAspectJAutoProxy

4. 测试类
 /**
     * 测试bean的aop anno配置
     */
    @Test
    public void testAopAnno() throws Exception {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        TransferService transferService = (TransferService) applicationContext.getBean("transferService");
        transferService.transfer("6029621011000","6029621011001",100);

    }
5. 测试结果

环绕通知方法之前执行类似beforeMethod…
aop之业务方法之前执行…获取参数0为6029621011000

执行转账业务逻辑

环绕通知方法之后执行类似afterMethod…
环绕通知方法成功之后执行类似successMethod…
aop之业务方法之后执行(无论异常与否)…
aop之业务方法之成功时执行…

Spring 声明式事务的⽀持

编程式事务:在业务代码中添加事务控制代码,这样的事务控制机制就叫做编程式事务

声明式事务:通过xml或者注解配置的⽅式达到事务控制的⽬的,叫做声明式事务

1. 事务的回顾

1.1 事务的概念

事务指逻辑上的⼀组操作,组成这组操作的各个单元,要么全部成功,要么全部不成功。从⽽确保了数据的准确与安全。类比转账,一方转走一方转入,必须都成功或者都失败,没有事务是会出现大问题的。

1.2 事务的四⼤特性

原⼦性(Atomicity) 原⼦性是指事务是⼀个不可分割的⼯作单位,事务中的操作要么都发⽣,要么都不发⽣。从操作的⻆度来描述,事务中的各个操作要么都成功要么都失败。

⼀致性(Consistency) 事务必须使数据库从⼀个⼀致性状态变换到另外⼀个⼀致性状态。

例如转账前A有1000,B有1000。转账后A+B也得是2000。⼀致性是从数据的⻆度来说的,(1000,1000) (900,1100),不应该出现(900,1000)

隔离性(Isolation) 事务的隔离性是多个⽤户并发访问数据库时,数据库为每⼀个⽤户开启的事务,每个事务不能被其他事务的操作数据所⼲扰,多个并发事务之间要相互隔离。

⽐如:事务1给员⼯涨⼯资2000,但是事务1尚未被提交,员⼯发起事务2查询⼯资,发现⼯资涨了2000块钱,读到了事务1尚未提交的数据(脏读)

**持久性(Durability)**持久性是指⼀个事务⼀旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发⽣故障也不应该对其有任何影响。

1.3 事务的隔离级别

不考虑隔离级别,会出现以下情况:(以下情况全是错误的),也即为隔离级别在解决事务并发问题

脏读:⼀个线程中的事务读到了另外⼀个线程中未提交的数据。

不可重复读:⼀个线程中的事务读到了另外⼀个线程中已经提交的update的数据(前后内容不⼀样)

场景:

员⼯A发起事务1,查询⼯资,⼯资为1w,此时事务1尚未关闭

财务⼈员发起了事务2,给员⼯A张了2000块钱,并且提交了事务

员⼯A通过事务1再次发起查询请求,发现⼯资为1.2w,原来读出来1w读不到了,叫做不可重复读

虚读(幻读):⼀个线程中的事务读到了另外⼀个线程中已经提交的insert或者delete的数据(前后条

数不⼀样)

场景:

事务1查询所有⼯资为1w的员⼯的总数,查询出来了10个⼈,此时事务尚未关闭

事务2财务⼈员发起,新来员⼯,⼯资1w,向表中插⼊了2条数据,并且提交了事务

事务1再次查询⼯资为1w的员⼯个数,发现有12个⼈,⻅了⻤了

数据库共定义了四种隔离级别:

Serializable(串⾏化):可避免脏读、不可重复读、虚读情况的发⽣。(串⾏化) 最⾼

Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 第⼆

该机制下会对要update的⾏进⾏加锁

Read committed(读已提交):可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。 第三

Read uncommitted(读未提交):最低级别,以上情况均⽆法保证。(读未提交) 最低

注意:级别依次升⾼,效率依次降低

MySQL的默认隔离级别是:REPEATABLE READ

查询当前使⽤的隔离级别: select @@tx_isolation;

设置MySQL事务的隔离级别: set session transaction isolation level xxx; (设置的是当前mysql连接会话的,并不是永久改变的)

1.4 事务的传播⾏为

事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏为。

A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为

PROPAGATION_REQUIRED如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中, 加⼊到这个事务中。这是最常⻅的选择。
PROPAGATION_SUPPORTS⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。
PROPAGATION_MANDATORY使⽤当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则执⾏与PROPAGATION_REQUIRED类似的操作。

2. Spring中事务的具体实现xml下

2.1 引入依赖

<!--spring aop的jar包支持-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>

    <!--第三方的aop框架aspectj的jar-->
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.8.13</version>
    </dependency>

    <!--引入spring声明式事务相关-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.1.12.RELEASE</version>
    </dependency>
2.2 xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans  xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
">

    <!--开启注解扫描,base-package指定扫描的包路径-->
    <context:component-scan base-package="com.lagou.edu"/>

    <!--引入外部资源文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--第三方jar中的bean定义在xml中数据源配置-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!--给Spring内置的jdbcTemplate配置数据源-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>

    <!--开始Spring声明式事务的配置-->
    <!--spring声明式事务配置,声明式事务无非就是配置一个aop,只不过有些标签不一样罢了-->
    <!--给横切逻辑transactionManager配置构造器注入-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean>
    <aop:config>     <!--advice-ref指向增强 横切逻辑+方位-->
        <aop:advisor advice-ref="txAdvice"  pointcut="execution(* com.lagou.edu.service.impl.TransferServiceImpl.*(*))"/>
    </aop:config>
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--定制事务细节,事务传播行为,隔离级别等-->
        <tx:attributes>
            <!--一般配置-->
            <tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT"/>
            <!--具体查询覆盖性配置-->
            <tx:method name="query*" read-only="true" propagation="SUPPORTS" isolation="DEFAULT"/>
        </tx:attributes>
    </tx:advice>
</beans>
2.3 java操作执行sql语句

/**
 * @author 
 */
@Repository("accountDao")
public class JdbcTemplateDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account queryAccountByCardNo(String cardNo) throws Exception {
        String sql = "select * from account where cardNo=?";
        return jdbcTemplate.queryForObject(sql, new RowMapper<Account>() {
            @Override
            public Account mapRow(ResultSet resultSet, int i) throws SQLException {
                Account account = new Account();
                account.setName(resultSet.getString("name"));
                account.setCardNo(resultSet.getString("cardNo"));
                account.setMoney(resultSet.getInt("money"));
                return account;
            }
        }, cardNo);
    }


    @Override
    public int updateAccountByCardNo(Account account) throws Exception {
        String sql = "update account set money=? where cardNo=?";

        return jdbcTemplate.update(sql,account.getMoney(),account.getCardNo());
    }
}

其中jdbcTemplate 有很多方法queryForObject,queryForList等,参数依次传就可以了。

2.4 Java操作之转账方法

/**
 * @author 应癫
 */
@Service("transferService")
@Transactional
public class TransferServiceImpl implements TransferService {


    @Autowired
    @Qualifier("accountDao")
    private AccountDao accountDao;

    @Override
    public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {

            Account from = accountDao.queryAccountByCardNo(fromCardNo);
            Account to = accountDao.queryAccountByCardNo(toCardNo);

            from.setMoney(from.getMoney()-money);
            to.setMoney(to.getMoney()+money);
            accountDao.updateAccountByCardNo(to);
            int c = 1/0;
            accountDao.updateAccountByCardNo(from);  

    }
}

2.5 操作结果

因为有 int c = 1/0 故而出错,配置了Spring的事务,成功实现了回滚

3. Spring中事务的具体实现xml+注解模式下和纯注解模式下

1. 在xml模式下的xml加上注解驱动注释掉xml下的事务配置aop和tx
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--开启spring对注解事务的⽀持-->
<tx:annotation-driven transaction-manager="transactionManager"/>
 
2. 在具体的接口或者实现类或者方法上添加注解
/**
 * @author 
 */
@Service("transferService")
@Transactional(readOnly = false,propagation = Propagation.SUPPORTS) //在这
public class TransferServiceImpl implements TransferService {


    @Autowired
    @Qualifier("accountDao")
    private AccountDao accountDao;
 
    @Override
    @Transactional //或者在这
    public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {

            Account from = accountDao.queryAccountByCardNo(fromCardNo);
            Account to = accountDao.queryAccountByCardNo(toCardNo);

            from.setMoney(from.getMoney()-money);
            to.setMoney(to.getMoney()+money);
            accountDao.updateAccountByCardNo(to);
            int c= 1/0;
            accountDao.updateAccountByCardNo(from)

    }
}

3. 纯注解模式下实现事务

只需要把注解驱动的注解形式@EnableTransactionManagement加在Spring 加在Spring的配置类上就可以了

@EnableTransactionManagement//开启spring注解事务的⽀持
public class SpringConfiguration {
}

spring的xml文件头介绍

1. xml文件头

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

xml文件头具体介绍

  1. xmlns 其中ns是namespace是缩写。相同xml的约束叫做namespace。指的是具体的声明属性约束
  2. xmlns:xsi 是xml基础文件头
  3. xmlns:是xmlns:bean 因为bean是spring的基础功能故而省略了,里面定义了spring bean的一些规范.
  4. xmlns:aop 里面定义了aop一些规范,可以实现aop相关功能。
  5. xmlns:context 里面定义了context一些规范,可以实现引入资源扫描文件等。
  6. xmlns:tx 里面定义了tx一些规范,可以实现事务之类的功能 。
  7. xmlns:后面可以加一些属性比如aop或者tx,你也可以写成xmlns:aop1,相应的beans里面也应该写成aop1。
  8. xsi:schemaLocation 是声明的具体指向,类似于Java中的赋值。 声明和指向成对出现。

Spring 的源码解读(打扰了)

1. 怎么下载阅读源码

  1. 从gitHub上 搜索Spring-framework下载,可以在分枝上选择版本
  2. 安装gradle 5.6.3(类似于maven)
  3. 导⼊(耗费⼀定时间)
  4. 编译⼯程 ⼯程—>tasks—>compileTestJava(顺序:core-oxm-context-beans-aspects-aop)

2. 读源码有什么好处

  1. 提⾼培养代码架构思维
  2. 深⼊理解框架

3. 读源码有什么坏处

  1. 源码很难,读不懂平白浪费时间
  2. 循序渐进,多学会几个框架的使用技巧或许更加有用
  3. 新手不推荐,以后水平高了再来阅源码

4. 读源码的技巧

  1. 定焦原则:抓主线 站在上帝视⻆,关注源码结构和业务流程(淡化具体某⾏代码的编写细节)

  2. 断点(观察调⽤栈) 反调(Find Usages) 经验(spring框架中doXXX,做具体处理的地⽅)

  3. 去b站之类的学习网站,站在别人的肩膀上学习源码,看别人怎么读的,有什么技巧。

结语

感觉至少花费了一周的时间才完成这个笔记。这个笔记上在应颠老师的pdf笔记基础上加上自己动手敲的代码和一些自己的理解排版而成。还是感觉蛮累的。

原版的spring笔记

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值