目录
1.Spring简介
1.1什么是Spring呢?
1.2Spring的优势?(面试会问你为什么要使用Spring?)
1.3Spring的架构
2.Spring IOC(重点)
2.1程序中的耦合&解耦
2.1.1什么是程序的耦合?
2.1.2工厂模式解耦介绍
2.1.3耦合示例
2.1.4使用工厂+配置文件+接口解耦代码如下:
2.2什么是IOC(控制反转)
2.3IOC入门案例
2.3.1创建Maven工程,引入相关spring 的jar包;
2.3.2 创建spring核心配置文件-applicationContext.xml
2.3.3创建测试类-TestSpring,通过spring的IOC解决程序中的耦合问题
3.Bean对象的单例和多例
3.1Bean对象的单例和多例概述
3.2为什么用单实例或多实例
3.3测试spring的单实例和多实例
4.Spring DI 依赖注入
4.1两种注入方式介绍
4.2set方式注入
4.2.1普通属性注入
4.2.2对象属性注入
4.3构造方法注入
1.Spring简介
1.1什么是Spring呢?
1).Spring是分层的JavaSE及JavaEE应用于全栈的轻量级开源框架
2).以IOC(Inverse Of Control:控制反转/反转控制)和AOP(Aspact Oriented Programming:面向切面编程)为核心,提供了表现层SpringMVC和持久层Spring JDBC以及业务层事务管理等众多模块的企业级应用技术,还能整合开源世界中众多著名的第三方框架和类库,逐渐成为使用最多的JavaEE企业应用开源框架。
3).Spring的本质是管理软件中的对象,即创建对象和维护对象之间的关系
1.2Spring的优势?(面试会问你为什么要使用Spring?)
1).方便解耦,简化开发
通过Spring提供的IOC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合;
2).AOP的编程支持
通过SpringAOP的功能,方便进行面向切面的编程,许多不容易用传统OOP(Object Oriented Programming:面向对象编程) 实现的功能可以通过 AOP 轻松应付。
3).声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
4).方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
5).方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz
等)的直接支持。
6).降低 JavaEE API 的使用难度。
Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的
使用难度大为降低。
7).Spring框架源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以及对 Java技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
1.3Spring的架构
Spring 最初的目标就是要整合一切优秀资源,然后对外提供一个统一的服务。Spring 模块构建在核心容器之上,核心容器定义了创建、配置和管理 bean 的方式,如下图所示:
组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现,但是核心容器是必不可少的。每个模块的功能如下:
模块 |
说明 |
核心容器Spring Core |
核心容器,提供Spring框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式,将应用程序的配置和依赖性规范与实际的应用程序代码分开。 |
Spring Context | 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。 |
2.Spring IOC(重点)
在学习IOC之前,一定先看如下内如,可以帮你快速理解IOC:
2.1程序中的耦合&解耦
2.1.1什么是程序的耦合?
定义: 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。
模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关系、数据传递关系。
模块间联系越多,其耦合性越强,同时表明其独立性越差(降低耦合性,可以提高其独立性)。
总结: 在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的依赖程度越高,耦合度就越高。对象之间的耦合越高,维护成本越高。因此对象的设计应使类和构件之间的耦合最小。
解耦定义: 降低程序之间的依赖程度,即降低程序之间的耦合度的过程就叫做解耦。
举个例子:
思考: 早期的Jdbc操作中,在注册数据库驱动时,经常采用的是Class.forName的方式,而不是采用DriverManager.registerDriver的方式,为什么?
public class TestJdbc {
public static void main(String[] args) throws Exception {
//1.注册数据库驱动
// DriverManager.registerDriver( new Driver() );
Class.forName("com.mysql.jdbc.Driver");
//2.获取数据库连接
//3.获取传输器
//4.发送sql到服务器执行并返回执行结果
//5.处理结果
//6.释放资源
}
}
说明: 通过 DriverManager.registerDriver(new Driver())的方式也可以注册驱动,它是com.mysql.jdbc包下的;
不用它的原因:
1).new Driver() 中有一个静态代码块,里面存在注册驱动的代码,而我们都知道,在创建实例前,静态代码块会执行,所以相当于注册了两次;
2).它因为是com.mysql.jdbc包下面的,所以只能注册mysql的驱动,那么公司有一天要求大家全部改用oracl的数据库,你怎么做呢? 是不是要在打开这个代码,再去换个oracl的驱动包,在new 一个oracl的驱动,估计是你也不乐意,因为一改动代码,就要重新编译,重新部署,是不是太麻烦了啊;
3).想必你也有这样的疑问,那Class.forName("com.mysql.jdbc.Driver")这样注册要换成orcal的数据库,不也要修改吗? 其实这是不需要的,我们可以把注册驱动,获取连接,获取传输对象这些需要重复写的代码,提取到一个JDBC的工具类中,需要时,直接调用,改动时,直接修改这个配置文件就可以了.
总结: 上述案例中我们获取的信息有:
1).使用DriverManager.registerDriver(new Driver) 这样的方式去注册驱动时,会造成程序的耦合性太高,太依赖于mysql数据库了,这样并不好,因为想要更换时,非常的消耗成本;
2).我们也要学会面向对象一个非常重要特性,封装,利用好这一点,我们可以避免代码的重复编写,减轻工作量,有时也会有解耦的效果!
通过这个案例也可以这么理解,其实耦合性就是类与类,层与层的依赖关系就是耦合性!
2.1.2工厂模式解耦介绍
有耦合性,我们就要解耦
说明: 在实际开发中可以将三层(表现层,业务层,持久层)的对象都使用配置文件配置起来,当启动服务器加载应用时,可以通过工厂读取配置文件,根据配置文件中的配置将这些对象创建出来,在接下来使用的时候,直接拿过来用即可!
那么读取配置文件,根据配置文件创建并返回这些对象的类就是工厂;
可以通过[工厂+接口+配置文件]的方式解除程序的耦合性;
2.1.3耦合示例
1.创建一个Test1的maven工程
2.创建Dao层,添加如下代码(也就是基本的三层):
package com.dao;
public interface UserDao {
void addUser();
}
dao层的实现类;
package com.dao;
public class UserDaoImpl implements UserDao {
@Override
public void addUser() {
System.out.println("Dao层的addUser()方法执行了..成功保存了一条员工信息..");
}
}
3.创建service层,添加如下代码:
package com.service;
public interface UserService {
public void addUser();
}
public class UserServiceImpl implements UserService {
private UserDao dao = new UserDaoImpl();
@Override
public void addUser() {
System.out.println("调用dao层的方法添加员工信息...");
dao.addUser();
}
}
4.创建控制层 controller
package com.controller;
import org.junit.Test;
import com.service.UserService;
import com.service.UserServiceImpl;
public class UserController {
private UserService service = new UserServiceImpl();
@Test
public void testAddEmp() {
System.out.println("调用service层的方法添加员工信息...");
service.addUser();
}
}
运行一下程序,结果如下:
如果在上面的程序中将UserDaoImpl或UserServiceImpl移除,会导致其他类中的代码编译错误。此时表现层和业务层,及业务层和持久层之间的依赖程度过高,如果将来替换某一层,很可能会造成其他层无法运行,只能通过修改程序代码保证程序运行,这样依赖就会提高维护成本以及造成不必要的麻烦。
而在程序中new对象的方式造成了这种程序之间的依赖程度提升,即提升了程序之间的耦合性。
2.1.4使用工厂+配置文件+接口解耦代码如下:
1、创建com.factory.BeanFactory类,用于创建各个层所需要的对象。
package com.factory;
import java.io.InputStream;
import java.util.Properties;
public class BeanFactory {
//声明一个Properties对象,在静态代码块中对其进行初始化
private static Properties prop;
static {
try {
//为prop进行实例化
prop = new Properties();
//获取配置文件的流对象
InputStream in = BeanFactory.class.getClassLoader()
.getResourceAsStream("config.properties");
//将配置文件中的内容读取到Properties对象中
prop.load( in );
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("初始化Properties对象失败!");
}
}
public static Object getBean(String key) {
Object bean = null;
try {
String className = prop.getProperty( key );
bean = Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return bean;
}
}
2、在源码目录下创建一个config.properties文件,文件内容配置如下:
UserService=com.tedu.service.UserServiceImpl
UserDao=com.tedu.dao.UserDaoImpl
将UserController类中通过"new对象的形式获取了UserService接口子类的实例" 以及 在UserServiceImpl类中通过"new对象的形式获取了UserDao接口子类的实例" 改为使用BeanFactory工厂获取Service和Dao层的实例。如下:
//private UserService service = new UserServiceImpl();
private UserService service = (UserService)BeanFactory.getBean("UserService");
//private UserDao dao = new UserDaoImpl();
private User0Dao dao = (UserDao)BeanFactory.getBean( "UserDao" );
Bean: 可重用组件(计算机英语)
JavaBean:使用Java语言编写的可重用组件,例如:service层、dao层等
JavaBean:通常分为业务Bean和实体Bean
业务Bean:处理业务逻辑,service层、dao层
实体Bean:封装数据,例如,为了封装员工信息而编写的User实体类。
2.2什么是IOC(控制反转)
说明: 控制反转(Inversion of Control,缩写为IOC),即把创建对象的权利交给框架,也就是指将对象的创建,对象的存储,对象的管理交给了spring容器.
(spring容器是spring中的一个核心模块,用于管理对象,底层可以理解为是一个map集合)
在此之前,当需要对象时,通常是利用new关键字创建一个对象:
private UserService service = new UserServiceImpl();
//private UserService service = (UserService)BeanFactory.getBean("UserService");
但由于new对象,会提高类和类之间的依赖关系,即代码之间的耦合性。
而现在我们可以将对象的创建交给框架来做:
//private EmpService service = new EmpServiceImpl();
private UserService service = (UserService)BeanFactory.getBean("UserService");
只需要将类提前配置在配置文件中,就可以将对象的创建交给框架来做。当需要对象时,不需要自己创建,而是通过框架直接获取即可,省去了new对象的过程,自然就降低类和类之间的依赖关系,也就是耦合性。
2.3IOC入门案例
下面再来看下Spring的ioc解决程序间的耦合性:
2.3.1创建Maven工程,引入相关spring 的jar包;
1、创建Maven—Java工程
2、引入junit、spring的jar包:在maven工程的pom.xml文件的根标签(project)内添加如下配置:
<dependencies>
<!-- 添加junit的jar包 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<!-- 添加spring的jar包 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.3.RELEASE</version>
</dependency>
</dependencies>
3、导入后保存项目;
2.3.2 创建spring核心配置文件-applicationContext.xml
1、在工程的src/main/resources源码目录下,创建applicationContext.xml文件:
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">
</beans>
3、将UserService接口的实现类与UserDao接口的实现类交给Spring容器创建,在核心配置文件中添加如下配置:
1.先完成接口与实现类的创建(三层)
2.
<?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">
<!-- 将UserDao接口的实现类的实例交给spring创建 -->
<bean id=UserDao class=com.dao.UserDaoImpl></bean>
<!-- 将UserService接口的实现类的实例交给spring创建 -->
<bean id=UserService class=com.service.UserServiceImpl></bean>
</beans>
2.3.3创建测试类-TestSpring,通过spring的IOC解决程序中的耦合问题
1、创建测试类
2、测试步骤及代码如下:
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.dao.UserDao;
import com.service.UserService;
public class TestSpring {
@Test
public void testIOC(){
//获取spring的核心容器对象
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
//通过spring的核心容器获取UserService接口的子类实例
UserDao dao= (UserDao)ac.getBean("UserDao");
UserService service =(UserService)ac.getBean("UserService");
System.out.println(dao);
System.out.println(service);
}
}
3、运行结果:
4、入门案例总结:
这就是spring框架的IOC---控制反转,之前我们自己new对象,例如:
User u = new User();
而现在,变成由一个初始化的xml配置文件来创建,也就是由spring容器来创建;
User user = (User) ac.getBean("user");
当程序运行,spring开始工作后,会加载整个xml核心配置文件,读取到<bean>,获取到class属性中类的全路径,利用反射创建类的对象;
3.Bean对象的单例和多例
说明: 通过spring容器创建的对象称之为bean;
3.1Bean对象的单例和多例概述
在Spring容器中管理的Bean对象的作用域,可以通过scope属性或用相关注解指定其作用域。
最常用是singleton(单例)或prototype(多例)。其含义如下:
1) singleton:单实例,是默认值。这个作用域标识的对象具备全局唯一性。
当把一个 bean 定义设置scope为singleton作用域时,那么Spring IOC容器只会创建该bean定义的唯一实例。也就是说,整个Spring IOC容器中只会创建当前类的唯一一个对象。
这个单一实例会被存储到单例缓存(singleton cache)中,并且所有针对该bean的后续请求和引用都 将返回被缓存的、唯一的这个对象实例。
singleton负责对象的创建、初始化、销毁。
2) prototype:多实例。这个作用域标识的对象每次获取都会创建新的对象。
当把一个 bean 定义设置scope为prototype作用域时,Spring IOC容器会在每一次获取当前Bean时,都会产生一个新的Bean实例(相当于new的操作)
prototype只负责对象的创建和初始化,不负责销毁。
3.2为什么用单实例或多实例
之所以用单实例,在没有线程安全问题的前提下,没必要每个请求都创建一个对象,这样子既浪费CPU又浪费内存;
之所以用多例,是为了防止并发问题;即一个请求改变了对象的状态(例如,可改变的成员变量),此时对象又处理另一个请求,而之前请求对对象状态的改变导致了对象对另一个请求做了错误的处理;
用单例和多例的标准只有一,当对象含有可改变的状态时(更精确的说就是在实际应用中该状态会改变),使用多例,否则单例;
在Spring中配置Bean实例是单例还是多例方法是:
单例:
<bean id="user" scope="singleton" class="com.spring.User"></bean>
多例:
<bean id="user" scope="prototype" class="com.spring.User"></bean>
3.3测试spring的单实例和多实例
1、创建TestIOC2类,测试代码如下:
package com.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestIOC2 {
public static void main(String[] args) {
//1.加载spring的核心配置文件
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext(
"applicationContext.xml"
);
//2.从spring容器中获取bean对象(而不是自己new)
User user1 = (User) ac.getBean("user");
User user2 = (User) ac.getBean("user");
//3.测试
if(user1 == user2){
System.out.println("当前实例为单实例...");
}else{
System.out.println("当前实例为多实例...");
}
}
}
3、将applicationContext.xml中,User类bean标签的scope值设置为singleton:
<bean id="user" scope="singleton" class="com.spring.User"></bean>
运行TestIOC2,运行结果为:
<bean id="user" scope="prototype" class="com.spring.User"></bean>
再次运行TestIOC2,运行结果为:
4.Spring DI 依赖注入
4.1两种注入方式介绍
DI(Dependency Injection)依赖注入 。
依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。
简单来说,所谓的依赖注入其实就是,在创建对象的同时或之后,如何给对象的属性赋值。
如果对象由我们自己创建,这一切都变得很简单,例如:
User user = new User();
user.setName("刘德华");
user.setAge(18);
或者:
User user = new User("刘德华", 18);
如果对象有spring创建,那么spring是怎么给属性赋值的?spring提供两种方式为属性赋值:
(1).Set方式注入
(2).构造方法注入(spring大都使用这种方式)
4.2set方式注入
4.2.1普通属性注入
需求:通过Spring创建User实例,并为User实例的name和age属性(普通属性)赋值
1、创建User类,声明name和age属性,并添加对应的setter和getter方法,以及toString方法
package com.spring;
public class User {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
2、在applicationContext.xml中声明User类的bean实例
<!-- 声明User类的bean实例 -->
<bean id="user" class="com.spring.User"></bean>
3、创建测试类—TestDI
package com.spring;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestDI {
public static void main(String[] args) {
//1.加载applicationContext.xml核心配置文件
ClassPathXmlApplicationContext ac =
new ClassPathXmlApplicationContext(
"applicationContext.xml"
);
//2.获取User类的实例
User user = (User) ac.getBean("user");
//3.输出User实例
System.out.println( user );
}
}
由于这里没有为User对象的属性赋值,所以此时运行测试,结果为:
4、修改applicationContext.xml中User实例的声明,为User实例注入属性
<!-- 声明User类的bean实例 -->
<bean id="user" class="com.tedu.spring.User">
<!-- 通过set方式为普通属性赋值 -->
<property name="name" value="军军"></property>
<property name="age" value="18"></property>
</bean>
其中name属性的值,必须要和User类中所注入属性对应的get方法的名字去掉get后首字母变为小写的名字相同
例如:为 User类中的age属性赋值,由于name属性对应的get方法名字为 getAge,当去调用get和 首字母变为小写后的名称为age,因此为age属性注入的配置内容为:
<property name="age" value="20"></property>
普通属性直接通过value注入即可。
5、运行测试类TestDI,结果为:
4.2.2对象属性注入
需求:通过Spring创建User实例,并为User对象的userInfo属性(对象属性)赋值
1、创建UserInfo类
package com.spring;
public class UserInfo {
}
2、在applicationContext.xml中,声明UserInfo类的bean实例
<!-- 声明UserInfo类的bean实例 -->
<bean id="userInfo" class="com.spring.UserInfo"></bean>
3、修改User类,声明userInfo属性,添加对应的setter和getter方法,并重新生成toString方法
public class User {
...
private UserInfo userInfo;
public UserInfo getUserInfo() {
return userInfo;
}
public void setUserInfo(UserInfo userInfo) {
this.userInfo = userInfo;
}
...
public String toString() {
return "User [name=" + name + ", age=" + age + ", userInfo=" + userInfo + "]";
}
}
4、在applicationContext.xml中,将UserInfo对象作为值,赋值给User对象的userInfo属性
<!-- 声明User类的bean实例 -->
<bean id="user" class="com.tedu.spring.User">
<!-- 通过set方式为普通属性赋值 -->
<property name="name" value="军军"></property>
<property name="age" value="18"></property>
<!-- 通过set方式为对象属性赋值 -->
<property name="userInfo" ref="userInfo"></property>
</bean>
由于此处是将UserInfo对象作为值赋值给另一个对象的属性,因此ref属性的值,为UserInfo对象bean标签的id值。
对象属性通过ref属性注入。
4.3构造方法注入
需求:通过Spring创建User对象,并为User对象的属性(name、age、UserInfo属性)赋值
1、为User类声明构造函数
//声明无参构造函数
public User() {
}
//声明有参构造函数
public User(String name, Integer age, UserInfo userInfo) {
super();
this.name = name;
this.age = age;
this.userInfo = userInfo;
}
2、修改applicationContext.xml文件,将set方式修改为构造方法注入。
<bean id="user" class="com.spring.User">
<!-- 通过set方式为普通属性赋值
<property name="name" value="军军"></property>
<property name="age" value="18"></property>
<property name="userInfo" ref="userInfo"></property>
-->
<!-- 通过构造器中参数为属性赋值 -->
<constructor-arg name="name" value="军军"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="userInfo" ref="userInfo"></constructor-arg>
</bean>
<!-- 声明UserInfo类的bean实例 -->
<bean id="userInfo" class="com.spring.UserInfo"></bean>
其中,constructor-arg标签name属性的值必须和构造函数中参数的名字相同!
同样的,普通属性直接通过value注入即可;
对象属性通过ref属性注入。
2、运行测试类TestDI,结果为:
感谢您的阅读,努力成为习惯,优秀自成常态!