1.Spring概述
1.1什么是Spring
Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的(解耦)。
框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架。
1.2 Spring的优点
1.方便解耦,简化开发(基础重要功能)
通过Spring提供的IOC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。有了Spring,用户不必再为单实例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
- AOP编程的支持开闭原则
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
- 声明式事务的支持
在Spring中, 我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量
- 方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
1.3 Spring的体系结构
2.IOC
2.1工厂模式解耦
耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立性)。 耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
在软件工程中, 耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计
应使类和构件之间的耦合最小。 软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。 划分模块的一个准则就是高内聚,低耦合。
总结:
耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:
如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。内聚与耦合内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之间的相互依存度却要不那么紧密。
内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他
模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
2.2 什么是工厂
它的核心思想就是:
1、通过Bean工厂读取配置文件使用反射创建对象。
2、把创建出来的对象都存起来,当使用者需要对象的时候,不再自己创建对象,而是调用Bean工厂的方法从容器中获取对象
这里面要解释两个问题:
什么是工厂?
工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。
原来:我们在获取对象时,都是采用 new 的方式。 是主动的。
之前的方式:
现在的方式: 我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象,是被动的。
3.Spring - IOC
3.1 创建好maven工程,并引入依赖:
<dependencies>
<!--引入相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
<!--druid的依赖 如果mysql是8.0及其以上版本,那么druid就要使用1.1.10-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql驱动,为本地mysql驱动-->
<dependency>
<groupId>com.yxinmiracle</groupId>
<artifactId>mysqlUtils</artifactId>
<version>8.0.16</version>
</dependency>
<!--dbutils的依赖-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
</dependencies>
<!--防止运行出错-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.4.2</version>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
3.2 创建接口以及实现类
- UserService接口:
package com.yxinmiracle.service;
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-07-23 00:15
*/
public interface UserService {
void addUser();
}
- UserServiceImpl实现类:
package com.yxinmiracle.service.impl;
import com.yxinmiracle.dao.UserDao;
import com.yxinmiracle.service.UserService;
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-07-23 00:16
*/
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("UserServiceImpl");
}
}
- AnotherUserService
package com.yxinmiracle.service.impl;
import com.yxinmiracle.service.UserService;
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-07-23 00:14
*/
public class AnotherUserService implements UserService {
public AnotherUserService() {
System.out.println("AnotherUserService对象已经创建");
}
public void initData(){
System.out.println("执行对象创建的时候的初始化操作");
}
@Override
public void addUser() {
System.out.println("AnotherUserService");
}
}
3.3 在类路径下创建spring的配置文件,命名为任意名字.xml(不能为中文)
目的:为了实现解耦,不使用new的方式来获取实现类的对象
<?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">、
<!--
我们要将,需要创建的类都配置到配置文件中
id 就是对象唯一标识
class就是他的全限名
scope表示对象的使用范围:
1:singleton 单例,默认值
2:prototype 多例 (有可变的成员变量的时候才使用多例)
lazy-init 标识创建对象的过程是否懒加载,默认是false
不懒加载,核心容器创建的时候,就将对象创建出来 !使用空间换时间
init-method 表示对象创建的时候,需要调用的方法,需要在对象创建之后立即执行的代码就写在这个方法里
destroy-method 表示对象销毁的时候需要调用的方法
-->
<bean id="UserServiceImpl" class="com.yxinmiracle.service.impl.UserServiceImpl"></bean>
<bean id="AnotherUserService" class="com.yxinmiracle.service.impl.AnotherUserService" init-method="initData"></bean>
</beans>
此时我们就可以在代码中通过id来获取UserServiceImpl这个实现类的对象去调用这个实现类中的addUser方法
3.4 测试类代码:
@Test
public void test01(){
// 创建UserService的实现类对象,执行addUser方法
// 1. 创建spring的核心容器对象,制定要加载的配置文件,ClassPathXmlApplicationContext去加载类路径下的xml配置文件
// spring在容器创建的时候就已经创建了对象
ApplicationContext act = new ClassPathXmlApplicationContext("spring.xml");
// 2. 从核心容器中根据id取出配置文件
UserService userService = (UserService) act.getBean("AnotherUserService");
/*
* 实现解耦
* */
userService.addUser();
}
此时要是我们将AnotherUserService这个实现类删除的话,整个项目的代码也不会报错,实现了解耦。spring默认创建的对象是单例的,可以通过debug来测试一下,此外还可以观察一下init-method 有无起作用:
当在加载核心容器的时候,也就是第62行代码,就将ApplicationContext中的构造函数以及initData都执行了。
最后打印为true,也就证明了spring核心容器创建出的对象默认是单例的。
3.5 配置文件的详解(bean标签)
id 就是对象唯一标识
class就是他的全限名
scope表示对象的使用范围:
1:singleton 单例,默认值
2:prototype 多例 (有可变的成员变量的时候才使用多例)
lazy-init 标识创建对象的过程是否懒加载,默认是false
不懒加载,核心容器创建的时候,就将对象创建出来 !使用空间换时间
init-method 表示对象创建的时候,需要调用的方法,需要在对象创建之后立即执行的代码就写在这个方法里
destroy-method 表示对象销毁的时候需要调用的方法
使用spring使用IOC:
1. 由spring的核心容器去加载spring的配置文件,从而创建出使用者想要的所有对象,存储到文档中
2. 在调用核心容器的方法,传入对象的id,从而获取对象,这样就可以实现解耦
spring的IOC的使用步骤:
1. 引入依赖
2. 编写配置文件,要创建对象的类就对应一个bean标签
3. 在要使用对象的地方,创建核心容器制定要加载的配置文件,然后调用核心容器的getBean(id)获取对象
! 可以解耦,但是不仅仅是为了解耦,就是把对象交给spring管理
spring核心容器中的对象什么时候创建,什么时候销毁
1. 如果是单例:
1.1 懒加载:在调用核心容器的getBean()方法的时候创建
1.2 不懒加载: 在核心容器穿件的时候就创建对象
2. 如果是多例:(不可能在核心容器创建的时候就创建出来,因为是多例,不可能只有一个)
都是在调用核心容器的getBean()方法的时候创建,可以使用debug的方法进行尝试
对象的销毁:
1. 如果对象是在核心容器里面的(单例) ,只有在容器close的时候,对象才会销毁,但是这个方法只有在接口的实现类中才有
2. 如果对象不是在核心容器里面的 (多例), 那么对象会在gc线程使用垃圾回收算法判定为垃圾后,就会被回收了
4. Spring工厂详解
4.1 ApplicationContext接口的三种实现类
- ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件
- FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
- AnnotationConfigApplicationContext:当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
ApplicationContext的类图:
可以清楚的看见他继承了六个接口,其中最重要的是:
其中有三个实现类较为重要,分别是:
- ClassPathXmlApplicationContext是加载类路径下的配置文件
- FileSystemXmlApplicationContext从构造函数中可以看出是加载电脑中磁盘中的配置文件
- AnnotationConfigApplicationContext 我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
从图中可以看出ApplicationContext是继承与BeanFactory的,那么他们两者有什么区别?
从类图中可以查出BeanFactory有一个直接实现类,为:XmlBeanFactory
4.2 BeanFactory 和 ApplicationContext 的区别
-
ApplicationContext (现在)
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
-
XmlBeanFactory是老版本使用的工厂,目前已经被废弃
BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
两者的区别:
-
ApplicationContext加载方式是框架启动时就开始创建所有单例的bean,存到了容器里面
- 非懒加载: 在核心容器创建的时候,创建出所有的bean对象,存到核心容器中
- 懒加载: 第一次调用getBean()的时候,创建出bean对象,存到核心容器中
-
BeanFactory 默认是懒加载
4.3 创建bean的三种方式
4.3.1 无参构造方式
4.3.2静态工厂
getDataSource方法为:
/**
* 获取数据源
* @return
*/
public static DataSource getDataSource(){
return dataSource;
}
可以通过类名直接调用,这个就是静态工厂的方式:
<bean id="dataSource" class="com.yxinmiracle.utils.DruidUtil" factory-method="getDataSource"></bean>
4.3.3实例工厂
现在将getDataSource改为:
/**
* 获取数据源
* @return
*/
public DataSource getDataSource(){
return dataSource;
}
不是静态方法就意味着我们需要先创建出DruidUtil类的对象,才能使用这个方法,也就是采用实例工厂的方式:
<!-- 实例工厂 -->
<bean id="DruidUtil2" class="com.yxinmiracle.utils.DruidUtil2"></bean>
<bean id="dataSource" factory-bean="DruidUtil2" factory-method="getDataSource"></bean>
5 注入依赖
5.1 注入依赖的两种方式:
- 构造方法方式注入
- set方法方式的注入
方式一:采用构造函数的方式进行注入,使用constructor-arg
name属性就是构造函数的参数名
ref属性就是要注入的对象的id
方式二:采用set方法的方式进行注入,使用property标签
name属性就是要赋值的变量名(具体来说是setxxxx函数的xxx)
ref就是要注入的对象id
方式一:采用构造函数的方式进行注入,使用constructor-arg
name属性就是构造函数的参数名
ref属性就是要注入的对象的id
方式二:采用set方法的方式进行注入,使用property标签
name属性就是要赋值的变量名(具体来说是setxxxx函数的xxx)
ref就是要注入的对象id
5.2 构造方式注入
如何用spring将userDao赋值
package com.yxinmiracle.service.impl;
import com.yxinmiracle.dao.UserDao;
import com.yxinmiracle.service.UserService;
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-07-23 00:16
*
* 1. 构造方法方式注入
* 2. set方法方式的注入
*/
public class UserServiceImpl implements UserService {
private UserDao userDao; // 如何使用spring核心对象给他赋值
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser() {
userDao.addUser();
}
}
配置文件中这样填写:
<bean id="userService" class="com.yxinmiracle.service.impl.UserServiceImpl">
<constructor-arg name="userDao" ref="userDao"></constructor-arg>
</bean>
5.3 set方法进行注入
如何用set方法将userDao进行赋值
package com.yxinmiracle.service.impl;
import com.yxinmiracle.dao.UserDao;
import com.yxinmiracle.service.UserService;
/**
* @version 1.0
* @author: YxinMiracle
* @date: 2021-07-23 00:16
*
* 1. 构造方法方式注入
* 2. set方法方式的注入
*/
public class UserServiceImpl implements UserService {
private UserDao userDao; // 如何使用spring核心对象给他赋值
public UserServiceImpl() {
}
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
@Override
public void addUser() {
userDao.addUser();
}
}
配置文件中这样填写:
<bean id="userService" class="com.yxinmiracle.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.yxinmiracle.service.impl.UserDaoImpl"></bean>
5.4 注入数组类型
<bean id="userDao" class="com.yxinmiracle.dao.impl.UserDaoImpl">
<!--注入数组 name为java代码中的数组名-->
<property name="stringArray">
<array>
<value>hello1</value>
<value>hello2</value>
<value>hello3</value>
<value>hello4</value>
</array>
</property>
</bean>
5.5 注入Map类型
<bean id="userDao" class="com.yxinmiracle.dao.impl.UserDaoImpl"">
<!--注入map-->
<property name="map">
<map>
<entry key="username" value="aobama"></entry>
<entry key="pwd" value="123456"></entry>
<entry key="address" value="召唤师峡谷"></entry>
</map>
</property>
</bean>
5.6 注入简单类型
<bean id="userDao" class="com.yxinmiracle.dao.impl.UserDaoImpl">
<!--set方法注入-->
<property name="age" value="28"></property>
</bean>