Spring-定义、功能、Ioc/DI、Junit、作用域(singleson、prototype)、惰性初始化

0.对前面学习的总结

  前面初步使用了Spring MVC 和 Spring Boot,大概总结如下:

  • Spring MVC:

    Controller 和 java与页面的交互,都是Spring MVC的功能

    M: Model模型,狭义上指实体类, 广义上指除了控制器之外的其它java类

    V: View视图,html页面, 即显示给用户看到的界面

    C: Controller控制器,Controller类,视图与java进行数据交互的

    Spring MVC框架简化了V和C之间的数据交互过程

  • Spring Boot:

    boot : 启动

    支持Spring Boot框架的项目内置了很多配置,这些配置都是大部分框架的常规配置,当我们使用这些框架时,就免去了那些繁琐的配置。除了这些配置之外,还包含了一些约定:例如static文件夹存放静态资源等。

1.什么是Spring?

  Spring本身的意思是春天、泉水,Spring是一个基础框架,Spring MVC、Spring Boot、Spring Data、Spring Security、Spring Validation、Spring Cloud都是Spring框架衍生出来的以及众多以Spring开头的框架的基础。它的出现是现在java能够长盛不衰的主要原因。因为它提供了一个java开发的生态环境,几乎市面上任何通用的常用需求,Spring都有解决方案。

2.Spring框架的功能

  • Ioc\DI (主要讲解内容):Ioc是一种思想,DI是基于Ioc的操作。

  • Aop (最后讲解内容)

2.1 什么是Ioc?

  • Ioc(Inversion of Control) 翻译为:控制反转,正常的控制称之为主动控制

  • 主动控制: 我们编写的程序主动控制对象组件的产生,再编写对象组件互相调用,以完成程序功能

  • 控制反转: 我们编写的程序需要的对象组件保存在外部容器中,需要时从容器中获取,之后调用方法实现功能

2.2 Spring 实现Ioc

(1)首先创建一个Maven项目,默认配置即可

  注意:Maven项目不同于普通项目,因为里面包含pom.xml文件,可以添加组件依赖,方便管理依赖,而普通项目需要单独导入jar包,当jar包较多时,不方便管理,使用不便。Spring Boot内置Maven!

Spring-定义、功能、Ioc/DI、Junit、作用域(singleson、prototype)、惰性初始化

(2)项目的pom.xml中添加依赖

 <?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">
     <modelVersion>4.0.0</modelVersion>
 ?
     <groupId>org.example</groupId>
     <artifactId>SpringDemo</artifactId>
     <version>1.0-SNAPSHOT</version>
     <dependencies>
         <!--Spring框架:添加Spring Context依赖-->
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-context</artifactId>
             <version>5.2.2.RELEASE</version>
         </dependency>
 ?
        <!--JUnit单元测试框架-->
         <dependency>
             <groupId>junit</groupId>
             <artifactId>junit</artifactId>
             <version>4.13</version>
         </dependency>
     </dependencies>
 ?
 </project>

注意:先添加依赖(代码正确),再刷新Maven,等待下载完毕即可!

maven下载资源失败的解决方案:

1.检查setting.xml文件的位置是否设置了阿里\华为的镜像

2.退出所有防火墙或电脑管家等占用电脑资源的程序

3.更换网络再试(手机热点,手机共享网络给电脑)

4.安装maven(可以从苍老师网站下载)

5.删除.m2文件夹下的repository文件夹,再导入依赖,刷新Maven

(3)详细步骤

在src/main/java路径下创建一个包hello,包下创建类Stu,类中编写代码如下:

 package hello;
 ?
 /**
  * 1.实体类Stu
  */
 public class Stu {
     private String name;
     private Integer age;
 ?
     public String getName() {
         return name;
    }
 ?
     public void setName(String name) {
         this.name = name;
    }
 ?
     public Integer getAge() {
         return age;
    }
 ?
     public void setAge(Integer age) {
         this.age = age;
    }
 ?
     @Override
     public String toString() {
         return "Stu{" +
                 "name=‘" + name + ‘\‘‘ +
                 ", age=" + age +
                 ‘}‘;
    }
 }

包下编写配置类Config,常见的将对象保存到Spring容器中的方法有两个

我们先介绍第一个:@Bean方式

 package hello;
 ?
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 ?
 /**
  * 2.Spring配置类
  * 配置Spring容器中内容
  */
 //标准情况下,Spring配置类要添加下面的注解:@Configuration
 @Configuration
 public class Config {
     //编写注解@Bean,注解下写方法,这个方法返回的对象,就会保存到Spring容器中
     @Bean
     public Stu stu(){//注意:调用id时注意写这个方法名
         //实际上每个保存到Spring容器中的对象都要有一个唯一的名字
         //这个名字就是这个方法的方法名
         //简单来说,这个杨过对象在Spring容器中的id就是stu
         Stu stu = new Stu();
         stu.setName("杨过");
         stu.setAge(26);
         return stu;
    }
 }
 ?

包下新建测试类:获得Spring容器中的对象

 package hello;
 ?
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 ?
 /**
  * 3.测试:获得Spring容器中的对象
  */
 public class Test {
     public static void main(String[] args) {
         //初始化Spring容器,参数为配置类的反射
         AnnotationConfigApplicationContext acac = new AnnotationConfigApplicationContext(Config.class);//快捷键ACAC,然后回车
 ?
         //从Spring容器中获取对象
         //getBean方法两个参数:
         //1.想获得的对象在Spring容器中的id(唯一的名字:@Bean时对应方法名)
         //2.获得对象类型的反射(不需要强转,直接返回这个类型的对象)
         Stu stu = acac.getBean("stu",Stu.class);
         System.out.println(stu);
         acac.close();//关闭资源
    }
 }
 ?

输出结果:Stu{name=‘杨过‘, age=26}

2.3 利用单元测试框架JUnit运行Spring

  我们之前使用过Junit来测试程序运行,Junit测试程序有很多好处,例如一个测试类可以编写多个可以直接运行的方法,来减少main方法和类的数量。

  在maven项目的pom.xml文件中添加junit的依赖,刷新Maven

 <!--JUnit单元测试框架-->
 <dependency>
     <groupId>junit</groupId>
     <artifactId>junit</artifactId>
     <version>4.13</version>
 </dependency>

  在我们测试Spring的方法中,发现实例化ACAC对象和ACAC对象的关闭是每次都要编写执行的,我们可以利用Junit的@Before和@After这两个注解减少冗余代码,简化编写。

  src下面有两个路径:main和test,它们的子文件中都包含java文件夹,其中main下面的java主要存储我们日常编写的程序,test下面的java主要是对上面编写程序的测试,虽然名字相同,但是具有不同的功能。在编写测试类时,尽量与上面的包同名,以便测试类中可以直接调用编写好的类,不需要导包。同时,注意测试类的名字不能为Test,因为测试时需要用到@Test注解,会发生冲突。

在test/java路径下,新建包hello,编写HelloTest的代码如下:

 package hello;
 ?
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 ?
 public class HelloTest {
     //声明ACAC为成员变量,方便每个方法使用(Java中叫做成员变量,有的语言中也叫全局变量)
     AnnotationConfigApplicationContext acac;
 ?
     //测试类中的@Before注解,注解下的方法会在@Test标记的方法运行之前自动运行
     @Before
     public void init(){
         //初始化Spring容器,参数为配置类的反射
         acac = new AnnotationConfigApplicationContext(Config.class);
    }
 ?
     //测试类中的@After注解,注解下的方法会在@Test标记的方法运行之后自动运行
     @After
     public void destroy(){
         //关闭资源
         acac.close();
    }
 ?
     //测试类中的方法上加@Test注解,可以直接运行该方法
     @Test
     public void test(){//直接运行该test方法
         //从Spring容器中获取对象
         Stu stu = acac.getBean("stu",Stu.class);
         System.out.println(stu);
    }
 }
 ?

输出结果:Stu{name=‘杨过‘, age=26}

2.4 将对象保存到Spring容器中方法2:Spring 组件扫描

  除了上面章节中给大家介绍的@Bean方式可以将对象保存到Spring容器中,还有组件扫描方式可以将对象保存到Spring容器中。

  在main/java路径下,新建包ioc,包中创建类Hero

 package ioc;
 ?
 import org.springframework.context.annotation.Lazy;
 import org.springframework.context.annotation.Scope;
 import org.springframework.stereotype.Component;
 //@Component(组件):这个注解一旦标记,表示当前类要自动实例化对象,保存到Spring容器中
 //这个对象保存到Spring容器中的id就是当前类名,只是首字母要小写
 //此处直接设置对象属性,适用于对象属性不需要改变的情况
 @Component
 public class Hero {
     private String name= "关羽";
     private String job = "战士";
 ?
     public Hero(){
         System.out.println("Hero实例化");
    }
 ?
     public String getName() {
         return name;
    }
 ?
     public void setName(String name) {
         this.name = name;
    }
 ?
     public String getJob() {
         return job;
    }
 ?
     public void setJob(String job) {
         this.job = job;
    }
 ?
     @Override
     public String toString() {
         return "Hero{" +
                 "name=‘" + name + ‘\‘‘ +
                 ", job=‘" + job + ‘\‘‘ +
                 ‘}‘;
    }
 }
 ?

包下编写配置类Config

 package ioc;
 ?
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.Configuration;
 ?
 @Configuration
 //如果想启动组件扫描的注入方式(即:如果想让Hero类上的@Component生效),
 //则还要在配置类中声明扫描的包
 @ComponentScan("ioc")
 public class Config {
 ?
 }
 ?

同样按照上面的方法,转到测试类进行测试

 package ioc;
 ?
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 ?
 public class IocTest {
     AnnotationConfigApplicationContext acac;
     @Before
     public void init(){
         acac = new AnnotationConfigApplicationContext(Config.class);
    }
     @After
     public void destroy(){
         acac.close();
    }
     @Test
     public void run(){
         Hero hero = acac.getBean("hero",Hero.class);
         System.out.println(hero);
    }
 }
 ?

输出结果:Hero{name=‘关羽‘, job=‘战士‘}

组件扫描流程:

1.实例化ACAC对象时,指定Config类的反射

2.这个Config类上如果有@ComponentScan注解,并指定了包

3.扫描这个包中的所有类(包括子孙包中的所有类)

4.如果某个类上带有支持组件扫描方式保存该类对象到Spring容器的注解(例如@Component)

5.实例化这个对象,并将这个对象保存到Spring容器中,注意:id是这个类名首字母小写

2.5 保存到Spring的细节

  • 除了@Component,还有以下常见注解有相同功能

    • @Controller \ RestController 控制器

    • @Service 业务逻辑

    • @Repository 数据访问

    • .....

  为什么功能一样要设计这么多的注解呢?原因是见到注解名称,就知道这个类的作用(见名知意)

  • 关于特殊的类名

    我们已经学习使用组件扫描时,当前类型对象的id是当前类名首字母小写,但是有特殊情况:如果有类名连续两个以上字母都是大写,例如:VIPStu,那么它保存到Spring容器中的id就是类名(原类名)。

  • 自定义组件ID

    如果有特殊原因不能或不希望类名做id,我们可以自定义组件id,例如:

     @Component("myHero")
     public class Hero {
        ...
     }
     //注意:此时测试类中的id要改变
     //Hero hero = acac.getBean("myHero",Hero.class);

    当然,其它支持组件扫描的注解,也可以用这个方法定义id,一般都是被动使用。

3.Spring中对象的作用域

3.1 作用域(Scope)概述

我们的飞机大战项目中,一共有6个类型,这6个类型中就涉及了我们要讲的两种作用域:

单例(singleton):从程序开始到程序结束,某个类型的对象始终只有一个

天空和英雄机就是单例

原型(prototype): 从程序开始到程序结束,某个类型不断出现新的对象,没有数量的限制(需要时就初始化创建新的对象)

小敌机,大敌机,小蜜蜂,子弹

3.2 单例(singleson)作用域

默认情况下,所有保存到Spring容器中的对象都是单例(singleton)(节省内存,原型要创建多个对象,占用内存)

 @Test
 public void singleton(){
     //从Spring容器中获得两次hero,虽然名字不同,但两次获得的对象完全相同,指同一对象
     Hero hero1 = acac.getBean("hero",Hero.class);
     Hero hero2 = acac.getBean("hero",Hero.class);
     //因为默认情况下,Spring容器中对象都是singleton
     //所以hero1和hero2是相同的引用,指向同一个对象
     hero1.setJob("法师");
     System.out.println(hero2);//Hero{name=‘关羽‘, job=‘法师‘}   修改1影响2,因为是同一对象
     System.out.println(hero1==hero2);//true
 }

3.3 原型(prototype)作用域

修改默认scope,将对象修改为prototype(原型)的scope

@Bean保存方式中的设置:

 @Bean
 @Scope("prototype")
 public Stu stu(){
     //代码略
 }

组件扫描方式保存的设置:

 @Component
 @Scope("prototype")
 public class Hero {
     // 代码略
 }

测试代码:

 @Test
 public void prototype(){
         //在@Component注解下编写了@Scope("prototype")注解
         //修改了当前Hero类为原型Scope
         Hero hero1 = acac.getBean("hero",Hero.class);
         Hero hero2 = acac.getBean("hero",Hero.class);
         //原型模式下,每次获得Hero对象Spring都会实例化一个新的对象返回
         //这样就造成了上面两个对象是不同引用的两个不同对象
         hero1.setJob("刺客");
         System.out.println(hero1);//Hero{name=‘关羽‘, job=‘刺客‘}
         System.out.println(hero2);//Hero{name=‘关羽‘, job=‘战士‘} 不受hero1的影响
         System.out.println(hero1==hero2);//false
 }

今后使用Spring框架的时候,如果需要设置Scope,根据这两个特性来设置修改即可。

4.惰性初始化(懒惰初始化)

  Spring容器中大部分对象都是单例的,单例对象会在实例化Spring容器时进行对象的实例化,也就是说实例化Spring容器时会有大量对象进行实例化。针对实例化时会消耗较多资源,但是运行过程中又不一定使用到的对象,我们可以设置为懒惰初始化。

使用@Lazy表示懒惰初始化

@Bean对象设置懒惰加载:

 @Bean
 @Lazy
 public Stu stu(){
     //代码略
 }

组件扫描的方式设置懒惰加载:

 @Component
 @Lazy
 public class Hero {
     // 代码略
 }

测试代码:

 package ioc;
 ?
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 ?
 public class IocTest {
     AnnotationConfigApplicationContext acac;
     @Before
     public void init(){
         acac = new AnnotationConfigApplicationContext(Config.class);
         System.out.println("init方法运行完毕");
    }
     @After
     public void destroy(){
         acac.close();
    }
     @Test
     public void run(){
         Hero hero = acac.getBean("hero",Hero.class);
         System.out.println(hero);
    }
     @Test
     public void singleton(){
         //从Spring容器中获得两次hero,虽然名字不同,但两次获得的对象完全相同,指同一对象
         Hero hero1 = acac.getBean("hero",Hero.class);
         Hero hero2 = acac.getBean("hero",Hero.class);
         //因为默认情况下,Spring容器中对象都是singleton
         //所以hero1和hero2是相同的引用,指向同一个对象
         hero1.setJob("法师");
         System.out.println(hero2);
         System.out.println(hero1==hero2);
    }
 ?
     @Test
     public void prototype(){
         //在@Component注解下编写了@Scope("prototype")注解
         //修改了当前Hero类为原型Scope
         Hero hero1 = acac.getBean("hero",Hero.class);
         Hero hero2 = acac.getBean("hero",Hero.class);
         //原型模式下,每次获得Hero对象Spring都会实例化一个新的对象返回
         //这样就造成了上面两个对象是不同引用的两个不同对象
         hero1.setJob("刺客");
         System.out.println(hero1);
         System.out.println(hero2);//不受hero1的影响
         System.out.println(hero1==hero2);
    }
 ?
     //通过测试验证实例化对象的运行顺序
     @Test
     public void lazy(){
         System.out.println("lazy方法运行");
         Hero hero = acac.getBean("hero",Hero.class);
         System.out.println(hero);
    }
 }
 ?

输出结果:

 init方法运行完毕
 lazy方法运行
 Hero实例化
 Hero{name=‘关羽‘, job=‘战士‘}

非Lazy情况下输出结果:

 Hero实例化
 init方法运行完毕
 lazy方法运行
 Hero{name=‘关羽‘, job=‘战士‘}

  正常情况下,是在init的时候实例化对象,而输出的结果证明我们的设置生效了,是在acac.getBean方法的位置才实例化的Hero对象。如果方法中没有获得Hero对象的代码,这个对象就不会实例化了。所以通过使用惰性初始化可以针对个别对象节省内存。

  什么时候需要使用这个对象?什么时候懒惰初始化的对象才会实例化?

  惰性初始化可以针对个别对象节省内存,不宜大范围使用,因为大范围使用会引起运行压力大\缓慢。由于prototype是在需要的时候产生对象,所以不能和Lazy一起使用。

5.DI

5.1 什么是DI?

  DI(Dependency Injection)依赖注入

  Ioc是控制反转,它是一种编程思想,DI是基于控制反转思想的一种操作,要明确依赖注入的原因和作用,我们要先了解"依赖的概念"。

5.2 什么是依赖?

  依赖指程序中A类需要使用到B类型对象的情况,那么就说A类依赖B类。

要在Spring中实现这个依赖过程,我们要创建如下类来演示:

青龙偃月刀类

 package san;
 ?
 public class DragonBlade {
     private String name = "青龙偃月刀";
 ?
     public String getName() {
         return name;
    }
 ?
     public void setName(String name) {
         this.name = name;
    }
 ?
     @Override
     public String toString() {
         return name;
    }
 }
 ?

关羽类

 package san;
 ?
 public class GuanYu {
     private String name = "关云长";
     //在GunaYu类中声明了属性DragonBlade
     //这就是依赖关系,关羽依赖青龙偃月刀
     private DragonBlade dragonBlade;
 ?
     public void fight(){
         //dragonBlade自动调用toString输出名字
         System.out.println(name+"使用"+dragonBlade+"战斗");
    }
     public String getName() {
         return name;
    }
 ?
     public void setName(String name) {
         this.name = name;
    }
 ?
     public DragonBlade getDragonBlade() {
         return dragonBlade;
    }
 ?
     public void setDragonBlade(DragonBlade dragonBlade) {
         this.dragonBlade = dragonBlade;
    }
 ?
 }
 ?

关羽依赖青龙偃月刀,在配置类中:

 package san;
 ?
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 ?
 @Configuration
 public class Config {
     @Bean
     public DragonBlade blade(){
         return new DragonBlade();
    }
     @Bean
     public GuanYu guanYu(){
         return new GuanYu();;
    }
 }
 ?

测试类让关羽战斗:

 package san;
 ?
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 ?
 public class SanTest {
     AnnotationConfigApplicationContext acac;
     @Before
     public void init(){
         acac = new AnnotationConfigApplicationContext(Config.class);
    }
     @After
     public void destroy(){
         acac.close();
    }
     @Test
     public void diTest(){
         //先获得青龙偃月刀
         DragonBlade blade = acac.getBean("blade",DragonBlade.class);
         //再获得关羽
         GuanYu guanYu = acac.getBean("guanYu",GuanYu.class);
         //把刀赋给关羽
         guanYu.setDragonBlade(blade);
         //关羽拿刀战斗
         guanYu.fight();
    }
 }
 ?

我们经过配置看到测试代码,逻辑和之前主动控制没有什么多大区别,但是使用依赖注入可以简化这个流程。

实现依赖注入:修改Config配置

 package san;
 ?
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 ?
 @Configuration
 public class Config {
     @Bean
     public DragonBlade blade(){
         return new DragonBlade();
    }
     //实现依赖注入
     //GuanYu方法的参数列表中声明DragonBlade
     //Spring会自动从Spring容器中找到匹配的对象赋值
     @Bean
     public GuanYu guanYu(DragonBlade blade){
         GuanYu guanYu = new GuanYu();
         guanYu.setDragonBlade(blade);
         return guanYu;
    }
 }
 ?

测试类修改代码运行:

 @Test
 public void diTest(){
     //直接获得关羽
     GuanYu guanYu=acac.getBean("guanYu",GuanYu.class);
     //关羽战斗,是带着刀的
     guanYu.fight();
 }

所谓依赖注入,就是将两个或更多类的依赖关系实现在Spring容器内部,从Spring容器中获取时,是一个已经注入好依赖对象的对象。

 

Spring-定义、功能、Ioc/DI、Junit、作用域(singleson、prototype)、惰性初始化

上一篇:多线程交替执行


下一篇:Java 高级编程 学习路线