首页 > 其他分享 >Spring体系化笔记(韩顺平课程)

Spring体系化笔记(韩顺平课程)

时间:2023-05-12 14:57:35浏览次数:37  
标签:String spring public 体系化 Spring out ioc class 顺平

Spring

Spring 核心学习内容 IOC、AOP、 JdbcTemplate、声明式事务

image-20230327175118115

1.Spring 几个重要概念

  1. Spring 可以整合其他的框架(Spring 是管理框架的框架)

  2. Spring 有两个核心的概念: IOC 和 AOP

  3. IOC Inversion Of Control 控制反转

  4. 动态代理(学好了才能学好AOP)

  5. AOP Aspect-oriented programming 面向切面编程

那么接下来我们就开始逐个击破他们吧

2.IOC / DI( 注入依赖)

我们传统的开发模式是:编写程序 ==> 在程序中读取配置文件信息,通过new或反射创建对象,用对象完成任务

IOC开发模式:在配置文件中配置好对象的属性和依赖 ==> 在程序中利用IOC直接根据配置文件,创建对象, 并放入到容器(ConcurrentHashMap)中, 并可以完成对象之间的依赖 ,当需要使用某个对象实例的时候, 就直接从容器中获取即可(那么容器到底是什么呢,我们待会再讲)

总结:由传统的new、反射创建对象 ==> IOC通过注解或配置直接创建对象

有什么好处呢?

  • 程序员可以更加关注如何使用对象完成相应的业务

  • IOC 也体现了 Spring 最大的价值,通过配置,给程序提供需要使用的web层对象(Servlet、Service、Dao、JavaBean、Entity),实现解耦。

3.Spring容器结构与机制

我们先快速入门一下使用IOC基本的代码

如上文步骤所说,先配置一个xml文件,当然配之前我们要先创建一个类,这样才能在配置xml的class中输入类的全路径

所以我们先简单的创建一个Monster类

package com.spring.bean;
​
public class Monster {
    private Integer id;
    private String name;
    private String skill;
​
    @Override
    public String toString() {
        return "Monster{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", skill='" + skill + '\'' +
                '}';
    }
​
    public Integer getId() {
        return id;
    }
​
    public void setId(Integer id) {
        this.id = id;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    public String getSkill() {
        return skill;
    }
​
    public void setSkill(String skill) {
        this.skill = skill;
    }
​
    public Monster(Integer id, String name, String skill) {
        this.id = id;
        this.name = name;
        this.skill = skill;
    }
​
    public Monster() {
    }
}
​

然后配置我们的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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
​
    <!--
        1.配置Monster对象/javabean
        2.在beans中可以配置多个bean
        3.bean就是一个java对象,只是符合几个条件而已
        4.class属性是类的全路径,记得类要有无参构造器,就可以让spring底层使用反射
        5.id 表示 该类在spring容器中的id,将来可通过id获取该对象(id一定要不同,后面会解释)
        6.property是用于给该对象属性赋值
    -->
    <bean class="com.spring.bean.Monster"  id="monster01">
        <property name="id" value="1"/>
        <property name="name" value="牛魔王"/>
        <property name="skill" value="芭蕉扇"/>
    </bean>
</beans>

然后我们再简单的写一个测试类展示一下基本代码

public class springBeanstest {
    @Test
    public void getMonster(){
        //1.创建容器 --> 和容器配置文件关联(debug处)
        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
        //2.通过getBean获取对象
        //Object monster01 = ioc.getBean("monster01");  //默认返回obj
        //也可以使用另一个getBean方法直接获取他的运行类型的对象,这样就无需强转获取monster的方法
        Monster monster01 = ioc.getBean("monster01", Monster.class);
        System.out.println(monster01.getSkill());
    }
}

那么简单的介绍了一下代码之后,我们正式开始分析ioc容器的底层机制,怎么办呢,毫无疑问的方法 ==> debug

断点打在第一行的创建容器上,让我们开始进入ioc的内部世界。

image-20230420150328709

点开我们的beanFactory,找到我们重点关注第3个:beanDefinitionMap,我们可以看到它的右边灰色括号内(就是它的类型)是一个ConcurrentHashMap,打开它我们看到有一个table,table的类型是ConcurrentHashMap的一个内部类Node,而且还是一个数组,每一个数组都存放着配置文件中的不同bean对象,它的初始化大小是512个,超过才会自动扩容

image-20230420150522823

我们往下翻,终于找到第217个数组存放着我们的Monster01对象

image-20230420151357665

我们继续往下翻,找到一个propertyValues

image-20230420151442784

其实也就是指xml里的property

<bean class="com.spring.bean.Monster"  id="monster01">
        <property name="id" value="1"/>
        <property name="name" value="牛魔王"/>
        <property name="skill" value="芭蕉扇"/>
    </bean>

好,hold on,hold on,hold on,所以说我现在考你一个问题,真真正正创建出来的monster01到底放在哪?你可能会说在beanDefinitionMap的table中,错,其实beanDefinitionMap的table存的只是beans.xml配置文件的对象,最终创建出来的monster01放在接下来的singletonObjects中

image-20230420152229708

image-20230420152317844

到此,我们终于找到了创建出来的monster01最终存放的地方,在beanFactory的singletonObjects的table里。我们可以看到它的类型雀雀实实是Monster了

那么我们来解释一下这行代码,getBean是怎么查到monster01的

Monster monster01 = ioc.getBean("monster01", Monster.class);

getBean是怎么查到monster01的

  • 首先,他会在beanDefinitionMap的table里找是否有monster01

  • 如果发现有一个单例的对象,就又用monster01这个id去singletonObjects的table里获取真正的monster01。

  • 如果发现不是单例的对象,他就通过反射机制动态创建一个单例的对象返回给ioc.getBean

说了这么多,肯定有小伙伴不知道什么叫单例,具体可以去网上了解,这里简要介绍一下,默认情况下,一个Spring容器的每一个bean对象都只能有一个实例对象,创建的再多也只有一个相同的对象,这就叫单例,这同时也是一个设计模式叫单例模式。

当然你要是写两遍这个代码,monster01 就不等于 monster02了,因为是两个spring容器

ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");//创建容器
Monster monster01 = ioc.getBean("monster01", Monster.class);            //获取对象
​
ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");//创建容器
Monster monster02 = ioc.getBean("monster01", Monster.class);            //获取对象

到这里,我们也验证了上面配置xml文件时为什么说每个bean的id要不同的原因了。(如果相同创建的一直是同一个对象)

最后再额外补充一个

image-20230420160541218

Spring容器结构总结:

image-20230420212327288

Bean的生命周期:

说明: bean 对象创建是由 JVM 完成的,然后执行如下方法

  1. 执行构造器

  2. 执行 set 相关方法

  3. 调用 bean 的初始化的方法(需要配置)

  4. 使用 bean

  5. 当容器关闭时候,调用 bean 的销毁方法(需要配置)

public class House {
    private String name;
    public House() {
        System.out.println("House() 构造器");
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        System.out.println("House setName()...");
        this.name = name;
    }
    public void init() {    //不一定要叫init,只要在xml里配置init-method这里的方法名就好
        System.out.println("House init()..");
    }
    public void destory() {  //同理,不一定要叫destory
        System.out.println("House destory()..");
    }
}
​
//输出顺序:
    House() 构造器  
    House setName()... 
    House init()..  
    业务操作  
    House destory()..
<!-- 配置 bean 的初始化方法和销毁方法 -->
<bean id="house" class="com.spring.beans.House"
init-method="init" destroy-method="destory">
<property name="name" value="北京豪宅"/>
</bean>

后置处理器:

后置处理器会在 bean 初始化init方法调用前初始化init方法调用后被调用,对所有的类都生效,这也是切面编程的核心之一(在不影响源代码情况下切进来,对多个对象进行操作),后面我们会细讲Spring是如何实现他的。

 

4.实现sping的底层的注解方式注入bean(重点!!!)

  1. 写一个简单的 Spring 容器 , 通过读取类的注解 (@Component @Controller @Service @Reponsitory),将对象注入到 IOC 容器

  2. 也就是说,不使用 Spring 原生框架,我们自己使用 IO+Annotaion+反射+集合 技术实现, 打通 Spring 注解方式开发的技术痛点

思路分析图:

那么开发一个程序首先做的就是画图分析思路啦

  • 我们用虚线分隔开官方实现和我们自己实现的两个部分,上面的是官方的实现,下面是我们自己的。

  • 我们将beans.xml分成两部分从而实现替代,一个是自定义注解(ComponentScan),一个是类似beans.xml的配置类(HspSpringConfig),配置类会写上我们的自定义注解,注解里的value就相当于xml文件中的base-package的作用,由此我们的配置类(HspSpringConfig)就达到了替代beans.xml的作用

  • 那么我们再来实现自己的容器类(HspSpringApplication)作为官方的ClassPathXmlApplicationContext的替代品,思路很简单,不过就是拿到到配置类的.class文件,然后获取自定义注解下的value值(也就是要扫描的包的全路径),通过反射包的全路径获取包下的各种class文件(编译后的java类),再进行一系列操作实现getBean的平替方法

image-20230423203909952

image-20230423205911453

实现:

思路分析完了,可能现在小伙伴就开始无从下手了,那么我们首先开始照着图片搭建一个框架,也就是在IDEA中创建好包和类先。其中Component就是写了一些自定义注解的类(也就是要扫描的包),test是测试用的

image-20230423232008008

于是乎,我们就可以开始辛苦的敲代码了

先从beans.xml的平替类下手,自定义注解和配置类

package com.spring.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
​
/**
 * @Author: Yjc
 * @Description: 自定义注解
 * @DateTime: 2023/4/23 20:23
 **/
@Target(ElementType.TYPE)          // @Target 注解用于定义注解的使用范围,即被描述的注解可以用在什                                    么地方。ElementType.TYPE 表示该注解可以用于类、接口、枚举和注解类型
@Retention(RetentionPolicy.RUNTIME)         //作用范围是运行时
public @interface ComponentScan {
    String value() default "";              //可以给一个叫value的字符串 , 也就是可以@ComponentScan(value = "xxx")这样使用
}
package com.spring.annotation;
​
/**
 * @Author: Yjc
 * @Description: 配置类
 * @DateTime: 2023/4/23 20:29
 **/
@ComponentScan(value = "com.spring.Component")
public class yuanSpringConfig {
}

再写几个简单的组件类用来测试,为了简洁我这就放一起了,实际上拆开放在各自的类里

@ComponentScan
public class MyComponent {
}
​
public class NoComponent {      //没有注解的类,后面要写逻辑来排除
}
​
@Repository
public class UserDao {
}
​
@Service
public class UserService {
}
​
@Controller
@Component(value = "id1")       //相当于给UserServlet命名为id1,最后测试getBean是否能够获取它
public class UserServlet {
}

然后就是我们最重要的容器类了,但是我们这里先实现一半,防止朋友们过于困惑,我们分段实现

package com.spring.annotation;

import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**

  • @Author: Yjc

  • @Description: 容器类

  • @DateTime: 2023/4/23 21:18
    **/
    public class yuanSpringApplicationContext {
    private Class configClass;
    private final ConcurrentHashMap<String, Objects> ioc = new ConcurrentHashMap<>(); //我们之前分析过了,其实ApplicationContext底层就是一个ConcurrentHashMap

    public yuanSpringApplicationContext(Class configClass) {
    //获取传入的配置类yuanSpringConfig.class
    this.configClass = configClass;
    //获取传入的配置类的注解
    ComponentScan ComponentScan = (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
    String path = ComponentScan.value();

     //扫描获取的value(包)下的所有文件
     //1.获取类加载器
     ClassLoader classLoader = yuanSpringApplicationContext.class.getClassLoader();
     //2.通过类加载器获取包下文件的url
     path = path.replace(".","/");       //将.全部改成 /  这样才是路径
     URL resource = classLoader.getResource(path);     //注意:通过阅读源码,这个空里的路径应该是以/为分割,所以有了上一句话
    
     File file = new File(resource.getFile());         //IO中目录也可以当成一个文件
     if (file.isDirectory()){
         File[] files = file.listFiles();              //获取包下的所有文件
         for (File f : files) {
             System.out.println("===========");       //检查一下有没有拿到
             File absoluteFilePath = f.getAbsoluteFile();
             System.out.println(absoluteFilePath);
         }
     }
    

    }
    }

写完这些,我们写个测试用例试一下,是否拿到了要扫描的包下的资源

package com.spring.test;
import com.spring.annotation.yuanSpringApplicationContext;
import com.spring.annotation.yuanSpringConfig;
import org.junit.Test;

/**

  • @Author: Yjc
  • @Description: 测试用例
  • @DateTime: 2023/4/23 21:26
    **/
    public class SpringTest {
    @Test
    public void testspring(){
    //调用容器类的构造器,放入配置类的class类。
    yuanSpringApplicationContext ioc = new yuanSpringApplicationContext(yuanSpringConfig.class);
    }
    }

很显然,我们成功了

image-20230423215246778

那么我们接下来继续实现,想办法拿到类的全路径从而通过反射去除没有web组件注解的类

package com.spring.annotation;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import java.io.File;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
​
/**
 * @Author: Yjc
 * @Description: 容器类
 * @DateTime: 2023/4/23 21:18
 **/
public class yuanSpringApplicationContext {
    private Class configClass;
    private final ConcurrentHashMap<String, Object> ioc = new ConcurrentHashMap<>();      //我们之前分析过了,其实ApplicationContext底层就是一个ConcurrentHashMap
​
    public yuanSpringApplicationContext(Class configClass) {
        //获取传入的配置类yuanSpringConfig.class
        this.configClass = configClass;
        //获取传入的配置类的注解
        ComponentScan ComponentScan = (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = ComponentScan.value();
​
        //扫描获取的value(包)下的所有文件
        //1.获取类加载器
        ClassLoader classLoader = yuanSpringApplicationContext.class.getClassLoader();
        //2.通过类加载器获取包下文件的url
        path = path.replace(".", "/");       //将.全部改成 / ,这样才是路径
        URL resource = classLoader.getResource(path);     //注意:通过阅读源码,这个空里的路径应该是以/为分割
​
        File file = new File(resource.getFile());         //IO中目录也可以当成一个文件
        if (file.isDirectory()) {
            File[] files = file.listFiles();              //获取包下的所有文件
            for (File f : files) {
                System.out.println("===========");       //检查一下有没有拿到
                String absoluteFilePath = String.valueOf(f.getAbsoluteFile());
                System.out.println(absoluteFilePath);
​
                //E:\JavaProject\Spring\out\production\Spring\com\spring\Component\MyComponent.class
                //获取com.spring.Component.MyComponent
​
                //1.获取类名MyComponent,也就是想办法去掉.class和前面的
                String className = absoluteFilePath.substring(absoluteFilePath.lastIndexOf("\\") + 1,
                        absoluteFilePath.lastIndexOf(".class"));
                //2.想办法拿到com.spring.Component.   其实只要把获取到的注解拿过来就好了(也就是path)
                String classFullName = path.replace("/", ".") + "." + className;
                System.out.println(classFullName);
                //3.把没有加自定义注解Component和相关的web组件注解的类去掉
                try {
                    //通过类加载器反射获取该类的class对象
                    //那么有人可能会问,为什么不用forName方法反射呢,因为forName还会调用static静态方法
                    //而classLoader只是获取class对象的信息,相当于一个轻量级的反射
                    //Class<?> classForName = Class.forName(classFullName);
​
                    Class<?> aClass = classLoader.loadClass(classFullName);
                    if (aClass.isAnnotationPresent(ComponentScan.class)
                            || aClass.isAnnotationPresent(Controller.class)
                            || aClass.isAnnotationPresent(Service.class)
                            || aClass.isAnnotationPresent(Repository.class)) {
                        //这个时候我们就需要完整的反射,如果该类有静态方法就可以执行了
                        Class<?> classForName = Class.forName(classFullName);
                        Object newInstance = classForName.newInstance();
                        //将其放入容器中
                        ioc.put(className, newInstance);
                    }else {
                        System.out.println("有一个不是组件的类");
                    }
                } catch (Exception e) {
                    System.out.println("出错了");
                }
            }
        }
    }
}

经过测试,冇问题。

image-20230423232558375

至此,我们已经可以将对象放入ioc容器中了,所以我们再写个简单的getBean方法作为获取实例对象的getter吧

public Object getBean(String name){
        return ioc.get(name);
    }

可以再写个简单的测试类试用一下

package com.spring.test;
import com.spring.annotation.yuanSpringApplicationContext;
import com.spring.annotation.yuanSpringConfig;
import org.junit.Test;
​
/**
 * @Author: Yjc
 * @Description: 测试用例
 * @DateTime: 2023/4/23 21:26
 **/
public class SpringTest {
    @Test
    public void testspring()  {
        yuanSpringApplicationContext ioc = new yuanSpringApplicationContext(yuanSpringConfig.class);
        System.out.println(ioc.getBean("id1"));
    }
}
​

image-20230424132004495

那么到此为止,一个简单的Spring注解的过程就实现完毕了。以后见到注解就知道它是怎么来的,有脚踏实地的感觉了

标签:String,spring,public,体系化,Spring,out,ioc,class,顺平
From: https://www.cnblogs.com/hanlinyuan/p/17394096.html

相关文章

  • [SpringCloud]Spring-Cloud-Gateway之启动过程(源码分析)
    1前言1.1环境信息Spring-Cloud-Gateway:2.2.9.RELEASEorg.springframework.boot:spring-boot:2.3.12.RELEASEio.projectreactor.netty:reactor-netty:0.9.20.RELEASEio.netty:netty-transport:4.1.65.FINAL2启动过程#与Netty的调用链路2.1简版(V1.0)cn.seres.b......
  • solon架构(spring-boot未来最有效的竞争力)
    一、现在基本WEB的开发都是用spring-boot来做开发了,但是springboot存在很多弊端。体量大,启动慢等。优点就是生态比较完整,文档说明也比较多。二、solon架构,是我学习其他框架兼容时了解的,说说其区别之处。1)solon目前已经有一定规模了,有自己的生态圈了吧2)sol......
  • spring出现依赖关系形成循环问题,The dependencies of some of the beans in the appli
    出现这个问题大多使用的springboot都是在2.6.x以上,springboot在2.6.x就将依赖循环禁用了,解决方式有以下几种:解决方式:1、第一种解决方式:可以优化自己程序的逻辑,优化bean的依赖关系,只要不形成一个环状就不会出该问题了 2、第二种解决方式:可以使用@Lazy注解(懒加载)和@Autowired注......
  • 【Spring 事务】【一】 Spring 事务简介
    1 前言本节我们开始来看看Spring事务哈,大家看之前首先要看过IOC、AOP、甚至代理哈,如果这些你不知道原理,你看任何东西都会很费劲,比如Bean的生命周期、AOP的切入时机、什么时候创建代理以及执行时机,这些不知道的话,你就看事务的话,会很懵,当然前提是大家是带着思考看的哈,单纯看不......
  • springboot 大文件切片上传
    1.前端(vueelementui&原生)初始变量声明: currentFile:{},//当前上传的文件bigFileSliceCount:20,//大文件切片后的子文件数量(也可使用其它限定方式,如按照文件大小,每10MB切一片,此处采用的是固定切片的子文件数量的方式倒推切片大小) 接口:切片上传图片&合并......
  • java基于springboot+html的学生就业管理系统的设计与实现,附源码+数据库+文档,包安装调
    1、项目介绍本系统是利用现代化的计算机网络技术将传统信息宣传方式整合,按照实践过程设计完成的。同时完善服务,初步设计一个学生就业管理系统平台以利于相关的事务操作。为了使系统在各项管理中发挥更大的作用,实现计算机信息化高效的管理,现将开发目标功能需求介绍如下:(1)管理员模......
  • 存下吧!Spring高频面试题总结
    Spring是什么?Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。Spring的优点通过控制反转和依赖注入实现松耦合。支持面向切面的编程,并且把应用业务逻辑和系统服务分开。通过切面和模板减少样板式代码。声明式事务的支持。可以从单调繁冗的事务管理代码中解脱......
  • SpringBoot中单元测试如何对包含AopContext.currentProxy()的方法进行测试
    今天在工作中遇到一个问题,一个Service类中有一个方法,其中使用了AopContext.currentProxy()去访问自身的函数,例如intresult=((OrderServiceImpl)AopContext.currentProxy()).save();单元测试方法如下:@InjectMocksprivateOrderServiceImplorderServiceUnderTest;@Tes......
  • SpringBoot3.x中spring.factories SPI 服务发现机制的改变
    目录一、基础背景二、服务发现接口spring.factories三、服务发现机制调用1.启动SpringApplication2.加载SpringApplication.run1.SpringApplication.createApplicationContext2.SpringApplication.prepareContext3.SpringApplication.refreshContext4.AutoConfigurationImportSele......
  • Spring项目的相关准备
    导入jar包只需要导入这个核心jar包其余包也就能够正常使用啦!简单练习再spring官网找到xml配置文件的框架,复制到我们自己的项目里面网址:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-introduction写入bean配置然后在test的java......