控制反转_Spring:IOC 控制反转
Spring 概述
Spring 是什么
Spring 是分层的 Java SE/EE 应用 full-stack (全栈式) 轻量级开源框架。
全栈式:对各种主流技术和框架都进行了整合,同时对三层架构都提供解决方案。
轻量级和重量级的划分主要依据就是看它使用了多少服务,启动时需要加载的资源多少以及耦合度等等。
提供了表现层 Spring MVC 和持久层 Spring JDBC Template 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。
Spring 两大核心:
IOC - Inverse Of Control 控制反转:把对象的创建权交给 Spring
AOP - Aspect Oriented Programming 面向切面编程:在不修改源码的情况下,对方法进行增强
Spring 发展历程
EJB - Enterprise Java Beans:
1997 年,IBM 提出了 EJB 的思想
1998 年,SUN 制定开发标准规范 EJB 1.0
1999 年,EJB 1.1 发布
2001 年,EJB 2.0 发布
2003 年,EJB 2.1 发布
2006 年,EJB 3.0 发布
Spring:
Rod Johnson( Spring 之父)改变 Java 世界的大师级人物
2002 年编著《Expert one on one J2EE design and development》
指出了 Java EE 和 EJB 组件框架中的存在的一些主要缺陷;提出普通 java 类依赖注入更为简单的解决方案
2004 年编著《Expert one-on-one J2EE Development without EJB》阐述了 Java EE 开发时不使用 EJB 的解决方式(Spring 雏形),同年 4 月 spring 1.0 诞生
2006 年 10 月,发布 Spring 2.0
2009 年 12 月,发布 Spring 3.0
2013 年 12 月,发布 Spring 4.0
2017 年 9 月, 发布最新 Spring 5.0 通用版(GA)
Spring 优势
耦合:程序间的依赖关系
解耦:降低程序间的依赖关系;体现在编译期不依赖,运行期才依赖
JDBC 例子:
public class JDBCTest {
@Test public void test1() throws ClassNotFoundException, SQLException { // 1.注册驱动 // 存在编译期依赖:耦合重的体现 // DriverManager.registerDriver(new com.mysql.jdbc.Driver()); // 去掉 new 关键字:编译期不依赖,运行期才依赖。虽然解决了编译期依赖,但是仍存在硬编码问题 Class.forName("com.mysql.jdbc.Driver"); ... }}
解耦思路:配置文件 + 反射
Spring:
1)方便解耦,简化开发
Spring 就是一个容器,可以将所有对象创建和关系维护交给 Spring 管理
什么是耦合度?对象之间的关系,通常说当一个模块 (对象) 更改时也需要更改其他模块 (对象),这就是耦合,耦合度过高会使代码的维护成本增加。要尽量解耦
2)AOP 编程的支持
Spring 提供面向切面编程,方便实现程序进行权限拦截,运行监控等功能。
3)声明式事务的支持
通过配置完成事务的管理,无需手动编程
4)方便测试,降低 Java EE API 的使用
Spring 对 Junit 4 支持,可以使用注解测试
5)方便集成各种优秀框架
不排除各种优秀的开源框架,内部提供了对各种优秀框架的直接支持
Spring 体系结构
以下八大模块可根据需求引入项目使用:
Data Access/Integration
JDBC
ORM
OXM
JMS
Transactions
Web
WebSocket
Servlet
Web
Portlet
AOP
Aspects
Instrumentation
Messaging
Core Container - 相当于盖房子的地基
Beans
Core
Context
SpEL
Test
初识 IOC
概述
控制反转(Inverse Of Control)是一种设计思想,它的目的是指导我们设计出更加松耦合的程序。
控制:在 java 中指的是对象的控制权限(创建、销毁)。
反转:指的是对象控制权从由“开发者在类中手动控制”反转到由“ Spring 容器控制”。
例子:
传统方式 - 需要一个
userDao
实例,需要开发者自己手动创建new UserDao();
IOC 方式 - 需要一个
userDao
实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了spring 控制
自定义 IOC 容器
介绍
需求:实现 Service 层与 Dao 层代码解耦合
步骤分析:
创建 java 项目,导入自定义 IOC 相关坐标
编写 Dao 接口和实现类
编写 Service 接口和实现类
编写测试代码
实现
创建 java 项目,导入自定义 IOC 相关坐标
<?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.0modelVersion>
<groupId>com.rendagroupId> <artifactId>jdbc_springartifactId> <version>1.0-SNAPSHOTversion>
<properties> <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> <maven.compiler.encoding>UTF-8maven.compiler.encoding> <java.version>1.11java.version> <maven.compiler.source>1.11maven.compiler.source> <maven.compiler.target>1.11maven.compiler.target> properties>
<dependencies> <dependency> <groupId>dom4jgroupId> <artifactId>dom4jartifactId> <version>1.6.1version> dependency> <dependency> <groupId>jaxengroupId> <artifactId>jaxenartifactId> <version>1.1.6version> dependency> <dependency> <groupId>junitgroupId> <artifactId>junitartifactId> <version>4.12version> dependency> dependencies>
project>
编写 Dao 接口和实现类
public interface IUserDao { void save();}
public class UserDaoImpl implements IUserDao { @Override public void save() { System.out.println("UserDao: Successfully Saved."); }}
编写 Service 接口和实现类
public interface IUserService { void save();}
public class UserServiceImpl implements IUserService { private IUserDao userDao = new UserDaoImpl();
@Override public void save() { userDao.save(); }}
编写测试代码
public class SpringTest { @Test public void test1() { // 获取业务层对象 IUserService userService = new UserServiceImpl(); // 调用 save 方法 userService.save(); }}
问题:当前 service 对象和 Dao 对象耦合度太高,而且每次 new 的都是一个新的对象,导致服务器压力过大。
解耦合的原则是编译期不依赖,而运行期依赖就行了。
采用反射方式:
public class UserServiceImpl implements IUserService { private IUserDao userDao;
public UserServiceImpl() throws IllegalAccessException, ClassNotFoundException, InstantiationException { // 传统方式使用 new 方式,导致编译期依赖,耦合度太高 // userDao = new UserDaoImpl();
// 反射方式,存在硬编码问题 userDao = (IUserDao) Class.forName("com.renda.dao.impl.UserDaoImpl").newInstance(); }
@Override public void save() { userDao.save(); }}
测试代码:
@Testpublic void test1() throws IllegalAccessException, InstantiationException, ClassNotFoundException { IUserService userService = new UserServiceImpl(); userService.save();}
改造步骤分析:
准备一个配置文件
编写一个工厂工具类,工厂类使用 dom4j 来解析配置文件,获取到类的全路径
使用反射生成对应类的实例对象,存到 Map 中,这个 Map 就是 IOC 容器
为了解决反射方式的硬编码问题,先编写 beans.xml
配置文件
<beans>
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">bean>beans>
编写 BeanFactory
工具类
public class BeanFactory {
private static Map iocMap = new HashMap<>();// 程序启动时,初始化对象实例static {// 读取配置文件 InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");try {// 解析 xml(dom4j) SAXReader saxReader = new SAXReader(); Document document = saxReader.read(resourceAsStream);// 编写 xpath 表达式 String xpath = "//bean";// 获取到所有的 bean 标签 List list = document.selectNodes(xpath);// 遍历并使用反射创建对象实例,存到 map 集合(ioc 容器)中for (Element element : list) { String id = element.attributeValue("id"); String className = element.attributeValue("class");// 使用反射生成实例对象 Object o = Class.forName(className).newInstance();// 存到 map 中:key-id,value-o iocMap.put(id, o); } } catch (DocumentException | InstantiationException | IllegalAccessException | ClassNotFoundException e) { e.printStackTrace(); } }public static Object getBean(String beanId) {return iocMap.get(beanId); }}
修改 UserServiceImpl
实现类
public UserServiceImpl() throws IllegalAccessException, ClassNotFoundException, InstantiationException { // 传统方式使用 new 方式,导致编译期依赖,耦合度太高 // userDao = new UserDaoImpl();
// 反射方式,存在硬编码问题 // userDao = (IUserDao) Class.forName("com.renda.dao.impl.UserDaoImpl").newInstance();
// 反射方式 + 配置文件 userDao = (IUserDao) BeanFactory.getBean("userDao");}
小结
其实升级后的
BeanFactory
就是一个简单的 Spring 的 IOC 容器所具备的功能。之前需要一个
userDao
实例,开发者自己手动创建new UserDao();
现在需要一个
userDao
实例,直接从 spring 的 IOC 容器获得,对象的创建权交给了 spring 控制最终目标:代码解耦合
Spring 快速入门
介绍
需求:借助 spring 的 IOC 实现 service 层与 DAO 层代码解耦合
步骤分析:
创建 java 项目,导入 spring 开发基本坐标
编写 DAO 接口和实现类
创建 spring 核心配置文件
在 spring 配置文件中配置
UserDaoImpl
使用 spring 相关 API 获得 Bean 实例
实现
创建 java 项目,导入 spring 开发基本坐标
<dependencies> <dependency> <groupId>org.springframeworkgroupId> <artifactId>spring-contextartifactId> <version>5.1.5.RELEASEversion> dependency> <dependency> <groupId>junitgroupId> <artifactId>junitartifactId> <version>4.12version> dependency>dependencies>
编写 Dao
接口和实现类
public interface IUserDao { void save();}
public class UserDaoImpl implements IUserDao { @Override public void save() { System.out.println("UserDao: save..."); }}
创建 spring 核心配置文件 applicationContext.xml
,并在 spring 配置文件中配置 UserDaoImpl
Bean
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
beans>
使用 spring 相关 API 获得 Bean
实例
@Testpublic void test1(){ // 获取到了 spring 上下文对象,借助上下文对象可以获取到 IOC 容器中的 bean 对象 ,加载的同时就创建了 bean 对象存到容器中 // ApplicationContext XmlApplicationContext = new FileSystemXmlApplicationContext("D:\\gitee_repository\\stage-6-module-2\\code\\spring_quickstart\\src\\main\\resources\\applicationContext.xml"); ApplicationContext XmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
// 使用上下文对象从 IOC 容器中获取到了 bean 对象 // 方法一:根据 bean id 在容器中找对应的 bean 对象 // IUserDao userDao = (IUserDao) XmlApplicationContext.getBean("userDao"); // 方法二:根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。 // IUserDao userDao = XmlApplicationContext.getBean(IUserDao.class); // 方法三:根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。 IUserDao userDao = XmlApplicationContext.getBean("userDao", IUserDao.class);
// 调用方法 userDao.save();}
Spring 的开发步骤总结
导入坐标
创建
Bean
创建
applicationContext.xml
在配置文件中进行 Bean 配置
创建
ApplicationContext
对象,执行getBean
Spring 相关 API
API 继承体系介绍
Spring 的 API 体系异常庞大,现在只关注两个 BeanFactory 和 ApplicationContext
BeanFactory 是 ApplicationContext 的父接口,ApplicationContext 接口下又有 FileSystemXmlApplicationContext 和 ClassPathXmlApplicationContext 子接口
BeanFactory
BeanFactory 是 IOC 容器的核心接口,它定义了 IOC 的基本功能
特点:在第一次调用 getBean() 方法时,创建指定对象的实例
@Testpublic void test2(){ // 核心接口,不会创建 bean 对象存到容器中 BeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
// getBean 的时候才真正创建 bean 对象 IUserDao userDao = (IUserDao) xmlBeanFactory.getBean("userDao");
// 调用方法 userDao.save();}
ApplicationContext
代表应用上下文对象,可以获得 spring 中 IOC 容器的 Bean 对象
特点:在 spring 容器启动时,加载并创建所有对象的实例
常用实现类:
ClassPathXmlApplicationContext
- 它是从类的根路径下加载配置文件,推荐使用这种。FileSystemXmlApplicationContext
- 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。AnnotationConfigApplicationContext
- 当使用注解配置容器对象时,需要使用此类来创建 spring 容器,它用来读取注解。
常用方法:
Object getBean(String name);
- 根据 Bean 的 id 从容器中获得 Bean 实例,返回是 Object,需要强转。T getBean(ClassrequiredType);
- 根据类型从容器中匹配 Bean 实例,当容器中相同类型的 Bean 有多个时,则此方法会报错。T getBean(String name,ClassrequiredType);
- 根据 Bean 的 id 和类型获得 Bean 实例,解决容器中相同类型 Bean 有多个情况。
Spring 配置文件
Bean 标签基本配置
用于配置对象交由 Spring 来创建。
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
<bean id="" class=""/>
Bean 标签范围配置
<bean id="" class="" scope="">bean>
scope
属性指对象的作用范围,取值如下:
singleton
- 默认值,单例的prototype
- 多例的request
- WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中session
- WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中global session
- WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么 global Session 相当于 session
测试 scope 属性:
@Testpublic void test3(){ ApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao_1 = (IUserDao) classPathXmlApplicationContext.getBean("userDao"); IUserDao userDao_2 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");
System.out.println(userDao_1); System.out.println(userDao_2);}
当 scope 的取值为 singleton 时:
- Bean 的实例化个数:1 个- Bean 的实例化时机:当 Spring 核心文件被加载时,实例化配置的 Bean 实例- Bean 的生命周期: + 对象创建:当应用加载,创建容器时,对象就被创建了 + 对象运行:只要容器在,对象一直活着 + 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
当 scope 的取值为 prototype 时:
- Bean 的实例化个数:多个- Bean 的实例化时机:当调用 getBean() 方法时实例化 Bean- Bean 的生命周期: + 对象创建:当使用对象时,创建新的对象实例 + 对象运行:只要对象在使用中,就一直活着 + 对象销毁:当对象长时间不用时,被 Java 的垃圾回收器回收了
Bean 生命周期配置
applicationContext.xml
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl" init-method="init" destroy-method="destroy"/>
init-method
:指定类中的初始化方法名称destroy-method
:指定类中销毁方法名称
public class UserDaoImpl implements IUserDao { public void init() { System.out.println("UserDao: Initialize..."); }
public void destroy() { System.out.println("UserDao: Destroy"); }
...}
测试代码
@Testpublic void test4(){ ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
IUserDao userDao_1 = (IUserDao) classPathXmlApplicationContext.getBean("userDao"); IUserDao userDao_2 = (IUserDao) classPathXmlApplicationContext.getBean("userDao");
System.out.println(userDao_1); System.out.println(userDao_2);
// 触发 destroy 方法 classPathXmlApplicationContext.close();}
Bean 实例化三种方式
无参构造方法实例化
它会根据默认无参构造方法来创建类对象,如果 bean 中没有默认无参构造函数,将会创建失败
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/>
工厂静态方法实例化
应用场景:依赖的 jar 包中有个 A 类,A 类中有个静态方法 m1,m1 方法的返回值是一个 B 对象。如果频繁使用 B 对象,此时可以将 B 对象的创建权交给 spring 的 IOC 容器,以后在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。
public class StaticFactoryBean { public static IUserDao createUserDao(){ return new UserDaoImpl(); }}
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.renda.factory.StaticFactoryBean" factory-method="createUserDao"/>
beans>
工厂普通方法实例化
应用场景:依赖的 jar 包中有个 A 类,A 类中有个普通方法 m1,m1 方法的返回值是一个 B 对象。如果我们频繁使用 B 对象,此时我们可以将 B 对象的创建权交给 spring 的 IOC 容器,以后我们在使用 B 对象时,无需调用 A 类中的 m1 方法,直接从 IOC 容器获得。
public class DynamicFactoryBean { public IUserDao createUserDao(){ return new UserDaoImpl(); }}
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dynamicFactoryBean" class="com.renda.factory.DynamicFactoryBean"/> <bean id="userDao" factory-bean="dynamicFactoryBean" factory-method="createUserDao"/>
beans>
Bean 依赖注入方式
依赖注入 DI(Dependency Injection):它是 Spring 框架核心 IOC 的具体实现。
在编写程序时,通过控制反转,把对象的创建交给了 Spring,但是代码中不可能出现没有依赖的情况。IOC 解耦只是降低他们的依赖关系,但不会消除。例如:业务层仍会调用持久层的方法。
这种业务层和持久层的依赖关系,在使用 Spring 之后,就让 Spring 来维护了。简单的说,就是通过框架把持久层对象传入业务层,而不用手动去获取。
Bean 依赖注入方式
构造方法
在 UserServiceImpl
中创建有参构造
public class UserServiceImpl implements IUserService { IUserDao userDao;
public UserServiceImpl(IUserDao userDao) { this.userDao = userDao; }
public UserServiceImpl() { }
@Override public void save() { // 调用 dao 层的 save 方法 userDao.save(); }}
配置 Spring 容器调用有参构造时进行注入
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/><bean id="userService" class="com.renda.service.impl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>bean>
`set` 方法
在 UserServiceImpl
中创建 set
方法
public class UserServiceImpl implements IUserService { IUserDao userDao;
public UserServiceImpl(IUserDao userDao) { this.userDao = userDao; }
public UserServiceImpl() { }
@Override public void save() { // 调用 dao 层的 save 方法 userDao.save(); }
public void setUserDao(IUserDao userDao) { this.userDao = userDao; }}
配置 Spring 容器调用 set
方法进行注入
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/><bean id="userService" class="com.renda.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao">property>bean>
P 命名空间注入
P 命名空间注入本质也是 set
方法注入,但比起上述的 set
方法注入更加方便,主要体现在配置文件 applicationContext.xml
中。
首先,需要引入 P 命名空间:
xmlns:p="http://www.springframework.org/schema/p"
其次,需要修改注入方式:
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"/><bean id="userService" class="com.renda.service.impl.UserServiceImpl" p:userDao-ref="userDao"/>
Bean 依赖注入的数据类型
上面操作,都是注入 Bean 对象,除了对象的引用可以注入,普通数据类型和集合都可以在容器中进行注入。
注入数据的三种数据类型:
普通数据类型
引用数据类型
集合数据类型
之前的操作都是对 UserDao 对象的引用进行注入的,属于引用数据类型注入。下面将以 set
方法注入为例,演示普通数据类型和集合数据类型的注入。
注入普通数据类型
public class UserDaoImpl implements IUserDao { private String username; private Integer age;
public void setUsername(String username) { this.username = username; }
public void setAge(Integer age) { this.age = age;
@Override public void save() { System.out.println(username); System.out.println(age); System.out.println("UserDao: save..."); }}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl">
<property name="username" value="张人大"/> <property name="age" value="25"/>bean>
注入集合数据类型
List 集合注入
public class User { private String username; private Integer age;
public void setUsername(String username) { this.username = username; }
public void setAge(Integer age) { this.age = age; }}
public class UserDaoImpl implements IUserDao { private List list;public void setList(List list) {this.list = list; }@Overridepublic void save() { System.out.println("List:" + list); }}
<bean id="user" class="com.renda.domain.User"> <property name="username" value="布莱尔"/> <property name="age" value="18"/>bean>
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"> <property name="list"> <list> <value>aaavalue> <ref bean="user"/> list> property>bean>
Set 集合注入
public class UserDaoImpl implements IUserDao { private Set set;public void setSet(Set set) {this.set = set; }@Overridepublic void save() { System.out.println("Set:" + set); }}
<bean id="user" class="com.renda.domain.User"> <property name="username" value="布莱尔"/> <property name="age" value="18"/>bean>
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"> <property name="set"> <set> <value>bbbvalue> <ref bean="user"/> set> property>bean>
Array 数组注入
public class UserDaoImpl implements IUserDao { private Object[] array;
public void setArray(Object[] array) { this.array = array; }
@Override public void save() { System.out.println("Array:" + Arrays.toString(array)); }}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"> <property name="array"> <array> <value>cccvalue> <ref bean="user"/> array> property>bean>
Map 集合注入
public class UserDaoImpl implements IUserDao { private Map map;public void setMap(Map map) {this.map = map; }@Overridepublic void save() { System.out.println("Map:" + map); }}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"> <property name="map"> <map> <entry key="k1" value="ddd"/> <entry key="k2" value-ref="user"/> map> property>bean>
Properties 配置注入
public class UserDaoImpl implements IUserDao { private Properties properties;
public void setProperties(Properties properties) { this.properties = properties; }
@Override public void save() { System.out.println("Properties:" + properties); }}
<bean id="userDao" class="com.renda.dao.impl.UserDaoImpl"> <property name="properties"> <props> <prop key="k1">v1prop> <prop key="k2">v2prop> <prop key="k3">v3prop> props> property>bean>
配置文件模块化
实际开发中,Spring 的配置内容非常多,这就导致 Spring 配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,也就是所谓的配置文件模块化。
并列的多个配置文件:
ApplicationContext ac = new ClassPathXmlApplicationContext("beans1.xml", "beans2.xml", "...");
主从配置文件:
<import resource="applicationContext-xxx.xml"/>
注意:
同一个 xml
中不能出现相同名称的 bean
,如果出现会报错。
多个 xml
如果出现相同名称的 bean
,不会报错,但是后加载的会覆盖前加载的 bean
。
小结
Spring 的重点配置
<bean> 标签:创建对象并放到 spring 的 IOC 容器 id 属性: 在容器中 Bean 实例的唯一标识,不允许重复 class 属性: 要实例化的 Bean 的全限定名 scope 属性: Bean 的作用范围,常用是 Singleton (默认) 和 prototype
<constructor-arg> 标签:属性注入 name 属性:属性名称 value 属性:注入的普通属性值 ref 属性:注入的对象引用值
<property> 标签:属性注入 name 属性:属性名称 value 属性:注入的普通属性值 ref 属性:注入的对象引用值 <list> <set> <array> <map> <properties>
<import> 标签: 导入其他的 Spring 的分文件
IOC 实战 - DbUtils
DbUtils 是什么?
DbUtils 是 Apache 的一款用于简化 Dao 代码的工具类,它底层封装了 JDBC 技术。
核心对象:
QueryRunner queryRunner = new QueryRunner(DataSource dataSource);
核心方法:
int update()
- 执行增、删、改语句T query()
- 执行查询语句ResultSetHandler
- 这是一个接口,主要作用是将数据库返回的记录封装到实体对象
查询数据库所有账户信息到 Account
实体中:
public class DbUtilsTest { @Test public void findAllTest() throws Exception { // 创建 DBUtils 工具类,传入连接池 QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource()); // 编写 sql String sql = "select * from account"; // 执行 sql List list = queryRunner.query(sql, new BeanListHandler (Account.class));// 打印结果for (Account account : list) { System.out.println(account); } }}
Spring 的 `xml` 整合 `DbUtils`
介绍
需求:基于 Spring 的 xml
配置实现账户的 CRUD 案例
步骤分析:
准备数据库环境
创建 java 项目,导入坐标
编写 Account 实体类
编写 AccountDao 接口和实现类
编写 AccountService 接口和实现类
编写 spring 核心配置文件
编写测试代码
实现
准备数据库环境
CREATE DATABASE `spring_db`;USE `spring_db`;CREATE TABLE `account` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL, `money` double DEFAULT NULL, PRIMARY KEY (`id`));insert into `account`(`id`,`name`,`money`) values (1,'tom',1000),(2,'jerry',1000);
创建 java 项目,导入坐标
<?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.0modelVersion>
<groupId>com.rendagroupId> <artifactId>spring_dbutilsartifactId> <version>1.0-SNAPSHOTversion>
<properties> <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> <maven.compiler.encoding>UTF-8maven.compiler.encoding> <java.version>1.11java.version> <maven.compiler.source>1.11maven.compiler.source> <maven.compiler.target>1.11maven.compiler.target> properties>
<dependencies> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> <version>5.1.47version> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>druidartifactId> <version>1.1.9version> dependency> <dependency> <groupId>commons-dbutilsgroupId> <artifactId>commons-dbutilsartifactId> <version>1.6version> dependency> <dependency> <groupId>org.springframeworkgroupId> <artifactId>spring-contextartifactId> <version>5.1.5.RELEASEversion> dependency> <dependency> <groupId>junitgroupId> <artifactId>junitartifactId> <version>4.12version> dependency>
<dependency> <groupId>javax.annotationgroupId> <artifactId>javax.annotation-apiartifactId> <version>1.3.2version> dependency> dependencies>project>
编写 Account
实体类
public class Account {
private Integer id; private String name; private Double money;
@Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; }
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Double getMoney() { return money; }
public void setMoney(Double money) { this.money = money; }}
编写 AccountDao
接口和实现类
public interface AccountDao {
List findAll();
Account findById(Integer id);
void save(Account account);
void update(Account account);
void delete(Integer id);
}
public class AccountDaoImpl implements AccountDao { private QueryRunner queryRunner;
public void setQueryRunner(QueryRunner queryRunner) { this.queryRunner = queryRunner; }
@Override public List findAll() { List list = null;// 编写 sql String sql = "select * from account";try {// 执行 sql list = queryRunner.query(sql, new BeanListHandler(Account.class)); } catch (SQLException e) { e.printStackTrace(); }return list; }@Overridepublic Account findById(Integer id) { Account query = null;// 编写 sql String sql = "select * from account where id = ?";try { query = queryRunner.query(sql, new BeanHandler(Account.class), id); } catch (SQLException e) { e.printStackTrace(); }return query; }@Overridepublic void save(Account account) { String sql = "insert into account values(null, ?, ?)";try { queryRunner.update(sql, account.getName(), account.getMoney()); } catch (SQLException e) { e.printStackTrace(); } }@Overridepublic void update(Account account) { String sql = "update `account` set `name` = ?, `money` = ? where `id` = ?";try { queryRunner.update(sql, account.getName(), account.getMoney(), account.getId()); } catch (SQLException e) { e.printStackTrace(); } }@Overridepublic void delete(Integer id) { String sql = "delete from `account` where `id` = ?";try { queryRunner.update(sql, id); } catch (SQLException e) { e.printStackTrace(); } }}
编写 AccountService 接口和实现类
public interface AccountService { List findAll();
Account findById(Integer id);
void save(Account account);
void update(Account account);
void delete(Integer id);}
public class AccountServiceImpl implements AccountService { private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; }
@Override public List findAll() { return accountDao.findAll(); }
@Override public Account findById(Integer id) { return accountDao.findById(id); }
@Override public void save(Account account) { accountDao.save(account); }
@Override public void update(Account account) { accountDao.update(account); }
@Override public void delete(Integer id) { accountDao.delete(id); }}
编写 spring 核心配置文件
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///spring_db?characterEncoding=utf8&useSSL=false"/> <property name="username" value="root"/> <property name="password" value="password"/> bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"> <constructor-arg name="ds" ref="dataSource"/> bean>
<bean id="accountDao" class="com.renda.dao.impl.AccountDaoImpl"> <property name="queryRunner" ref="queryRunner"/> bean>
<bean id="accountService" class="com.renda.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> bean>
beans>
编写测试代码
public class AccountServiceTest {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = (AccountService) applicationContext.getBean("accountService");
// 测试添加 @Test public void testSave(){ Account account = new Account(); account.setName("renda"); account.setMoney(888d); accountService.save(account); }
// 测试查询 @Test public void testFindById(){ Account account = accountService.findById(3); System.out.println(account); }
// 测试查询所有 @Test public void testFindAll(){ List all = accountService.findAll();for (Account account : all) { System.out.println(account); } }// 测试更新@Testpublic void testUpdate(){ Account account = new Account(); account.setId(3); account.setName("Blair"); account.setMoney(2000d); accountService.update(account); }// 测试删除@Testpublic void testDelete(){ accountService.delete(3); }}
抽取 JDBC 配置文件
applicationContext.xml
加载 jdbc.properties
配置文件获得连接信息。
首先,需要引入 context
命名空间和约束路径:
* 命名空间: xmlns:context="http://www.springframework.org/schema/context"* 约束路径: http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/>bean>
小结
* DataSource 的创建权交由 Spring 容器去完成* QueryRunner 的创建权交由 Spring 容器去完成,使用构造方法传递 DataSource* Spring 容器加载 properties 文件 <context:property-placeholder location="xx.properties"/><property name="" value="${key}"/>
Spring 注解开发
Spring 是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替 xml
配置文件可以简化配置,提高开发效率。
Spring 常用注解
介绍
Spring 常用注解主要是替代 的配置
@Component
- 使用在类上用于实例化 Bean@Controller
- 使用在 web 层类上用于实例化 Bean@Service
- 使用在 service 层类上用于实例化 Bean@Repository
- 使用在 dao 层类上用于实例化 Bean@Autowired
- 使用在字段上用于根据类型依赖注入;当使用注解注入属性时,set 方法可以省略@Qualifier
- 结合@Autowired
一起使用,根据名称进行依赖注入;在自动按照类型注入基础之上,再按照 Bean 的 id 注入;它给字段注入时必须和@Autowired
一起使用,但是给方法参数注入时可以独立使用@Resource
- 相当于@Autowired
+@Qualifier
,按照名称进行注入@Value
- 注入普通属性@Scope
- 标注 Bean 的作用范围@PostConstruct
- 使用在方法上标注该方法是 Bean 的初始化方法@PreDestroy
- 使用在方法上标注该方法是 Bean 的销毁方法
说明:JDK 11 以后完全移除了 javax
扩展导致不能使用 @resource
注解,如果要使用它需要在 Maven 引入依赖:
<dependency> <groupId>javax.annotationgroupId> <artifactId>javax.annotation-apiartifactId> <version>1.3.2version>dependency>
注意:使用注解进行开发时,需要在 applicationContext.xml
中配置组件扫描,作用是指定哪个包及其子包下的 Bean 需要进行扫描以便识别使用注解配置的类、字段和方法。
<context:component-scan base-package="com.renda">context:component-scan>
实现
Bean 实例化(IOC)
<bean id="userDao1" class="com.renda.dao.impl.UserDaoImpl">bean>
使用 @Component
或 @Repository
标识 UserDaoImpl
需要 Spring 进行实例化。
// @Component(value = "userDao")@Repository // 如果没有写 value 属性值,Bean 的 id 为:类名首字母小写public class UserDaoImpl implements UserDao { ...}
属性依赖注入(DI)
<bean id="userService" class="com.lagou.service.impl.UserServiceImpl"> <property name="userDao" ref="userDao1"/>bean>
使用 @Autowired
或者 @Autowired
+ @Qulifier
或者 @Resource
进行 userDao
的注入
@Servicepublic class UserServiceImpl implements UserService { @Autowired // @Qualifier("userDao1") // @Resource(name = "userDao1") private UserDao userDao;
public void setUserDao(UserDao userDao) { this.uDao = userDao; }}
`@Value`
使用 @Value
进行字符串的注入,结合 SpEL (Spring Expression Language)
表达式获得配置参数
@Servicepublic class UserServiceImpl implements UserService { @Value("注入普通数据") private String str;
@Value("${jdbc.driver}") private String driver;}
`@Scope`
<bean scope=""/>
使用 @Scope
标注 Bean 的范围
@Service@Scope("singleton")public class UserServiceImpl implements UserService { ...}
`Bean` 生命周期
<bean init-method="init" destroy-method="destory" />
使用 @PostConstruct
标注初始化方法,使用 @PreDestroy
标注销毁方法
@PostConstructpublic void init(){ System.out.println("初始化方法....");}
@PreDestroypublic void destroy(){ System.out.println("销毁方法.....");}
Spring 常用注解整合 `DbUtils`
步骤分析:
拷贝
xml
配置项目,改为注解配置项目修改
AccountDaoImpl
实现类修改
AccountServiceImpl
实现类修改 Spring 核心配置文件
编写测试代码
修改 `AccountDaoImpl` 实现类
@Repositorypublic class AccountDaoImpl implements AccountDao { @Autowired private QueryRunner queryRunner; ...}
修改 `AccountServiceImpl` 实现类
@Service(value = "accountService")public class AccountServiceImpl implements AccountService { @Autowired private AccountDao accountDao; ...}
修改 spring 核心配置文件
<?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.renda"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> bean>
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"> <constructor-arg name="ds" ref="dataSource"/> bean>
beans>
Spring 新注解
使用上面的注解还不能全部替代 xml 配置文件,还需要使用注解替代的配置如下:
非自定义的 Bean 的配置:<bean>加载 properties 文件的配置:<context:property-placeholder>组件扫描的配置:<context:component-scan>引入其他文件:<import>
@Configuration
- 用于指定当前类是一个 Spring 配置类,当创建容器时会从该类上加载注解@Bean
- 使用在方法上,标注将该方法的返回值存储到 Spring 容器中@PropertySource
- 用于加载 properties 文件中的配置@ComponentScan
- 用于指定 Spring 在初始化容器时要扫描的包@Import
- 用于导入其他配置类
Spring 纯注解整合 `DbUtils`
步骤分析:
编写 Spring 核心配置类
编写数据库配置信息类
编写测试代码
编写 Spring 核心配置类
@Configuration@ComponentScan("com.renda")@Import(DataSourceConfig.class)public class SpringConfig { @Bean("queryRunner") public QueryRunner getQueryRunner(@Autowired DataSource dataSource){ return new QueryRunner(dataSource); }}
编写数据库配置信息类
@PropertySource("classpath:jdbc.properties")public class DataSourceConfig { @Value("${jdbc.driverClassName}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password;
@Bean("dataSource") public DataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driver); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); return druidDataSource; }}
编写测试代码
public class AccountServiceTest {
/* ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("com/renda/service/applicationContext.xml"); AccountService accountService = (AccountService) applicationContext.getBean("accountService"); */
// 当前改成了纯注解形式 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class); AccountService accountService = (AccountService) applicationContext.getBean("accountService");
...}
Spring 整合 Junit
普通 Junit 测试问题
在普通的测试类中,需要开发者手动加载配置文件并创建 Spring 容器,然后通过 Spring 相关 API 获得 Bean 实例;如果不这么做,那么无法从容器中获得对象。
开发者可以让 Spring Junit 负责创建 Spring 容器来简化这个操作,直接在测试类注入 Bean 实例;但是需要将配置文件的名称告诉它。
Spring 整合 Junit
步骤分析:
导入 spring 集成 Junit 的坐标
使用
@Runwith
注解替换原来的运行器使用
@ContextConfiguration
指定配置文件或配置类使用
@Autowired
注入需要测试的对象创建测试方法进行测试
导入 spring 集成 Junit 的坐标
<dependency> <groupId>org.springframeworkgroupId> <artifactId>spring-testartifactId> <version>5.1.5.RELEASEversion>dependency><dependency> <groupId>junitgroupId> <artifactId>junitartifactId> <version>4.12version>dependency>
使用 `@Runwith` 注解替换原来的运行器
@RunWith(SpringJUnit4ClassRunner.class)public class AccountServiceTest { ...}
使用 `@ContextConfiguration` 指定配置文件或配置类
@RunWith(SpringJUnit4ClassRunner.class)// @ContextConfiguration(value = {"classpath:applicationContext.xml"})@ContextConfiguration(classes = {SpringConfig.class})public class AccountServiceTest { ... }
使用 `@Autowired` 注入需要测试的对象
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = {SpringConfig.class})public class AccountServiceTest { @Autowired private AccountService accountService; ... }
控制反转_Spring:IOC 控制反转相关推荐
- 控制反转(ioc)和 面向切面(AOP)
1. IOC(Inversion of control)不是技术,是一种编程思想.ioc意味着将设计好的对象交给容器控制,而不是传统的在对象内部直接控制. 2. IOC(控制反转) '谁控制了谁 控 ...
- 控制反转(IOC)模式
依赖倒置:依赖抽象,而不依赖具体实现. 控制反转(Inversion of Control):提倡实现松耦合层.组件和类的设计原则,颠倒程序的控制流程.IoC使用分离执行特定问题处理代码的概念: Io ...
- Spring的控制反转(IOC)和依赖注入(DI)具体解释
Spring的控制反转(IOC)和依赖注入(DI)具体解释 首先介绍下(IOC)控制反转: 所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的.这样控制器就有应 ...
- Spring IOC(控制反转)详解及示例
控制反转--Spring通过一种称作控制反转(IOC)的技术促进了低耦合.当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象.你可以认为IoC与JN ...
- 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 创建 事件监听器 对应的 动态代理 | 动态代理的数据准备 | 创建调用处理程序 | 创建动态代理实例对象 )
文章目录 前言 一.创建 事件监听器 对应的 动态代理 二.动态代理 数据准备 三.动态代理 调用处理程序 四.动态代理 实例对象创建 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...
- 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取要注入事件的 View 对象 | 通过反射获取 View 组件的事件设置方法 )
文章目录 前言 一.获取要注入事件的 View 对象 二.通过反射获取 View 组件的事件设置方法并执行 前言 Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , ...
- 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取 Activity 中的所有方法 | 获取方法上的注解 | 获取注解上的注解 | 通过注解属性获取事件信息 )
文章目录 前言 一.获取 Activity 中的所有方法 二.获取方法上的注解 三.获取注解上的注解 四.通过注解属性获取相关事件信息 前言 Android 依赖注入的核心就是通过反射获取 类 / 方 ...
- 【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )
文章目录 总结 一.Android 事件依赖注入示例 1.创建依赖注入库 2.声明注解 (1).修饰注解的注解 (2).修饰方法的注解 3.Activity 基类 4.动态代理类调用处理程序 5.依赖 ...
- 【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )
文章目录 总结 一.Android 视图依赖注入步骤 二.Android 布局依赖注入示例 1.创建依赖注入库 2.声明注解 3.Activity 基类 4.依赖注入工具类 5.客户端 Activit ...
- 小菜学习设计模式(五)—控制反转(Ioc)
写在前面 设计模式目录: 小菜学习设计模式(一)-模板方法(Template)模式 小菜学习设计模式(二)-单例(Singleton)模式 小菜学习设计模式(三)-工厂方法(Factory Metho ...
最新文章
- 造句简单_零基础也能说一口流利英语,用简单的you are造句学英语
- 网站内容页面如何优化才利于排名提升?
- Json反序列化与Java泛型
- boost::hana模块使用 Hana 实现基本维度分析的示例
- Oracle脚本笔记
- python使用print不换行
- 获取当前周和前一周周一和周天,下一周周一和周天
- Confluence 6 配置 Office 转换器
- 会员制营销系统_想提升门店经营水平?会员制营销法可以帮到你
- Mac OS开启黑暗模式
- 奶块最新服务器叫什么,奶块5.4.0版本更新公告
- c++ pdflib 生成中文内容
- android 歌曲的流派信息,音乐流派分类介绍.doc
- 2019python二级真题_2019年3月二级python真题,上岸必备!
- 【NEW02】Servlet 基础
- 有一天,你不上班后,打算干什么?
- 语音信号处理(一):对声母和韵母进行录音并时域分析
- 【信息安全案例】——身份与访问安全(学习笔记)
- Tkinter 的text使用方法
- iOS 通过github自动打包ipa
热门文章
- react学习(72)--row上面加样式
- react学习(54)--注意传递请求
- 前端学习(3048):vue+element今日头条管理-分页布局
- [html] 移动端如何禁止用户手动缩放页面?
- 25利他行为可以学习和模仿吗
- [jQuery] 你知道自定义事件吗?jQuery里的fire函数是什么意思,什么时候用?
- [css] 写出你知道的CSS水平和垂直居中的方法
- 前端学习(2667):退出编辑状态
- 前端学习(2531):Vuex中getter
- “约见”面试官系列之常见面试题之第七十四篇之v-if和v-for优先级(建议收藏)