Spring 核心技术

发布于 2025-02-02 19:19:11 字数 9248 浏览 7 评论 0

1. IOC(控制反转)

1.1 什么是 IOC

IoC(Inversion of Control),意为控制反转,不是什么技术,而是一种设计思想。Ioc 意味着 将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制

如何理解好 Ioc 呢?理解好 Ioc 的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:

  • 谁控制谁,控制什么 :传统 Java SE 程序设计,我们直接在对象内部通过 new 进行创建对象,是程序主动去创建依赖对象;而 IoC 是有专门一个容器来创建这些对象,即由 Ioc 容器来控制对 象的创建;谁控制谁?当然是 IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  • 为何是反转,哪些方面反转了 :有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

简单来说

正转:比如有一个类,在类里面有方法(不是静态的方法),调用类里面的方法,创建类的对象,使用对象调用方法,创建类对象的过程,需要 new 出来对象

反转:把对象的创建不是通过 new 方式实现,而是交给 Spring 配置创建类对象

1.2 IoC 能做什么

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了 IoC 容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实 IoC 对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在 IoC/DI 思想中,应用程序就变成被动的了,被动的等待 IoC 容器来创建并注入它所需要的资源了。

IoC 很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

1.3 IoC 和 DI

DI—Dependency Injection,即“依赖注入” :组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

理解 DI 的关键是:“ 谁依赖谁,为什么需要依赖,谁注入谁,注入了什么 ”,那我们来深入分析一下:

  • 谁依赖于谁: 当然是应用程序依赖于 IoC 容器;
  • 为什么需要依赖: 应用程序需要 IoC 容器来提供对象需要的外部资源;
  • 谁注入谁: 很明显是 IoC 容器注入应用程序某个对象,应用程序依赖的对象;
  • 注入了什么: 就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

IoC 和 DI 由什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以 2004 年大师级人物 Martin Fowler 又给出了一个新的名字:“依赖注入”,相对 IoC 而言, “依赖注入” 明确描述了 “被注入对象依赖 IoC 容器配置依赖对象”

对于 Spring Ioc 这个核心概念,我相信每一个学习 Spring 的人都会有自己的理解。这种概念上的理解没有绝对的标准答案,仁者见仁智者见智。 理解了 IoC 和 DI 的概念后,一切都将变得简单明了,剩下的工作只是在框架中堆积木而已,下一节来看看 Spring 是怎么用的

1.4 IOC 底层原理 (降低类之间的耦合度)

  • 底层原理使用技术
    • xml 配置文件
    • dom4j 解决 xml
    • 工厂设计模式
    • 反射
  • 原理
//伪代码
//需要实例化的类
public class UserService{
}

public class UserServlet{
  //得到 UserService 的对象
  //原始的做法:new 对象();  来创建
  
  //经过 spring 后
  UserFactory.getService();   //(下面两步的代码调用的)
}

第一步:创建 xml 配置文件,配置要创建的对象类

<bean id="userService" class="cn.blinkit.UserService"/>

第二步:创建工厂类,使用 dom4j 解析配置文件+反射

public class Factory {
  //返回 UserService 对象的方法
  public static UserService getService() {
    //1.使用 dom4j 来解析 xml 文件  
    //根据 id 值 userService,得到 id 值对应的 class 属性值
    String classValue = "class 属性值";
    //2.使用反射来创建类对象
    Class clazz = Class.forName(classValue);
    //创建类的对象
    UserService service = clazz.newInstance();
    return service;
  }
}

超详细原理讲解: java-bible/4.principle.md at master · biezhi/java-bible

1.5 Spring 中怎么用

(1)配置文件方式

我们在 Spring 中是这样获取对象的:

public static void main(String[] args) {   
  ApplicationContext context = new FileSystemXmlApplicationContext("applicationContext.xml");   
  Lol lol = (Lol) context.getBean("lol");   
  lol.gank(); 
}

一起看看 Spring 如何让它生效呢,在 applicationContext.xml 配置文件中是酱紫的:

<bean id="lol" class="com.biezhi.test.Lol">
  <property name="name" value="剑圣" />   
</bean>  

Lol 类是这样的:

public class Lol {

  private String name;
  
  public Lol() {
  }
  
  public void gank(){
    System.out.println(this.name + "在 gank!!");
  }
  
  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

上面的代码运行结果自然是 剑圣在 gank!!

(2)注解方式

Spring 更高级的用法,在 3.0 版本之后有了基于 Annotation 的注入实现,为毛每次都要配置 Xml 看到都蛋疼。。

首先还是要在 xml 中配置启用注解方式

<context:annotation-config/>  

这样就能使用注解驱动依赖注入了,下面是一个使用场景

public class Lol {

  @Autowired
  private DuangService duangService ;
  
  public void buyDuang(String name, int money) {
    duangService.buy(name, money);
  }
}
@Service("duangService")
public class DuangService {
  
  public void buy(String name, int money){
    if(money > 0){
      System.out.println(name + "买了" + money + "毛钱的特效,装逼成功!");
    } else{
      System.out.println(name + "没钱还想装逼,真是匪夷所思");
    }
  }
}

这只是一个简单的例子,剑圣打野的时候想要买 5 毛钱的三杀特效,嗯。。虽然不符合逻辑

此时 DuangService 已经注入到 Lol 对象中,运行代码的结果(这里是例子,代码不能运行的)就是:

德玛买了 5 毛钱的特效,装逼成功!

2. DI(依赖注入)

2.1 什么是依赖注入

在依赖注入的模式下,创建被调用者得工作不再由调用者来完成,创建被调用者实例的工作通常由 Spring 容器完成,然后注入调用者。 创建对象时,向类里的属性设置值

2.2 为什么使用依赖注入

为了实现代码/模块之间松耦合。

2.3 为什么要实现松耦合

上层调用下层,上层依赖于下层,当下层剧烈变动时上层也要跟着变动,这就会导致模块的复用性降低而且大大提高了开发的成本。

一般情况下抽象的变化概率很小,让用户程序依赖于抽象,实现的细节也依赖于抽象。即使实现细节不断变动,只要抽象不变,客户程序就不需要变化。这大大降低了客户程序与实现细节的耦合度。

2.4 IOC 和 DI 区别

  1. IOC 控制反转,把对象创建交给 Spring 配置
  2. DI 依赖注入,向类里面属性注入值
  3. 关系,依赖注入不能单独存在,需要在 IOC 基础上完成操作

2.5 依赖注入方式

  1. 使用 set 方法注入

  2. 使用有参构造注入

  3. 使用接口注入

说明:Spring 框架中支持前两种方式

(1)使用 set 方法注入

<bean id="person" class="cn.wang.property.Person">
<!--set 方法注入属性
  name 属性值:类中定义的属性名称
  value 属性值:设置具体的值
-->
    <property name="pname" value="zs"></property>
</bean>1234567

(2)使用有参构造注入

public class Person {
  private String pname;

  public void setPname(String pname) {
    this.pname = pname;
  }
}
<bean id="user" class="cn.wang.ioc.User">
    <!--构造方法注入属性-->
    <constructor-arg name="pname" value="Tony"></constructor-arg>
</bean>

(3)注入对象类型属性

  • 创建 service 和 dao 类,在 service 中得到 dao

具体实现过程

  • 在 service 中把 dao 作为属性,生成 dao 的 set 方法
public class UserService {
  // 1.定义 UserDao 类型属性
  private UserDao userDao;

  // 2.生成 set 方法
  public void setUserDao(UserDao userDao) {
    this.userDao = userDao;
  }
}
  1. 配置文件注入关系
<bean id="userDao" class="cn.wang.property.UserDao">
    <property name="name" value="Tom"></property>
  </bean>
  <bean id="userService" class="cn.wang.property.UserService">
    <!--name 属性值:UserService 类里的属性名称-->
    <!--ref 属性:UserDao 类配置 bean 标签中的 id 值-->
    <property name="userDao" ref="userDao"></property>
  </bean>12345678

(4)p 名称空间注入

p 名称空间注入

p 名称空间注入

(5)注入复杂类型属性

<!-- 注入复杂类型属性值 -->
  <!-- 
  String pname;
  String[] arrs;
  List<String> list;
  Map<String, String> map;
  Properties props; 
  -->
  <bean id="person" class="cn.wang.property.Person">
    <property name="pname" value="zs"></property>
    <property name="arrs">
      <list>
        <value>aaa</value>
        <value>bbb</value>
        <value>ccc</value>
      </list>
    </property>
    <property name="list">
      <list>
        <value>qqq</value>
        <value>www</value>
        <value>eee</value>
      </list>
    </property>
    <property name="map">
      <map>
        <entry key="001" value="Tom"></entry>
        <entry key="002" value="Amy"></entry>
        <entry key="003" value="Jim"></entry>
      </map>
    </property>
    <property name="props">
      <props>
        <prop key="username">admin</prop>
        <prop key="passwprd">admin</prop>
      </props>
    </property>
  </bean>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

文章
评论
27 人气
更多

推荐作者

冰魂雪魄

文章 0 评论 0

qq_Wl4Sbi

文章 0 评论 0

柳家齐

文章 0 评论 0

无法言说的痛

文章 0 评论 0

魄砕の薆

文章 0 评论 0

盗琴音

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文