【目录】
2.1 Spring的开发包
2.2 开发环境测试搭建
2.3 业务代码编写
2.4 IoC和DI
2.4.1 最基本的IoC控制反转的实现
2.4.2 DI依赖注入的实现
2.5 Spring的工厂
2 Spring IoC快速入门
Spring核心内容的基本开发步骤:
- 下载开发包,导入jar包
- 编写代码(基础代码和调用代码)
- 编写配置文件
2.1 Spring的开发包
2.2 开发环境测试搭建
回顾利用Maven搭建第一个Spring测试项
新建Web工程:spring_day1。pom.xml代码如下。
<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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.test</groupId>
<artifactId>spring_day1</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>spring_day1 Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- Spring依赖 -->
<!-- 1.Spring核心依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.3.22.RELEASE</version>
</dependency>
<!-- 2.Spring 日志依赖 -->
<!-- 日志框架 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.3</version>
</dependency>
<!-- 日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<build>
<finalName>spring_day1</finalName>
</build>
</project>
在接下来的学习中,日志统一采用log4j,日志框架根据整合情况再决定。
2.3 业务代码编写
采用传统方式模拟用户登录,思路如下:
下面开始具体编写步骤。
具体代码见下:
数据层接口 UserDAO.java
package dao;
//用户操作dao层
public interface UserDAO {
//从数据库查询数据
public void findUserByUsernameAndPassword();
}
数据层实体类UserDAOImpl.java
package dao;
public class UserDAOImpl implements UserDAO{
@Override
public void findUserByUsernameAndPassword() {
System.out.println("dao层,向数据库查询数据了");
}
}
业务层接口UserService .java
package service;
//用户操作
public interface UserService {
//模拟用户登录
public void login();
}
业务层实体类UserServiceImpl.java
package service;
import dao.UserDAO;
import dao.UserDAOImpl;
public class UserServiceImpl implements UserService{
public void login() {
System.out.println("service层被调用了");
UserDAO userDAO = new UserDAOImpl();
userDAO.findUserByUsernameAndPassword();
}
}
测试类SpringTest.java
package test;
import org.junit.Test;
import service.UserService;
import service.UserServiceImpl;
public class SpringTest {
@Test
public void test(){
//测试serivce
UserService userService = new UserServiceImpl();
userService.login();
}
}
完成以上代码以后,进行测试。
虽然完成了功能,但是可以看到,代码过于耦合,上层代码过度依赖于下一层代码的实现。例如,通过业务层去查询存在于数据层的数据,先必须现在业务层准备一个数据层的实例对象。
UserDAO userDAO = new UserDAOImpl();
在传统的代码中,经常是这样的情况:如果有某个对象A依赖于其他对象B,在使用时,需要先创建所依赖的对象B,传入对象A,然后才能对对象A进行操作。这种方式提高了代码的耦合度,十分不利于重构和维护。(为什么要高内聚低耦合?)
为了解决这种耦合,Spring采用了IoC(Inverse of Control,控制反转)的思想。这种思想,简单来说,就是引入工厂(也称第三者/IoC容器),将原来在程序中手动创建管理的对象,交给工厂来创建管理。在Spring框架中,这个工厂就是Spring中的工厂。
2.4 IoC和DI
提到IoC(控制反转),就不得不提到DI(依赖注入)。
以下是常见的两个问题。
Q1:怎样理解DI?
依赖:所有类的对象,都依赖于IoC容器来进行管理。
注入:类中的某个对象,需要某种外部资源(包括对象、资源、常量数据),无需自己去引用,IoC容器直接注入给它。这个对象不需要关心具体的资源来怎么来的,反正是IoC容器统统搞定。(提一句:Spring通过反射机制来实现注入。)
综上,下个定义,应用程序依赖于IoC容器来注入某个对象所需要的外部资源(包括对象、资源、常量数据),这就是DI(通过配置的方式为对象赋值/初始化/注入)。
Q2:许多人把IoC和DI这两个术语理解为同一个事物。怎样恰当的理解它们的关系?
笔者认为,可以这样理解:
IoC是一种思想,也是一种设计模式——把我们自己创建对象的控制权交给IoC容器,让IoC容器负责创建对象。
在IoC容器创建过程中,有一个重点——在系统运行中,动态的向某个对象提供它所需要的其他外部资源(包括对象、资源、常量数据)。
怎样能完成这个重点?可以通过DI来实现——给对象动态的注入外部资源(包括对象、资源、常量数据)。
综上,DI和IoC的关系就是这样——IoC的实现需要DI技术的支持。
PS:给对象注入一个对象,就实现了对对象间依赖关系的“一键”管理,不需要多个相互依赖的类new来new去。什么是依赖关系,看下面的示意代码。
class B {
private A a; //A是B的依赖对象,两者间存在依赖关系
}
在spring_day1工程中,userService依赖于userDAO。现在,仍然使用spring_day1工程来进行下面两个小节的演示。
2.4.1小节展示最基本的IoC控制反转的实现,通过Spring的工厂获取一个Bean,不实现动态的资源管理(UserService得到Spring创建好的bean对象userDAO,而SpringTest仍然使用传统方式新建对象userService)。
2.4.2小节展示DI依赖注入的实现,通过Spring的工厂获取自动注入好的资源的Bean,依赖关系的管理由配置文件实现。(SpringTest直接获取bean对象userService,而在userService这个bean里,已经注入好了userDAO)
2.4.1 最基本的IoC控制反转的实现
效果:UserService得到Spring创建好的bean对象userDAO,而SpringTest仍然使用传统方式新建对象userService。
过程:先添加配置文件,让Spring工厂创建一个bean对象userDAO。再修改UserServiceImpl实体类,从Spring工厂获取bean对象userDAO。
有两种spring配置方式:基于XML配置方式、基于注解配置方式。我们在这里采用前一种进行演示。对于这两种配置方式,后续会有更详细的讲解。
下面开始具体的演示。
1.新建配置文件:applicationContext.xml(位置:src/main/resources目录或者 WEB-INF目录、习惯性命名:applicationContext.xml)。
ApplicationContext直译为应用上下文,用来加载Spring框架配置文件,构建Spring的工厂对象,它也称之为Spring容器的上下文对象,也称之为Spring的容器。
引入xml的头部信息bean schema约束,可以参考规范文档中的的xsd-config.html。在引入之前先本地配置开发包中的xsd文件。
配好以后,在xml文件内的<beans></beans>标签内,按alt加斜杠,会出现提示,说明配置成功了。
也可以参看这里Eclipse安装STS插件使用更便捷的导入方式。
这里提供applicationContext.xml代码:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 快速入门 -->
<!-- IoC的配置 -->
<bean id="userDAO" class="dao.UserDAOImpl"></bean>
</beans>
在后续讲解中,将详细说明配置文件xml的编写。在这里可以先了解一下Bean元素基本属性。
2.通过Spring的工厂获取Bean完成相关操作,基本过程是:在程序中读取Spring配置文件,得到Spring的Bean工厂,通过Spring框架获得Bean,完成相应操作。
修改UserServiceImpl.java代码如下:
package service;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import dao.UserDAO;
public class UserServiceImpl implements UserService{
@Override
public void login() {
System.out.println("service层被调用了");
//传统方式
//UserDAO userDAO = new UserDAOImpl();
//IoC方式
//ApplicationContext认为就是Spring工厂
//ClassPathXmlApplicationContext:参数就是spring的核心配置文件的名字(我们写的那个xml),这个函数的作用就是自动寻找xml文件
ApplicationContext applicationContext =new ClassPathXmlApplicationContext("applicationContext.xml");
//获取bean对象(spring帮忙new的对象),参数就是我们写的bean的id
//applicationContext.getBean("userDAO");然后换对象格式
UserDAO userDAO=(UserDAO) applicationContext.getBean("userDAO");
//消除警告: - Resource leak: 'applicationContex' is never closed的处理
//导入包import org.springframework.context.ConfigurableApplicationContext;
//添加这条语句
((ConfigurableApplicationContext)applicationContext).close();
userDAO.findUserByUsernameAndPassword();
}
}
提示:出现new ClassPathXmlApplicationContext错误,是因为Eclipse默认为我们导入一个不符合当前创建容器所需的ApplicationContext 包,需要手动替换后解除错误。
默认导入的包:
import org.apache.catalina.core.ApplicationContext;
需要手动替换的包:
import org.springframework.context.ApplicationContext;
添加log4j.properties文件后(内容参考这里),进行测试。
2.4.2 DI依赖注入的实现
- 注入方式:set方法属性注入、构造器参数注入、接口注入 (后续再详细展开讲解);
- 注入类型:值类型(八大基本数据类型)、引用类型(String、自定义对象等)、复杂类型(Array、List、Set、Map、Properties)。
在spring_day1工程中,userService依赖于userDAO,也就说依赖对象是userDAO、注入对象userService,现在用set方法实现DI依赖注入,把bean对象userDAO注入给bean对象userService。
效果:SpringTest直接获取bean对象userService,而在userService这个bean里,已经注入好了userDAO。
过程:先在UserServiceImpl实体类中加一个setUserDAO(),在SpringTest中获取spring工程加工好的userService。然后通过配置文件,让Spring工厂实例化好一个bean对象userDAO、一个bean对象userService,同时,用属性把bean对象userDAO注入给bean对象userService,就结束了。
下面开始具体的演示。
修改业务层代码UserServiceImpl.java如下:
package service;
import dao.UserDAO;
public class UserServiceImpl implements UserService{
//这里写依赖对象的set方法
private UserDAO userDAO;
public void setUserDAO(UserDAO userDAO){
this.userDAO=userDAO;
}
@Override
public void login() {
System.out.println("service层被调用了");
//传统方式
//UserDAO userDAO = new UserDAOImpl();
//IoC方式
//ApplicationContext认为就是Spring工厂
//ClassPathXmlApplicationContext:参数就是spring的核心配置文件的名字(我们写的那个xml),这个函数的作用就是自动寻找xml文件
//ApplicationContext applicationContext =new ClassPathXmlApplicationContext("applicationContext.xml");
//获取bean对象(spring帮忙new的对象),参数就是我们写的bean的id
//applicationContext.getBean("userDAO");然后换对象格式
//UserDAO userDAO=(UserDAO) applicationContext.getBean("userDAO");
//消除警告: - Resource leak: 'applicationContex' is never closed的处理
//导入包import org.springframework.context.ConfigurableApplicationContext;
//添加这条语句
//((ConfigurableApplicationContext)applicationContext).close();
userDAO.findUserByUsernameAndPassword();
}
}
修改测试类SpringTest.java如下:
package test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import service.UserService;
public class SpringTest {
@Test
public void test(){
//传统创建
//UserService userService = new UserServiceImpl();
//IoC方式
//1.获取Spring工厂
ApplicationContext applicationContext =new ClassPathXmlApplicationContext("applicationContext.xml");
//2.获取工厂制造的bean对象
UserService userService = (UserService) applicationContext.getBean("userService");
//消除警告: - Resource leak: 'applicationContex' is never closed的处理
//导入包import org.springframework.context.ConfigurableApplicationContext;
//添加这条语句
((ConfigurableApplicationContext)applicationContext).close();
userService.login();
}
}
修改applicationContext.xml如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 快速入门 -->
<!-- IoC的配置 -->
<bean id="userDAO" class="dao.UserDAOImpl"></bean>
<!-- 将DAO传入Service -->
<!-- 将service也交给spring管理(new) -->
<bean id="userService" class="service.UserServiceImpl">
<!-- 依赖关系,在这里维护-->
<!-- userService依赖于userDAO,userService需要用到userDAO,那么创建一个userDAO的bean,并注入给userService的bean里
name:UserServiceImpl类中被注入对象的setter的名字:例如:setXxx,名字xxx(小写的)
ref:要注入的具体bean对象的引用,写被依赖的bean的名字
-->
<property name="userDAO" ref="userDAO"/>
</bean>
</beans>
进行测试。
2.5 Spring的工厂
前文我们用到的ApplicationContext,直译为应用上下文,它被用来加载Spring框架配置文件,构建Spring的工厂对象。它也称之为Spring容器的上下文对象、Spring的容器。
从图中可以看到,ApplicationContext 只是BeanFactory(Bean工厂,Bean就是一个java对象) 一个子接口,但由于ApplicationContext比它的顶层接口对象BeanFactory 更加强大, 所以现在开发基本没人使用它的顶层接口对象BeanFactory。
提示:后面还有个FactoryBean,注意区别。
Spring工厂有两种直接获取,见下图:
补充:bean的获取除了根据名称获取,也可以根据类型(具体类型、接口类型)获取,见下图: