Spring IoC和DI

文章目录

一、丑陋代码(理解)

1、代码耦合度高

java 复制代码
public class EmployeeServiceImpl implements IEmployeeService {
    private IEmployeeDAO employeeDAO =  employeeDAO = new EmployeeDAOJdbcImpl();
    
    public void save(Employee employee){
        // TODO
    }
  
    // ...
}

此时如果把 IEmployeeDAO 的实现类换成 EmployeeDAOMyBatisImpl,此时需要修改 EmployeeServiceImpl 的源代码,不符合开闭原则。

开闭原则:对于扩展是开放的,对于修改是关闭的。 开闭原则的好处:可维护性。

2、控制事务繁琐

考虑一个应用场景:需要对系统中的某些业务方法做事务管理,拿 save 方法举例。

java 复制代码
public class EmployeeServiceImpl implements IEmployeeService {
    private IEmployeeDAO employeeDAO;
    
    public EmployeeServiceImpl(){
        employeeDAO = new EmployeeDAOJdbcImpl();
    } 
    
    public void save(Employee employee){
        // 打开资源
        // 开启事务
        try {
            // 保存业务操作
            // 提交事务
        }catch (Exception e){
            // 回滚事务
        }finally{
            // 释放资源
        }
    }
  
    // ...
}

但问题,若很多方法都要加事务的话,就会存在大量的重复代码分散不同类的不同方法中,不利于维护。

3、第三方框架运用太麻烦

单独使用 MyBatis 框架的保存操作代码如下:

java 复制代码
SqlSession session = MyBatisUtil.getSession();
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = new Employee();
employee.setSalary(new BigDecimal("900"));
employee.setId(1L);
employeeMapper.update(employee); // 使用
session.commit();
session.close();

对使用者而言最关心的是获取到 EmployeeMapper 对象使用,而不关心这个对象创建。

二、Spring 介绍(了解)

1、Spring 定义(理解)

源于 Rod Johnson 在其著作 《Expert one on one J2EE design and development》 中阐述的部分理念和原型衍生而来。

Spring 是一个轻量级的 IoC / DI和 AOP 容器 的开源框架,致力于构建轻量级的 JavaEE 应用,简化应用开发,本身涵盖了传统应用开发,还拓展到移动端,大数据等领域。

什么是容器(Container):从程序设计角度看就是装对象的的对象(map),因为存在放入、拿出等操作,所以容器还要管理对象的生命周期,如 Tomcat 就是 Servlet 的容器。

2、Spring 在 JavaEE开发中作用(理解)

Spring 提供了 JavaEE 每一层的解决方案(full stack)。Web 开发中的最佳实践:根据职责的纵向划分:控制层、业务层、持久层:不同框架解决不同领域的问题。

3、Spring 优势

Spring 除了不能帮我们写业务逻辑,其余的几乎什么都能帮助我们简化开发,作用如下

  • Spring 能帮我们低侵入/低耦合地根据配置文件创建及组装对象之间的依赖关系。
  • Spring 面向切面编程能帮助我们无耦合的实现日志记录,性能统计,安全控制等。
  • Spring 能非常简单的且强大的声明式事务管理。
  • Spring 提供了与第三方数据访问框架(如Hibernate、JPA)无缝集成,且自己也提供了一套 JDBC 模板来方便数据库访问。
  • Spring 提供与第三方 Web(如 Struts1/2、JSF)框架无缝集成,且自己也提供了一套 Spring MVC 框架,来方便 Web 层搭建。
  • Spring 能方便的与如 Java Mail、任务调度、缓存框架等技术整合,降低开发难度。

4、Spring FrameWork 体系

  • Core Container(核心容器 IoC)包含有 Beans、Core、Context 和 SpEL 模块。
  • Test 模块支持使用 JUnit 和 TestNG 对 Spring 组件进行测试。
  • AOP 模块提供了一个符合 AOP 联盟标准的面向切面编程的实现。
  • Data Access / Integration 层包含有 JDBC、ORM、OXM、JMS 和 Transaction 模块。
  • Web 层包含了 Web、Web-Servlet、WebSocket、Web-Porlet 模块。

课程重点设涉及 Test、Core Container、AOP、TX、MVC。

5、Spring 依赖

这里我们使用 5.0.8.RELEASE 的版本。

三、IoC 和 DI 思想(理解)

1、IoC

IoC:Inversion of Control(控制反转):读作"反转控制",更好理解,不是什么技术,而是一种设计思想,好比于 MVC。

就是将原本在程序中手动创建对象的控制权,交由别人来管理。

1.1、没用 IoC

若调用者需要使用某个对象,其自身就得负责该对象及该对象所依赖对象的创建和组装。

1.2、用 IoC

调用者只管负责从IoC 容器中获取需要使用的对象,不关心对象的创建过程,也不关心该对象依赖对象的创建以及依赖关系的组装,也就是把创建对象的控制权反转给了 IoC 容器。

1.3、对比

2、DI

DI(Dependency Injection):依赖注入。

IoC 从字面意思上很难体现出谁来维护对象之间的关系,Martin Fowler 提出一个新的概念 DI,更明确描述了"被注入对象(Service 对象)依赖 IoC 容器来配置依赖对象"。

所以两者配合在一起时:

  • IoC:指将对象的创建权,反转给了IoC 容器;
  • DI:指 IoC 容器创建对象的过程中,将对象依赖属性(常量,对象)通过配置设值给该对象。

四、Spring 的 HelloWorld 程序(掌握)

1、需求

创建对象,设置对象属性,并调用对象的方法。

2、新建 Maven 项目

设置编译版本及添加依赖:

xml 复制代码
<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>11</maven.compiler.source>
    <maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
	<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
		<scope>test</scope>
	</dependency>
</dependencies>

3、使用非 IoC 方式

3.1、编写类

java 复制代码
package cn.wolfcode._01_hello;

public class Person {
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void doWork() {
		System.out.println(this.name + "工作");
	}
}

3.2、编写单元测试类

java 复制代码
public class PersonTest {
	@Test
	public void testNotIoc() {
		Person person = new Person();
		person.setName("小罗");
		person.doWork();
	}
}

4、使用 Spring IoC 方式

4.1、添加依赖

xml 复制代码
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-context</artifactId>
	<version>5.0.8.RELEASE</version>
</dependency>

4.2、通过配置告诉 Spring 来管理对象

在配置告诉 Spring 管理哪个类的对象,想想之前学 JavaWeb 的时候,怎么配置 Servlet 的,和其配置有些类似,都是使用 XML 配置。在 resources 目录新建 Spring 的配置文件 01.hello.xml,配置如下:

xml 复制代码
<!-- 告诉 Spring 帮我们创建什么对象,设置什么属性值 -->
<bean id="person" class="cn.wolfcode._01_hello.Person"> <!-- IoC -->
    <!--  
        Person p = new Person();
        p.setName("红旗")
    -->
    <property name="name" value="大罗"/> <!-- DI -->
</bean>

4.3、启动容器获取对象

java 复制代码
public class PersonTest {
	@Test
	public void testIoc() {
        // 解析配置启动容器
		ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:01.hello.xml");
		// 从容器中根据名称获取对象
		Person person = (Person) ctx.getBean("person");
		person.doWork();
	}

    // ...
}

五、Spring 的 HelloWorld 程序分析(掌握)

1、ApplicationContext 及 bean

  • BeanFactory:Spring 最底层的接口,只提供了的 IoC 功能,负责创建、组装、管理 bean,在应用中;
  • ApplicationContext:继承了BeanFactory,除此之外还提供 AOP 集成、国际化处理、事件传播、统一资源加载等功能。

一般不使用 BeanFactory,而推荐使用 ApplicationContext(应用上下文)

被 Spring IoC 容器管理的对象称之为 bean。

2、Spring 配置方式

Spring IoC 容器启动时通过读取配置文件中的配置元数据,通过元数据对应用中的各个对象进行实例化及装配。元数据的配置有三种方式:

  • XML-based configuration
  • Annotation-based configuration
  • Java-based configuration

六、Spring XML 基本配置(掌握)

1、XML 语法提示

只要添加了对应的约束头,写配置的时候就可以提示。而在 IDEA 中是通过手写一些提示出来的。

2、bean 元素中 id 和 name 属性

在 Spring 配置中,id 和 name 属性都可以定义 bean 元素的名称,不同的是:

  • id 属性,遵守 XML 语法 ID 约束。必须以字母开始,可以使用字母、数字、连字符、下划线、句话、冒号,不能以"/"开头。
  • name 属性,就可以使用很多特殊字符,比如在 Spring 和 Struts1,就得使用 name 属性来的定义 bean 的名称。当然也可以使用 name 属性为 bean 元素起多个别名,多个别名之间使用逗号或空格隔开,在代码中依然通过容器对象.getBean(...) 方法获取。

注意:从 Spring3.1 开始,id 属性不再是 ID 类型了,而是 String 类型,也就是说 id 属性也可以使用"/"开头了,而 bean 元素名称的唯一性由容器负责检查

xml 复制代码
<bean name="/login" class="cn.wolfcode.hello.web.controller.LoginController" />  
xml 复制代码
<bean name="hello,hi" class="cn.woldcode.hello.HelloWorld"/>

建议:bean 起名尽量规范,不要搞一些非主流的名字,尽量使用 id

七、getBean 方法及 Spring 常见异常(掌握)

1、按照名字

缺点:要强转,不太安全。

java 复制代码
Person bean1 = (Person)ctx.getBean("person");

2、按照类型

要求在 Spring 中只配置一个这种类型的实例。缺点:可能找多个报错。

java 复制代码
Person bean2 = ctx.getBean(Person.class);

3、按照名字和类型

java 复制代码
Person bean3 = ctx.getBean("person3", Person.class);

4、常见异常

异常1:NoSuchBeanDefinitionException: No bean named 'person2' available

按照 bean 名称去获取 bean 时,不存在名称为 person2 的 bean。

异常2:NoUniqueBeanDefinitionException: No qualifying bean of type 'cn.wolfcode._01_hello.Person' available: expected single matching bean but found 2: person,person2

按照 cn.wolfcode._01_hello.Person 类型去获取 bean 时,期望找到该类型唯一的一个 bean,可是此时找到了两个。

异常3:BeanDefinitionParsingException: Configuration problem: Bean name 'person' is already used in this element Offending resource: class path resource [01.hello.xml]

在 01.hello.xml 文件中,多个 bean 元素的名称是 person。

八、Spring 测试(掌握)

1、传统测试存在的问题

每个测试都要重新启动 Spring 容器,启动容器的开销大,测试效率低下。不应该是测试代码管理 Spring 容器,应该是 Spring 容器在管理测试代码。

2、使用 Spring 测试

2.1、添加依赖

xml 复制代码
<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-test</artifactId>
	<version>5.0.8.RELEASE</version>
	<scope>test</scope>
</dependency>

2.2、编写测试代码

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class) // 在测试方法之前启动容器
@ContextConfiguration("classpath:01.hello.xml") // 指定加载的配置文件
public class PersonTest {
	@Autowired // 在测试方法运行之前,获取容器对象 person = ctx.getBean(Person.class)
	private Person person;
	
	@Test
	public void testIoC() {
		person.doWork();
	}
}

九、IoC 概述(理解)

将原本在程序中手动创建对象的控制权,交由 IoC 容器来管理,在 Spring 中被管理的对象称之为 bean。接下我们来看下 Spring 创建对象的时机、作用域等。

十、bean 创建时机(了解)

给类添加构造器:

java 复制代码
package cn.wolfcode._01_hello;

public class Person {
	private String name;
	
	public Person() {
		System.out.println("对象创建了");
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public void doWork() {
		System.out.println(this.name + "工作");
	}
}

运行单元测试,你会发现在启动 Spring 容器的时候就会创建所有的 bean。

十一、bean 实例方式(掌握)

1、构造器实例化

一般使用无参构造器,最标准,也使用最多。

1.1、编写类

java 复制代码
package cn.wolfcode._02_ioc;

public class Cat1 {
	public Cat1() {
		System.out.println("Cat1 对象被创建了");
	}
}

1.2、编写配置文件

在 resources 目录下新建 02.ioc.xml,配置如下:

xml 复制代码
<bean id="cat1" class="cn.wolfcode._02_ioc.Cat1"/>

1.3、编写测试类

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:02.ioc.xml")
public class Cat1Test {
	@Autowired
	private Cat1 cat1;

	@Test
	public void test() {
		System.out.println(cat1);
	}
}

2、实现 FactoryBean 接口实例化

2.1、编写类

java 复制代码
package cn.wolfcode._02_ioc;
public class Cat2 {
	public Cat2() {
		System.out.println("Cat2 对象被创建了");
	}
}
java 复制代码
package cn.wolfcode._02_ioc;

// 实列工厂类
public class Cat2FactoryBean implements FactoryBean<Cat2> {
    // 创建对象的方法,给 Spring 用, 创建对象的时候使用
	@Override
	public Cat2 getObject() throws Exception {
		Cat2 cat2 = new Cat2();
		return cat2;
	}
	// 判断类型用的方法,给 Spring 用
	@Override
	public Class<?> getObjectType() {
		return Cat2.class;
	}
}

2.2、编写配置文件

在 02.ioc.xml,配置如下:

xml 复制代码
<bean id="cat2" class="cn.wolfcode._02_ioc.Cat2FactoryBean"/>

2.3、编写测试类

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:02.ioc.xml")
public class Cat2Test {
	@Autowired
	private Cat2 cat2;
	@Test
	public void test() {
		System.out.println(cat2);
	}
}

2.4、配置解释及应用

因为 Spring 在对个 bean 元素解析创建对象的时候,还是先创建这个工厂对象;但发现这个类实现了 FactoryBean 这接口,就会再调用工厂对象的 getObject() 方法创建 bean 对象,把这个 bean 对象存在容器中。总结一句配置类发现类名是以 FactoryBean 结尾的话,注意到底是创建什么类型的对象存在容器中。

这种方式与其他框架整合的时候用的比较多,如集成 MyBatis 框架使用,就会配置 org.mybatis.spring.SqlSessionFactoryBean。也可以是工厂对象注入属性,为后面整合框架作铺垫。

十二、bean 作用域(了解)

1、作用域

在 Spring 容器中是指其创建的 bean 对象相对于其他 bean 对象的请求可见范围。

2、分类

  • singleton:单例 ,在 Spring IoC 容器中仅存在一个 bean 实例 (缺省默认的 scope)。
  • prototype:多例 ,每次从容器中调用 Bean 时,都返回一个新的实例,即每次调用 getBean() 时 ,相当于执行 new XxxBean():不会在容器启动时创建对象。
  • request:用于 web 开发,将 Bean 放入 request 范围,request.setAttribute("xxx") , 在同一个 request 获得同一个 Bean。
  • session:用于 web 开发,将 Bean 放入 Session 范围,在同一个 Session 获得同一个 Bean。
  • application:Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext。
  • websocket:Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext。

3、配置

xml 复制代码
<bean id="" class="" scope="作用域"/>

默认不配置就是 singleton,注意当 scope 是 prototype 时容器启动不会创建该 bean。

4、使用总结

在开发中主要使用 scope="singleton"。对于 Struts1 的 Action 使用 request,Struts2 中的 Action 使用 prototype 类型,其他使用 singleton,即不配置

十三、bean 初始化和销毁(掌握)

1、问题

比如 DataSource,SqlSessionFactory 最终都需要关闭资源,以前用完(回收销毁前)需自己手动关闭资源,即都要调用 close 方法。

在使用 Spring 之后,这些对象等都会被 Spring IoC 容器管理的,但这些 bean 使用完之后也需要释放资源的,但现在这些可交由 Spring 来完成。

2、正常关闭容器

调用容器的销毁方法 close(),才是正常关闭容器。有以下几种操作方式:

  • 使用 Spring Test 会自动关闭容器
  • 手动关闭容器 ctx.close()(注意需类型强转)
  • 使用 Java7 自动关闭资源
  • 使用 Lombok 的注解 @CleanUp 贴要关闭的资源上
  • 把 Spring 线程作为 JVM 的子线程:ctx.registerShutdownHook()

3、没使用 Spring

3.1、编写类

java 复制代码
package cn.wolfcode._02_ioc;

// 模拟的数据库连接池
public class MyDataSource {
	public MyDataSource() {
		System.out.println("对象创建");
	}
	public void getConnection() {
		System.out.println("拿到连接");
	}
	public void init() {
		System.out.println("初始化池子");
	}
	public void close() {
		System.out.println("销毁池子");
	}
}

3.2、编写单元测试类

java 复制代码
public class MyDataSourceTest {
	@Test
	public void testOld() {
		MyDataSource dataSource = new MyDataSource();
		dataSource.init();
		dataSource.getConnection();
		dataSource.close(); 
	}
}

4、使用 Spring

4.1、编写配置文件

在 02.ioc.xml,配置如下:

xml 复制代码
<bean id="myDataSource" class="cn.wolfcode._02_ioc.MyDataSource" 
    init-method="init" destroy-method="close" scope="prototype"/>

4.2、修改单元测试类

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:02.ioc.xml")
public class MyDataSourceTest {
	@Autowired
	private MyDataSource dataSource;

	@Test
	public void testNew() {
		dataSource.getConnection();
	}
}

注意:

  • 只有正常关闭容器,才会执行 bean 的配置的 destroy-method 方法。
  • bean 的 scope 为 prototype 的,那么容器只负责创建和初始化,它并不会被 Spring 容器管理(不需要存起来),交给用户自己处理。即 bean 作用域为 prototype 的即使配置了 destroy-method 也不会被调用。

十四、DI 概述(理解)

1、定义

指 Spring 创建对象的过程中,将对象依赖属性通过配置设值给该对象。

2、注入方式

  • setter 注入(或叫属性注入),其类必须提供对应 setter 方法
  • 构造器注入。
java 复制代码
public class User {
    private String name;
    
    public User() {}
    public User(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
java 复制代码
public class UserTest {
    @Test
    public void test() {
        User u1 = new User();
        u1.setName("郭嘉"); // 属性注入

        User u2 = new User("荀彧"); // 构造器注入
    }
}

3、注入值

常见注入值由常量、bean 等。

十五、注入常量值(掌握)

给对象注入的值的类型为八大基本数据类型及其包装类,String,BigDecimal 等等。

1、XML 配置语法

xml 复制代码
<property name="对象属性名称"  value="需要注入的值"/>

2、代码示例

2.1、编写类

java 复制代码
package cn.wolfcode._03_di;

public class Employee {
	private String name;
	private int age;
	private BigDecimal salary;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public BigDecimal getSalary() {
		return salary;
	}
	public void setSalary(BigDecimal salary) {
		this.salary = salary;
	}
	@Override
	public String toString() {
		return "Employee [name=" + name + ", age=" + age + ", salary=" + salary + "]";
	}
}

2.2、编写配置文件

在 resources 目录下新建 03.di.xml,配置如下:

xml 复制代码
<bean id="employee" class="cn.wolfcode._03_di.Employee">
    <!-- name 写属性名  value 是写属性值 -->
    <property name="name" value="罗老师"/>
    <property name="age" value="12"/>
    <property name="salary" value="1000000"></property>
</bean>

2.3、编写单元测试类

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:03.di.xml")
public class EmployeeTest {
	@Autowired
	private Employee employee;

	@Test
	public void test() {
		System.out.println(employee);
	}
}

注意:Spring 帮我们做类型转换,但前提是能转换,不能会报错的。

十六、注入 bean(掌握)

给对象注入的值的是容器中的另外一个对象。

1、XML 配置语法

xml 复制代码
<property name="对象属性名称" ref="容器另外一个 bean 的 id 值"/>

2、代码示例

2.1、编写类

java 复制代码
package cn.wolfcode._03_di;

public class EmployeeDao {
}
java 复制代码
package cn.wolfcode._03_di;

public class EmployeeService {
	private EmployeeDao employeeDao;
	public void setEmployeeDao(EmployeeDao employeeDao) {
		this.employeeDao = employeeDao;
	}
	@Override
	public String toString() {
		return "EmployeeService [employeeDao=" + employeeDao + "]";
	}
}

2.2、编写配置文件

在 03.di.xml,配置如下:

xml 复制代码
<!-- 配置 EmployeeDao bean -->
<bean id="employeeDao" class="cn.wolfcode._03_di.EmployeeDao"/>

<!-- 配置 EmployeeService bean -->
<bean id="employeeService" class="cn.wolfcode._03_di.EmployeeService">
    <!-- 
        name 写的是属性名
        ref 注入 bean 的时候使用,写容器中另外一个 bean 的 id 的值
     -->
    <property name="employeeDao" ref="employeeDao"/>
</bean>

2.3、编写单元测试类

java 复制代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:03.di.xml")
public class EmployeeServiceTest {
	@Autowired
	private EmployeeService employeeService;

	@Test
	public void test() {
		System.out.println(employeeService);
	}
}
相关推荐
Bug退退退12326 分钟前
RabbitMQ 高级特性之死信队列
java·分布式·spring·rabbitmq
六毛的毛2 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack2 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669132 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong2 小时前
curl案例讲解
后端
一只叫煤球的猫3 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
大鸡腿同学4 小时前
身弱武修法:玄之又玄,奇妙之门
后端
轻语呢喃6 小时前
JavaScript :字符串模板——优雅编程的基石
前端·javascript·后端
MikeWe6 小时前
Paddle张量操作全解析:从基础创建到高级应用
后端
岫珩6 小时前
Ubuntu系统关闭防火墙的正确方式
后端