ToB企服应用市场:ToB评测及商务社交产业平台

标题: Spring体系化笔记(韩顺平课程) [打印本页]

作者: 种地    时间: 2023-5-12 14:31
标题: Spring体系化笔记(韩顺平课程)
Spring

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

1.Spring 几个重要概念

那么接下来我们就开始逐个击破他们吧
2.IOC / DI( 注入依赖)

我们传统的开发模式是:编写程序 ==> 在程序中读取配置文件信息,通过new或反射创建对象,用对象完成任务
IOC开发模式:在配置文件中配置好对象的属性和依赖 ==> 在程序中利用IOC直接根据配置文件,创建对象, 并放入到容器(ConcurrentHashMap)中, 并可以完成对象之间的依赖 ,当需要使用某个对象实例的时候, 就直接从容器中获取即可(那么容器到底是什么呢,我们待会再讲)
总结:由传统的new、反射创建对象 ==> IOC通过注解或配置直接创建对象
有什么好处呢?

3.Spring容器结构与机制

我们先快速入门一下使用IOC基本的代码
如上文步骤所说,先配置一个xml文件,当然配之前我们要先创建一个类,这样才能在配置xml的class中输入类的全路径
所以我们先简单的创建一个Monster类
  1. package com.spring.bean;
  2. public class Monster {
  3.    private Integer id;
  4.    private String name;
  5.    private String skill;
  6.    @Override
  7.    public String toString() {
  8.        return "Monster{" +
  9.                "id=" + id +
  10.                ", name='" + name + '\'' +
  11.                ", skill='" + skill + '\'' +
  12.                '}';
  13.    }
  14.    public Integer getId() {
  15.        return id;
  16.    }
  17.    public void setId(Integer id) {
  18.        this.id = id;
  19.    }
  20.    public String getName() {
  21.        return name;
  22.    }
  23.    public void setName(String name) {
  24.        this.name = name;
  25.    }
  26.    public String getSkill() {
  27.        return skill;
  28.    }
  29.    public void setSkill(String skill) {
  30.        this.skill = skill;
  31.    }
  32.    public Monster(Integer id, String name, String skill) {
  33.        this.id = id;
  34.        this.name = name;
  35.        this.skill = skill;
  36.    }
  37.    public Monster() {
  38.    }
  39. }
复制代码
然后配置我们的xml文件
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3.       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4.       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
  5.    
  6.    <bean   id="monster01">
  7.        <property name="id" value="1"/>
  8.        <property name="name" value="牛魔王"/>
  9.        <property name="skill" value="芭蕉扇"/>
  10.    </bean>
  11. </beans>
复制代码
然后我们再简单的写一个测试类展示一下基本代码
  1. public class springBeanstest {
  2.    @Test
  3.    public void getMonster(){
  4.        //1.创建容器 --> 和容器配置文件关联(debug处)
  5.        ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");
  6.        //2.通过getBean获取对象
  7.        //Object monster01 = ioc.getBean("monster01");  //默认返回obj
  8.        //也可以使用另一个getBean方法直接获取他的运行类型的对象,这样就无需强转获取monster的方法
  9.        Monster monster01 = ioc.getBean("monster01", Monster.class);
  10.        System.out.println(monster01.getSkill());
  11.    }
  12. }
复制代码
那么简单的介绍了一下代码之后,我们正式开始分析ioc容器的底层机制,怎么办呢,毫无疑问的方法 ==> debug
断点打在第一行的创建容器上,让我们开始进入ioc的内部世界。

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

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

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

其实也就是指xml里的property
  1. <bean   id="monster01">
  2.        <property name="id" value="1"/>
  3.        <property name="name" value="牛魔王"/>
  4.        <property name="skill" value="芭蕉扇"/>
  5.    </bean>
复制代码
好,hold on,hold on,hold on,所以说我现在考你一个问题,真真正正创建出来的monster01到底放在哪?你可能会说在beanDefinitionMap的table中,错,其实beanDefinitionMap的table存的只是beans.xml配置文件的对象,最终创建出来的monster01放在接下来的singletonObjects中


到此,我们终于找到了创建出来的monster01最终存放的地方,在beanFactory的singletonObjects的table里。我们可以看到它的类型雀雀实实是Monster了
那么我们来解释一下这行代码,getBean是怎么查到monster01的
  1. Monster monster01 = ioc.getBean("monster01", Monster.class);
复制代码
getBean是怎么查到monster01的

说了这么多,肯定有小伙伴不知道什么叫单例,具体可以去网上了解,这里简要介绍一下,默认情况下,一个Spring容器的每一个bean对象都只能有一个实例对象,创建的再多也只有一个相同的对象,这就叫单例,这同时也是一个设计模式叫单例模式。
当然你要是写两遍这个代码,monster01 就不等于 monster02了,因为是两个spring容器
  1. ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");//创建容器
  2. Monster monster01 = ioc.getBean("monster01", Monster.class);            //获取对象
  3. ApplicationContext ioc = new ClassPathXmlApplicationContext("beans.xml");//创建容器
  4. Monster monster02 = ioc.getBean("monster01", Monster.class);            //获取对象
复制代码
到这里,我们也验证了上面配置xml文件时为什么说每个bean的id要不同的原因了。(如果相同创建的一直是同一个对象)
最后再额外补充一个

Spring容器结构总结:


Bean的生命周期:

说明: bean 对象创建是由 JVM 完成的,然后执行如下方法
  1. public class House {
  2.     private String name;
  3.     public House() {
  4.         System.out.println("House() 构造器");
  5.     }
  6.     public String getName() {
  7.         return name;
  8.     }
  9.     public void setName(String name) {
  10.         System.out.println("House setName()...");
  11.         this.name = name;
  12.     }
  13.     public void init() {    //不一定要叫init,只要在xml里配置init-method这里的方法名就好
  14.         System.out.println("House init()..");
  15.     }
  16.     public void destory() {  //同理,不一定要叫destory
  17.         System.out.println("House destory()..");
  18.     }
  19. }
  20. //输出顺序:
  21.    House() 构造器  
  22.    House setName()...
  23.    House init()..  
  24.    业务操作  
  25.    House destory()..
复制代码
  1. [/code][size=4]后置处理器:[/size]
  2. 后置处理器会在 bean [b]初始化init方法调用前[/b]和[b]初始化init方法调用后[/b]被调用,对所有的类都生效,这也是切面编程的核心之一(在不影响源代码情况下切进来,对多个对象进行操作),后面我们会细讲Spring是如何实现他的。
  3.  
  4. [size=5]4.实现sping的底层的注解方式注入bean(重点!!!)[/size]
  5. [list=1]
  6. [*]写一个简单的 Spring 容器 , 通过读取类的注解 (@Component @Controller @Service @Reponsitory),将对象注入到 IOC 容器
  7. [*]也就是说,不使用 Spring 原生框架,我们自己使用 IO+Annotaion+反射+集合 技术实现, 打通 Spring 注解方式开发的技术痛点
  8. [/list][size=4]思路分析图:[/size]
  9. 那么开发一个程序首先做的就是画图分析思路啦
  10. [list]
  11. [*]我们用虚线分隔开官方实现和我们自己实现的两个部分,上面的是官方的实现,下面是我们自己的。
  12. [*]我们将beans.xml分成两部分从而实现替代,一个是自定义注解(ComponentScan),一个是类似beans.xml的配置类(HspSpringConfig),配置类会写上我们的自定义注解,注解里的value就相当于xml文件中的base-package的作用,由此我们的配置类(HspSpringConfig)就达到了替代beans.xml的作用
  13. [*]那么我们再来实现自己的容器类(HspSpringApplication)作为官方的ClassPathXmlApplicationContext的替代品,思路很简单,不过就是拿到到配置类的.class文件,然后获取自定义注解下的value值(也就是要扫描的包的全路径),通过反射包的全路径获取包下的各种class文件(编译后的java类),再进行一系列操作实现getBean的平替方法
  14. [/list][img]https://s2.loli.net/2023/04/23/1Q8kKtr7UNVSJwz.png[/img]
  15. [img]https://s2.loli.net/2023/04/23/xzMwkSBlhVRQCXr.png[/img]
  16. [size=4]实现:[/size]
  17. 思路分析完了,可能现在小伙伴就开始无从下手了,那么我们首先开始照着图片搭建一个框架,也就是在IDEA中创建好包和类先。其中Component就是写了一些自定义注解的类(也就是要扫描的包),test是测试用的
  18. [img]https://s2.loli.net/2023/04/23/r14oUnTxYHhptSf.png[/img]
  19. 于是乎,我们就可以开始辛苦的敲代码了
  20. 先从beans.xml的平替类下手,自定义注解和配置类
  21. [code]package com.spring.annotation;
  22. import java.lang.annotation.ElementType;
  23. import java.lang.annotation.Retention;
  24. import java.lang.annotation.RetentionPolicy;
  25. import java.lang.annotation.Target;
  26. /**
  27. * @Author: Yjc
  28. * @Description: 自定义注解
  29. * @DateTime: 2023/4/23 20:23
  30. **/
  31. @Target(ElementType.TYPE)          // @Target 注解用于定义注解的使用范围,即被描述的注解可以用在什                                    么地方。ElementType.TYPE 表示该注解可以用于类、接口、枚举和注解类型
  32. @Retention(RetentionPolicy.RUNTIME)         //作用范围是运行时
  33. public @interface ComponentScan {
  34.    String value() default "";              //可以给一个叫value的字符串 , 也就是可以@ComponentScan(value = "xxx")这样使用
  35. }
复制代码
  1. package com.spring.annotation;
  2. /**
  3. * @Author: Yjc
  4. * @Description: 配置类
  5. * @DateTime: 2023/4/23 20:29
  6. **/
  7. @ComponentScan(value = "com.spring.Component")
  8. public class yuanSpringConfig {
  9. }
复制代码
再写几个简单的组件类用来测试,为了简洁我这就放一起了,实际上拆开放在各自的类里
  1. @ComponentScan
  2. public class MyComponent {
  3. }
  4. public class NoComponent {      //没有注解的类,后面要写逻辑来排除
  5. }
  6. @Repository
  7. public class UserDao {
  8. }
  9. @Service
  10. public class UserService {
  11. }
  12. @Controller
  13. @Component(value = "id1")       //相当于给UserServlet命名为id1,最后测试getBean是否能够获取它
  14. public class UserServlet {
  15. }
复制代码
然后就是我们最重要的容器类了,但是我们这里先实现一半,防止朋友们过于困惑,我们分段实现
  1. package com.spring.annotation;
  2. <p>import java.io.File;<br>
  3. import java.lang.annotation.Annotation;<br>
  4. import java.net.URL;<br>
  5. import java.util.Objects;<br>
  6. import java.util.concurrent.ConcurrentHashMap;</p>
  7. <p>/**</p>
  8. <ul>
  9. <li>
  10. <p>@Author: Yjc</p>
  11. </li>
  12. <li>
  13. <p>@Description: 容器类</p>
  14. </li>
  15. <li>
  16. <p>@DateTime: 2023/4/23 21:18<br>
  17. **/<br>
  18. public class yuanSpringApplicationContext {<br>
  19. private Class configClass;<br>
  20. private final ConcurrentHashMap<String, Objects> ioc = new ConcurrentHashMap<>();      //我们之前分析过了,其实ApplicationContext底层就是一个ConcurrentHashMap</p>
  21. <p>public yuanSpringApplicationContext(Class configClass) {<br>
  22. <a href="//xn--yuanSpringConfig-ql3z221ai5lh18ohg0attn0hxbr8e.class" target="_blank" rel="noopener">//获取传入的配置类yuanSpringConfig.class</a><br>
  23. this.configClass = configClass;<br>
  24. //获取传入的配置类的注解<br>
  25. ComponentScan ComponentScan = (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);<br>
  26. String path = ComponentScan.value();</p>
  27. [code] //扫描获取的value(包)下的所有文件
  28. //1.获取类加载器
  29. ClassLoader classLoader = yuanSpringApplicationContext.class.getClassLoader();
  30. //2.通过类加载器获取包下文件的url
  31. path = path.replace(".","/");       //将.全部改成 /  这样才是路径
  32. URL resource = classLoader.getResource(path);     //注意:通过阅读源码,这个空里的路径应该是以/为分割,所以有了上一句话
  33. File file = new File(resource.getFile());         //IO中目录也可以当成一个文件
  34. if (file.isDirectory()){
  35.      File[] files = file.listFiles();              //获取包下的所有文件
  36.      for (File f : files) {
  37.          System.out.println("===========");       //检查一下有没有拿到
  38.          File absoluteFilePath = f.getAbsoluteFile();
  39.          System.out.println(absoluteFilePath);
  40.      }
  41. }
复制代码
}
}
[/code]
写完这些,我们写个测试用例试一下,是否拿到了要扫描的包下的资源
  1. package com.spring.test;
  2. import com.spring.annotation.yuanSpringApplicationContext;
  3. import com.spring.annotation.yuanSpringConfig;
  4. import org.junit.Test;
  5. <p>/**</p>
  6. <ul>
  7. <li>@Author: Yjc</li>
  8. <li>@Description: 测试用例</li>
  9. <li>@DateTime: 2023/4/23 21:26<br>
  10. **/<br>
  11. public class SpringTest {<br>
  12. @Test<br>
  13. public void testspring(){<br>
  14. //调用容器类的构造器,放入配置类的class类。<br>
  15. yuanSpringApplicationContext ioc = new yuanSpringApplicationContext(yuanSpringConfig.class);<br>
  16. }<br>
  17. }</li></ul>
复制代码
很显然,我们成功了

那么我们接下来继续实现,想办法拿到类的全路径从而通过反射去除没有web组件注解的类
  1. 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 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)<bean   id="monster01">
  2.        <property name="id" value="1"/>
  3.        <property name="name" value="牛魔王"/>
  4.        <property name="skill" value="芭蕉扇"/>
  5.    </bean>|| aClass.isAnnotationPresent(Controller.class)<bean   id="monster01">
  6.        <property name="id" value="1"/>
  7.        <property name="name" value="牛魔王"/>
  8.        <property name="skill" value="芭蕉扇"/>
  9.    </bean>|| aClass.isAnnotationPresent(Service.class)<bean   id="monster01">
  10.        <property name="id" value="1"/>
  11.        <property name="name" value="牛魔王"/>
  12.        <property name="skill" value="芭蕉扇"/>
  13.    </bean>|| 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("出错了");                }            }        }    }}
复制代码
经过测试,冇问题。

至此,我们已经可以将对象放入ioc容器中了,所以我们再写个简单的getBean方法作为获取实例对象的getter吧
  1. public Object getBean(String name){
  2.        return ioc.get(name);
  3.    }
复制代码
可以再写个简单的测试类试用一下
  1. package com.spring.test;
  2. import com.spring.annotation.yuanSpringApplicationContext;
  3. import com.spring.annotation.yuanSpringConfig;
  4. import org.junit.Test;
  5. /**
  6. * @Author: Yjc
  7. * @Description: 测试用例
  8. * @DateTime: 2023/4/23 21:26
  9. **/
  10. public class SpringTest {
  11.    @Test
  12.    public void testspring()  {
  13.        yuanSpringApplicationContext ioc = new yuanSpringApplicationContext(yuanSpringConfig.class);
  14.        System.out.println(ioc.getBean("id1"));
  15.    }
  16. }
复制代码


那么到此为止,一个简单的Spring注解的过程就实现完毕了。以后见到注解就知道它是怎么来的,有脚踏实地的感觉了
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4