• spring学习笔记(一)


    前言

    最近,在智联、Boss和拉勾上投了一些简历,深感自己还需提升能力。不少企业在招聘中写明精通springboot、springcloud(虽然我也知道这是套话啦,什么程度能叫精通?起码得通读源码加项目实操经验吧),我也不指望能短时间“精通”这些技术。虽然springboot一直在用,但是实际上会的不过是一些自己常用的操作,事务管理啦、ioc啦、一些框架整合啦... ...,早先就听说了springboot这款产品整合了spring家族所有框架,简化了配置,提供了starter,总之是真的强。而springcloud在springboot的基础上又整合了很多优秀的开源组件,注册中心、熔断降级、负载均衡等等,所以我觉得想成为一个熟手,起码要先把spring学好。所以,我决定抛弃曾经浮躁的心态,从spring开始复习。由于英语不好,官方文档实在搞不定,找了一份中文文档,虽然读起来稍微有点那个,但是好歹之前学过用过,应该基本上读的下来,在此感谢汉化官方文档的大佬!

    环境搞起

    考虑了一下,如果采用传统SSM来搭建环境的话,有点麻烦,我知道的整合办法就是至少写三个xml,既然springboot已经整合了spring,那就直接springboot+tk.mybatis好了,反正这次学习的主角是spring,只要它在就好。

    环境什么的,就按照自己的习惯来吧,先上yaml文件。

    server:
      port: 9090
    # 数据源
    spring:
      application:
        name: spring
      datasource:
        url: jdbc:mysql://localhost:3306/spring?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8&useSSL=true
        username: root
        password:
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
        #初始化链接数量
        initialSize: 5
        #最小空闲链接数
        minIdle: 5
        #最大并发连接数
        maxActive: 10
        #获取链接等待超时的时间
        maxWait: 60000
        #配置间隔多久才进行一次检测,检测需要关闭的空闲链接,单位是毫秒
        timeBetweenEvictionRunsMillis: 60000
        #内部jackson
        jackson:
          #json格式
          date-format: yyyy-MM-dd HH:mm:ss
          #时区
          time-zone: GMT+8
          logging:
            level:
              com.simple.blog.modules.*.mapper: debug
    mybatis:
      #type-aliases-package: 
      mapper-locations: classpath:mapper/*Mapper.xml

    额外加入了druid连接池,sql采用xml集中管理,为了以防新入坑的小伙伴不知道,额外提一句,8.x以上版本的mysql(好像是这个版本以上),与之前版本的mysql配置数据源时有所不同,这一点需要额外注意一下。

    接下来上pom文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <parent>
            <artifactId>TheFuture</artifactId>
            <groupId>org.example</groupId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <modelVersion>4.0.0</modelVersion>
    
        <artifactId>Spring</artifactId>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
    
            <dependency>
                <groupId>tk.mybatis</groupId>
                <artifactId>mapper-spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>

    由于已经建好了父项目,所以子项目的依赖可以不写版本号,直接继承父项目的版本号,如果子项目自己写了版本号,则使用自己的版本号。

    devtools是用来做热部署的,也就是说我们修改了项目之后,不需要手动重启项目,它会帮助我们自动重启。热部署的话只引入依赖还不行,按照网上的教程还有以下两步操作:

     按下shift + ctrl + A 搜索图中文字

     点击Registry...

     到这里就可以了,经实践发现网上有些教程给出的依赖不好用哦,所以如果你在网上没有找到合适的热部署依赖,建议试一下本文的依赖。


    注意,后续的内容包括本系列的其他文章都可能掺杂了我自己对于一些概念的理解(主要是我不喜欢死背概念),以及从入坑开始东拼西凑来的一些见解,可能不准确哦,毕竟这是一系列新手学习笔记,不是教程,请酌情参考。

    在spring中有几个基本概念得了解一下,什么是bean,什么是ioc,什么是BeanFactory以及ApplicationContext?

    bean也是对象,只不过它不是由程序员通过new关键字创建出来的对象,而是由spring创建并管理的对象,那么spring怎么知道程序员需要什么样的对象呢?当然也是程序员告诉它的,据我所知可以通过xml配置的方式(我很久不用了),也可以通过注解的方式(贼好用,大力推荐)。

    程序员——new——对象      这一过程是传统创建对象的流程。

    ioc容器——读取配置文件——创建对象      这一过程就是控制反转,程序员将创建对象的权限交给ioc容器,由ioc容器来创建和管理对象,当需要使用对象时,从容器中取出。

    那么我们为什么要让ioc容器来做这件事情而不是自己手动创建呢?就我了解到的来说,比如是每次http请求都使用一个单例对象还是每次都用不同的对象或者是其他情况,这一点如果自己来管理的话,需要自己写代码。但是交给ioc容器的话,直接配置scope属性搞定。再比如controller、service这类无状态的对象,是把他们统一放在自己的工厂类中进行管理呢?还是一个注解直接让spring帮忙管理,再一个注解让spring帮忙依赖注入(将对象放在它应该在的位置)更好呢?

    显然是交给它比较舒服。

    BeanFactory和ApplicationContext都是容器,后者属于前者的增强版,对于这两个东西我也了解不多哈,当某些情况下,比如想将对象注入给静态声明的时候,需要将容器取出手动getBean赋值给静态变量。

    xml配置bean的方式就不提了,个人感觉注解用着更爽。

    先介绍几个注解:

    @Configuration:这个注解放在类名上,表示当前类是一个配置类,你也可以把这个类当成配置文件来看。
    @Bean:这个注解放在方法名上,表示当前方法用于创建bean。
    @DependsOn:这个注解就有意思了,可能许多初学的小伙伴们没见过它,这东西可以用于bean创建顺序的控制。
    @Configuration
    public class BeanConfig {
        @Bean
        @DependsOn("firstInit")
        public SecondInit secondInit() {
            return new SecondInit();
        }
    
        @Bean
        public FirstInit firstInit() {
            return new FirstInit();
        }
    }
    
    class FirstInit {
        FirstInit() {
            System.out.println("first");
        }
    }
    
    class SecondInit {
        SecondInit() {
            System.out.println("second");
        }
    }

    上面是一个超简易配置类,可以看到它的作用就是创建两个,当创建的时候就会调用他们各自的构造器,打印出相应信息。

    在不加控制创建顺序的注解时,打印顺序为second、first,加上以后为first、second。在这个类中,@DependsOn的意思是,SecondInit的实例化依赖于FirstInit的实例化,至于它后面的("firstInit")实际上是bean的名字,当没有指定Bean的名字时,它是有默认名字的,我记得是方法名首字母小写,差不多,大概也许是这么回事,具体的各位可以自行探索一下。

    立即实例化和延迟实例化

    通常情况下,容器会在启动后实例化程序员配置的对象信息(通过xml或注解配置),如果不希望在启动时立即创建对象,而是在用到该对象时,再进行创建可以使用延迟实例化。

    xml配置的话,需要在标签内加上<bean id="xxx" class="xxx.xx.x" lazy-init="true"/>或者<beans default-lazy-init="true"></beans>

    如果是注解配置的话,@Lazy即可。

    自动装配

    @Autowired  按照类型注入

    @Resource   按照名称注入

    @Qualifier     按照名称注入(与@Autowired配合使用)

    当名称冲突时,貌似会回退到按照类型注入,嗯,记不太清了。

    使用方法较为简单,用几张图片说明一下:

     这种组合就是两个注解配合使用。

     这里就是按照类型注入,单独使用。

    @Autowired这个注解如果点进去会看到,boolean required() default true;就是说被注入的对象必须存在,否则就报错。

    如果你不希望这样,可以让它改为false,也就是存在就注入,不存在就跳过,@Autowired(required = false)。

    还有,@Resource这个注解不是spring的注解,而是java自带的,但是spring支持它。

    关于bean的生命周期,上一张文档截图吧:

     前两种比较简单,单例bean和原型bean,单例bean也就是实例化一次放在ioc容器中,什么时候用什么时候取出来,但是用来用去都是那一个,spring中默认就是这种方式创建bean,一些诸如controller、service或者其他的可复用组件就可以采用这种方式创建。原型bean和单例bean正相反,每次用到它的时候都创建一个新的bean。

    关键点来了,如果将原型bean注入单例bean会怎么样?它还会保持它自己的特性吗?

    文档中给出了说明,我自己理解总结了一下,当ioc容器初始化bean的时候会创建原型bean,然后依照程序员配置的依赖关系将原型bean放入单例bean中,但是,这种操作只发生一次。也就是说,如果将原型bean注入单例bean,再多次访问原型bean也不会重复创建新的原型bean了。

    request bean是针对于每一个http请求创建一个bean,@RequestScope这个注解可以标识它的作用范围。

    session bean是与http请求的session生命周期有关,session就是记录在服务端的信息,比如可以用来记录一下用户的登录状态,下次再访问相同服务端的时候,通过cookie将sessionId带过来,就可以找到该用户的session了。如果是微服务的话,多个后端服务做了负载均衡,这种情况下,需要考虑一下共享session,我没怎么研究这个,简单尝试了一下基于JWT的token来应对这种情况,生成令牌,令牌中包含客户的可公开信息,由于采用base64进行编码,如果存了一些私密信息小心被人家盗取。然后每次访问都带着令牌,进行解析、认证、授权,有点扯远了,回到我们的bean作用范围。

    application bean作用范围是ServletContext,可这ServletContext又是何许人也?看到这里我是彻底蒙了,上网查了一下,唤醒了一点点久远的记忆,之前貌似用过呀,获取项目根目录。

    https://www.jianshu.com/p/31d27181d542

    这篇文章里写了很多servlet上下文的作用,有兴趣可以看一下,总之,明确一点,并不是一个servlet对应一个ServletContext,而是一个web应用对应一个ServletContext。

    然后webSocket级别的bean,没在中文文档中看到诶,什么情况?那不如大胆猜测一下吧,反正我了解到的webSocket就是可以让服务端主动向客户端推送消息。socket这东西是用来通信的,有比较古老的BIO,还有比它先进的NIO,之前为了了解一下这两个东西还特意写过demo,至于AIO哪天有时间再研究吧。既然要推送消息那就应该有连接吧?看这意思貌似是长连接,那会不会是在长连接断开之前,这个级别的bean一直有效?这一段纯属自己乱想,不知道是不是这么回事。

    然后文档中讲到了自定义bean作用范围,居然还有这种操作,我看了一遍,决定暂时先不学这一块,原因有如下两点,感觉以我目前的水平就算学也就是学个操作而已,可能三五天不用就记不太清楚了。不像@Controller之类的注解总用,不记也记住了。其次是目前不论是学习阶段自己练手的项目,还是就业后在企业做的项目都没有见过这等操作,猜测应该是比较稀有的需求才会用到这种操作,不如等到水平提升后或者用到时再学一波(此处疯狂安慰自己)。

    ApplicationContextAware

    这个接口就有意思了,当ApplicationContext容器创建实现了这个接口的类的实例对象时,该对象会获得ApplicationContext的引用。

    换句话来讲,用这个接口就可以拿到ioc容器,写了个简单的类,大概就是这么个意思吧。

    @Component
    public class SpringApplicationContext implements ApplicationContextAware {
        static ApplicationContext applicationContext = null;
    
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
    
        public static <T> T getBean(String name, Class<T> clazz) {
            return applicationContext.getBean(name, clazz);
        }
    }

    可以看到实现了这个接口后,要求重写setApplicationContext方法,这是真的见名知意。

    那么我们为什么要用这个东西呢?用注解注入对象他不香吗?

    为了解答这个疑惑,我首先验证了一下,之前从别人那里问到的理论知识,修改代码如图所示:

     可见属性(成员变量)前加了static,启动项目后一切安好,但是当使用到service时,报错了。

     空指针异常,显然是service值为null引起的,为什么还能正常启动?因为@Autowired使用了required = false,证明了静态变量的确是不能用这个注解注入对象的。

    这个时候就可以手动从ioc容器中获取对象了,还有其他Aware接口(文档中翻译为感知接口),下篇文章继续学习。

  • 相关阅读:
    ASP.NET——From验证:全部代码及讲解
    JavaScript 经典代码大全:有 目录 及 编号 的哦 !
    很好的一首英文歌曲:不论是旋律、还是歌词或者MV
    2007年10月份_很想念大家
    NND,8月没有来发贴,现在是9月了,要发一个
    买了一个新的域名和主机,呵呵,
    视频下载:HTML基础及应用
    简单的哲理,放在最上面,提醒自己
    学的东西忘记得差不多啦......
    欲找情人 要做哪些准备?
  • 原文地址:https://www.cnblogs.com/wxdmw/p/14098026.html
Copyright © 2020-2023  润新知