Spring介绍及IOC
一、Spring
官网 : http://spring.io/
官方下载地址 : https://repo.spring.io/libs-release-local/org/springframework/spring/
GitHub : https://github.com/spring-projects
1. Spring概述
Spring是一个支持快速开发Java EE应用程序的框架。它提供了一系列底层容器和基础设施,并可以和大量常用的开源框架无缝集成,可以说是开发Java EE应用程序的必备。
Spring最早是由Rod Johnson这哥们在他的《Expert One-on-One J2EE Development without EJB》一书中提出的用来取代EJB的轻量级框架。随后这哥们又开始专心开发这个基础框架,并起名为Spring Framework。
随着Spring越来越受欢迎,在Spring Framework基础上,又诞生了Spring Boot、Spring Cloud、Spring Data、Spring Security等一系列基于Spring Framework的项目。
简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术
2. spring模块组成(了解)
Spring 框架是一个分层架构,由 7 个定义良好的模块组成。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
- 核心容器:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
- Spring 上下文:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
- Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
- Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
- Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
- Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
- Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。
3. Spring优点
方便解耦,简化开发:
- Spring就是一个大工厂,专门负责生成Bean,可以将所有对象创建和依赖关系维护由Spring管理
AOP编程的支持:
- Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能
声明式事务的支持:
- 只需要通过配置就可以完成对事务的管理,而无需手动编程
方便程序的测试:
- Spring对Junit4支持,可以通过注解方便的测试Spring程序
方便集成各种优秀框架:
- Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的支持
降低JavaEE API的使用难度Spring:
- 对JavaEE开发中一些难用的API(JDBC、JavaMail、远程调webservice用等),都提供了封装,使这些API应用难度大大降低
二、IOC容器
在学习Spring框架时,我们遇到的第一个也是最核心的概念就是容器。
什么是容器?容器是一种为某种特定组件的运行提供必要支持的一个软件环境。例如,Tomcat就是一个Servlet容器,它可以为Servlet的运行提供运行环境。类似Docker这样的软件也是一个容器,它提供了必要的Linux环境以便运行一个特定的Linux进程。
通常来说,使用容器运行组件,除了提供一个组件运行环境之外,容器还提供了许多底层服务。例如,Servlet容器底层实现了TCP连接,解析HTTP协议等非常复杂的服务,如果没有容器来提供这些服务,我们就无法编写像Servlet这样代码简单,功能强大的组件。早期的JavaEE服务器提供的EJB容器最重要的功能就是通过声明式事务服务,使得EJB组件的开发人员不必自己编写冗长的事务处理代码,所以极大地简化了事务处理。
Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。
1. IOC原理
Spring提供的容器又称为IoC容器,什么是IoC?
IoC全称Inversion of Control,直译为控制反转。那么何谓IoC?在理解IoC之前,我们先看看通常的Java组件是如何协作的。
- 创建一个UserDao接口
public interface UserDao {
/**
*功能描述: 获取用户信息
* @author jiaoqianjin
* @date 2021/3/12
*/
void getUser();
}
- 创建Dao的实现类
public class UserDaoImpl implements UserDao {
public void getUser() {
System.out.println("获取用户数据");
}
}
- 编写UserService的接口
public interface UserService {
/**
*功能描述:获取用户信息
* @author jiaoqianjin
* @date 2021/3/12
*/
void getUser();
}
- 编写Service的实现类
public class UserServiceImpl implements UserService {
UserMysqlImpl userDao = new UserMysqlImpl();
/**
* 功能描述:获取用户信息
*
* @author jiaoqianjin
* @date 2021/3/12
*/
public void getUser() {
userDao.getUser();
}
}
- 测试
public class UserTest {
@Test
public void testUser(){
UserService service = new UserServiceImpl();
service.getUser();
}
}
上述的逻辑是正常的一个获取一个用户信息的流程,现在我们修改一下,再去根据UserDao接口实现一个类,返回不同的信息
public class UserMysqlImpl implements UserDao {
public void getUser() {
System.out.println("从mysql中获取数据");
}
}
紧接着我们要去使用MySql的话 , 我们就需要去service实现类里面修改对应的实现
public class UserServiceImpl implements UserService {
// UserDaoImpl userDao = new UserDaoImpl();
UserMysqlImpl userDao = new UserMysqlImpl();
/**
* 功能描述:获取用户信息
*
* @author jiaoqianjin
* @date 2021/3/12
*/
public void getUser() {
userDao.getUser();
}
}
在假设, 我们再增加一个Userdao的实现类
public class UserDaoOracleImpl implements UserDao {
/**
* 功能描述: 获取用户信息
*
* @author jiaoqianjin
* @date 2021/3/12
*/
public void getUser() {
System.out.println("从Oracle获取数据");
}
}
那么我们要使用Oracle , 又需要去service实现类里面修改对应的实现 . 假设我们的这种需求非常大 , 这种方式就根本不适用了
从上面的例子可以看出,如果一个系统有大量的组件,其生命周期和相互之间的依赖关系如果由组件自身来维护,不但大大增加了系统的复杂度,而且会导致组件之间极为紧密的耦合,继而给测试和维护带来了极大的困难。
解决这一问题的核心方案就是IoC。
传统的应用程序中,控制权在程序本身,程序的控制流程完全由开发者控制,例如:
UserServiceImpl
创建了UserMysqlImpl
组件,使用Oracle实现的时候又需要创建UserDaoOracleImpl
组件,这种模式的缺点是,一个组件如果要使用另一个组件,必须先知道如何正确地创建它。
如何解决?
例如,UserServiceImpl
自己并不会创建userDao,而是等待外部通过setUserDao()
方法来注入一个userDao
:
我们可以在需要用到它的地方 , 不去实现它 , 而是留出一个接口 , 利用set , 我们去代码里修改下
public class UserServiceImpl implements UserService {
// UserDaoImpl userDao = new UserDaoImpl();
// UserMysqlImpl userDao = new UserMysqlImpl();
private UserDao userDao;
/**
* 功能描述: 利用set注入
*
* @param userDao 接口实现类
* @author jiaoqianjin
* @date 2021/3/12
*/
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
/**
* 功能描述:获取用户信息
*
* @author jiaoqianjin
* @date 2021/3/12
*/
public void getUser() {
userDao.getUser();
}
}
测试
@Test
public void testUser1() {
UserServiceImpl service = new UserServiceImpl();
// 使用mysql实现
service.setUserDao(new UserMysqlImpl());
service.getUser();
//那我们现在又想用Oracle去实现呢
service.setUserDao(new UserDaoOracleImpl());
service.getUser();
}
不直接new
一个UserDao
,而是注入一个UserDao
,这个小小的改动虽然简单,却带来了一系列好处:
以前所有东西都是由程序去进行控制创建 , 而现在是由我们自行控制创建对象 , 把主动权交给了调用者 . 程序不用去管怎么创建,怎么实现了 . 它只负责提供一个接口 .
这种思想 , 从本质上解决了问题 , 我们程序员不再去管理对象的创建了 , 更多的去关注业务的实现 . 耦合性大大降低 . 这也就是IOC的原型 !
三、Spring入门
1. 入门案例
1. 导入Maven依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.10.RELEASE</version>
</dependency>
2. 编写一个User实体类
public class User {
private Integer id;
private String name;
private String phone;
public void show(){
System.out.println("Hello,"+ name );
}
}
3. 编写一个Spring文件
beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--bean就是java对象 , 由Spring创建和管理-->
<bean id="user" class="com.shida.entity.User">
<property name="name" value="焦前进"/>
</bean>
</beans>
4. 测试
@Test
public void test(){
//解析beans.xml文件 , 生成管理相应的Bean对象
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//getBean : 参数即为spring配置文件中bean的id .
User user = (User) context.getBean("user");
user.show();
}
回到我们的IOC原理案例
新增一个Spring配置文件beans.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="MysqlImpl" class="com.shida.dao.impl.UserMysqlImpl"/>
<bean id="OracleImpl" class="com.shida.dao.impl.UserDaoOracleImpl"/>
<bean id="ServiceImpl" class="com.shida.service.impl.UserServiceImpl">
<!--注意: 这里的name并不是属性 , 而是set方法后面的那部分 , 首字母小写-->
<!--引用另外一个bean , 不是用value 而是用 ref-->
<property name="userDao" ref="OracleImpl"/>
</bean>
</beans>
测试
@Test
public void testUser2(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
UserServiceImpl serviceImpl = (UserServiceImpl) context.getBean("ServiceImpl");
serviceImpl.getUser();
}
OK , 到了现在 , 我们彻底不用再程序中去改动了 , 要实现不同的操作 , 只需要在xml配置文件中进行修改 , 所谓的IoC,一句话搞定 : 对象由Spring 来创建 , 管理 , 装配 !
2. IOC创建对象方式
1. 通过无参构造方法创建
1. User实体类
public class User {
private Integer id;
private String name;
private String phone;
public User() {
System.out.println("user无参构造方法");
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("Hello,"+ name );
}
}
2. beans.xml
<bean id="user" class="com.shida.entity.User">
<property name="name" value="焦前进"/>
</bean>
3. 测试类
@Test
public void test(){
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
//在执行getBean的时候, user已经创建好了 , 通过无参构造
User user = (User) context.getBean("user");
//调用对象的方法 .
user.show();
}
结果可以发现,在调用show方法之前,User对象已经通过无参构造初始化了
2. 通过有参构造创建
1. User实体类
public class User {
private Integer id;
private String name;
private String phone;
// public User() {
// System.out.println("user无参构造方法");
// }
public User(String name) {
System.out.println("user有参构造方法");
this.name = name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
System.out.println("Hello,"+ name );
}
}
2. beans.xml
①根据index参数下标设置
<!-- 第一种根据index参数下标设置 -->
<bean id="user" class="com.shida.entity.User">
<!-- index指构造方法 , 下标从0开始 -->
<constructor-arg index="0" value="焦前进"/>
</bean>```
②根据参数名字设置
<!-- 第二种根据参数名字设置 -->
<bean id="user" class="com.shida.entity.User">
<!-- name指参数名 -->
<constructor-arg name="name" value="焦前进"/>
</bean>
③根据参数类型设置
<!-- 第三种根据参数类型设置 -->
<bean id="user" class="com.shida.entity.User">
<constructor-arg type="java.lang.String" value="焦前进"/>
</bean>
3. Spring配置
1. 别名
<!--设置别名:在获取Bean的时候可以使用别名获取-->
<alias name="user" alias="userNew"/>
2. Bean的配置
<!--bean就是java对象,由Spring创建和管理-->
<!--
id 是bean的标识符,要唯一,如果没有配置id,name就是默认标识符
如果配置id,又配置了name,那么name是别名
name可以设置多个别名,可以用逗号,分号,空格隔开
如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;
class是bean的全限定名=包名+类名
-->
<bean id="user" name="user" class="com.shida.entity.User">
<property name="name" value="焦前进"/>
</bean>
四、依赖注入(手动装配)
依赖注入(Dependency Injection,DI)。
- 依赖 : 指Bean对象的创建依赖于容器 . Bean对象的依赖资源 .
- 注入 : 指Bean对象所依赖的资源 , 由容器来设置和装配 .
1. 构造器注入
2. Set方法注入
1. 创建Address与Student实体类
public class Address {
private String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Address{" +
"address='" + address + '\'' +
'}';
}
}
public class Student {
private String name;
private Address address;
private String[] books;
private List<String> hobbys;
private Map<String,String> card;
private Set<String> games;
private String wife;
private Properties info;
// set get toString()
}
2.注入 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="addr" class="com.shida.entity.Address">
<property name="address" value="河南新乡"/>
</bean>
<bean id="student" class="com.shida.entity.Student">
<!-- 1. 常量注入-->
<property name="name" value="焦前进"/>
<!-- 2. bean注入-->
<property name="address" ref="addr"/>
<!-- 3. 数组注入-->
<property name="books">
<array>
<value>Java入门很简单</value>
<value>Mysql入门很简单</value>
</array>
</property>
<!-- 4. 集合注入-->
<property name="hobbys">
<list>
<value>打篮球</value>
<value>写bug</value>
</list>
</property>
<!-- 5. map注入-->
<property name="card">
<map>
<entry key="邮政" value="123456"></entry>
<entry key="建设" value="654321"></entry>
</map>
</property>
<!-- 6. set注入-->
<property name="games">
<set>
<value>王者荣耀</value>
<value>和平精英</value>
</set>
</property>
<!-- 7. null注入-->
<property name="wife"><null/></property>
<!-- 8. properties注入-->
<property name="info">
<props>
<prop key="学号">20170624215</prop>
<prop key="姓名">焦前进</prop>
</props>
</property>
</bean>
</beans>
3. 测试
public class StudentTest {
@Test
public void test1() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(JSONUtil.toJsonPrettyStr(student));
}
}
推荐hutool工具类
<!-- hutool的java开发工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>4.6.3</version>
</dependency>
3. 注解注入
1、在spring配置文件中引入context文件头
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
五、注解注入
1. 新建dog、cat、people实体类
@ToString
public class Cat {
public void shout() {
System.out.println("miao~");
}
}
@ToString
public class Dog {
public void shout() {
System.out.println("wang~");
}
}
@ToString
public class User {
private Cat cat;
private Dog dog;
private String str;
// set get
}
2. beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="dog" class="com.shida.entity.Dog"/>
<bean id="cat" class="com.shida.entity.Cat"/>
<bean id="user" class="com.shida.entity.User">
<property name="cat" ref="cat"/>
<property name="dog" ref="dog"/>
<property name="str" value="焦前进"/>
</bean>
</beans>
3. 测试
public class UserTest {
@Test
public void test() {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
User user = applicationContext.getBean("user", User.class);
user.getCat().shout();
user.getDog().shout();
}
4. 使用注解 修改
将User类中的set方法去掉,使用@Autowired注解
@ToString
public class User {
@Autowired
private Cat cat;
@Autowired
private Dog dog;
private String str;
public Cat getCat() {
return cat;
}
public Dog getDog() {
return dog;
}
public String getStr() {
return str;
}
}
在spring配置文件中引入context文件头
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
完整的spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启属性注解支持!-->
<context:annotation-config/>
<bean id="dog" class="com.shida.entity.Dog"/>
<bean id="cat" class="com.shida.entity.Cat"/>
<bean id="user" class="com.shida.entity.User"/>
</beans>
六、使用注解开发
1. 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">
</beans>
我们之前都是使用 bean 的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
2、配置扫描哪些包下的注解
<!--指定注解扫描包-->
<context:component-scan base-package="com.shida.entity"/>
3、在指定包下编写类,增加注解
@Component("user")
// 相当于配置文件中 <bean id="user" class="当前注解的类"/>
public class User {}
4. 属性注入
使用注解注入属性
1、可以不用提供set方法,直接在直接名上添加@value(“值”)
2、如果提供了set方法,在set方法上添加@value(“值”);
@Component三个衍生注解
为了更好的进行分层,Spring可以使用其它三个注解,功能一样,目前使用哪一个功能都一样。
- @Controller:web层
- @Service:service层
- @Repository:dao层
写上这些注解,就相当于将这个类交给Spring管理装配了!
5. 总结
XML与注解比较
- XML可以适用任何场景 ,结构清晰,维护方便
- 注解不是自己提供的类使用不了,开发简单方便
xml与注解整合开发 :推荐最佳实践
- xml管理Bean
- 注解完成属性注入
- 使用过程中, 可以不用扫描,扫描是为了类上的注解
<context:annotation-config/>
作用:
- 进行注解驱动注册,从而使注解生效
- 用于激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册
- 如果不扫描包,就需要手动配置bean
- 如果不加注解驱动,则注入的值为null