1、Spring概述
Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层。Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架
2、spring发展历程
1997 年 IBM 提出了 EJB 的思想
1998 年,SUN 制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布
Rod Johnson(spring之父) ,Expert One-to-One J2EE Design and Development(2002) 阐述了 J2EE 使用 EJB 开发设计的优点及解决方案。Expert One-to-One J2EE Development without EJB(2004) 。阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形) ,2017年9月份发布了spring的最新版本spring 5.0通用版(GA)
3、spring的优势
(1)、方便解耦,简化开发:IOC通过 Spring 提供的 IoC容器,可以将对象间的依赖关系交由 Spring进行控制,避免硬编码所造 成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这 些很底层的需求编写代码,可以更专注于上层的应用。(2)、AOP编程的支持通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP实现的功能可以。通过 AOP轻松应付。(3)、声明式事务的支持可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理, 提高开发效率和质量。(4)、方便程序的测试可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可 做的事情。(5)、方便集成各种优秀框架Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz等)的直接支持(6)、降低 JavaEE API 的使用难度Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的 使用难度大为降低。(7)、Java 源码是经典学习范例Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java设计模式灵活运用以 及对 Java技术的高深造诣。它的源代码无疑是 Java技术的最佳实践的范例。
4、spring体系结构
二、Spring入门案例のHelloWorld
和大多数框架一样,使用第三方的框架,基本步骤:
1.引入框架使用需要的依赖******
2.配置文件(格式大多数xml格式或yaml、properties)
3.spring提供的API完成对象的获取和实例化
案例代码
-
pom.xml导入spring依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-beans</artifactId><version>5.3.28</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-core</artifactId><version>5.3.28</version> </dependency> <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.3.28</version> </dependency>
-
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:p="http://www.springframework.org/schema/p"xmlns:c="http://www.springframework.org/schema/c"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsdhttp://www.springframework.org/schema/txhttps://www.springframework.org/schema/tx/spring-tx.xsd"><!-- spring配置文件中配置对象的基本信息 --><bean id="myUserDaoImpl1" class="com.woniu.dao.impl.UserDaoImpl" scope="prototype"></bean> </beans>
-
创建Person类型,利用spring获取Person对象
通过案例代码的运行,我们会发现,不用写new UserDaoImpl()也能得到UserDaoImpl对象。解决了传统自己new对象的“硬编码”问题。
思考1:Spring中获取的对象是单例的吗?
案例分析
-
UserDaoImpl.java
-
public class UserDaoImpl implements UserDao {public UserDaoImpl() {System.out.println("UserDaoImpl构造器被调用");}@Overridepublic int insert() {System.out.println("链接mysql数据库,完成用户信息");return 0;}
}
-
测试类
-
@Test public void mt01(){ApplicationContext factory=new ClassPathXmlApplicationContext("ApplicationContext.xml");//获取特定的对象 spring容器装的对象,默认:单例的!!!Object obj1 = factory.getBean("myUserDaoImpl1");Object obj2 = factory.getBean("myUserDaoImpl1");System.out.println("obj1内存地址:"+obj1);System.out.println("obj2内存地址:"+obj2);System.out.println(obj1==obj2);
}
-
测试结果
观察测试代码运行结果,可以得出结论:默认情况下,所有由Spring构建的对象都是单例对象。
思考2:如何从Spring容器得到一个多例对象呢?
我们可以通过设置bean标签的scope属性来改变对象在Spring容器中的创建方式:
<bean id="userDao" class="cn.woniu.dao.UserDao" scope="prototype"></bean>
- 作用:
用于注册对象信息,让 spring 来创建。
默认情况下 它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。 - 属性:
id:给对象在容器中提供一个唯一标识。用于获取对象。
class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
scope:指定对象的作用范围。
1、singleton :默认值,单例的.
2、prototype :多例的.
3、request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
4、session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
5、global session :WEB 项目中,应用在 Portlet 环境 .如果没有 Portlet 环境那么 globalSession 相当于 session.
三、解读Helloworld案例代码
1、ApplicationContext是什么?
我们可以把ApplicationContext理解成是一个装载对象的“大容器”,其本质就是一个对象工厂。借助IDEA快捷键Alt+Ctrl+U可以看到ApplicationContext实现过的所有父接口:
2、BeanFactory和ApplicationContext的区别【常见面试题】
ApplicationContext底层就是对象工厂(即BeanFactory)。Spring提供两种创建对象工厂的方式(BeanFactory和ApplicationContext两个接口)。这种方式创建对象工厂时有什么区别呢?
案例演示ApplicationContext
-
在UserDaoImpl类中添加无参构造方法:代码略
-
测试方法
//1.创建spring ioc容器 ApplicationContext可以理解成是Spring容器,本质其实就是BeanFactory,职责就是负责获取对象
//ApplicationContext:工厂建好,立马将需要工厂管理对象也一起建好
ApplicationContext cnt=new ClassPathXmlApplicationContext(“spring-config.xml”); -
结果如下:
UserDaoImpl对象的构造器被调用,意味着UserDaoImpl对象在创建ApplicationContext时就被创建
案例演示BeanFactory
-
修改测试方法
//1.创建spring ioc容器 ApplicationContext可以理解成是Spring容器,本质其实就是BeanFactory,职责就是负责获取对象
//BeanFactory延迟加载(也称为懒加载)创建工厂对象时,不会帮我们把工厂内部对象创建
Resource resource=new ClassPathResource(“spring-config.xml”);
BeanFactory cnt=new XmlBeanFactory(resource); -
结果如下:
控制台什么也没输出,也就是UserDaoImpl对象的构造器没有调用,意味着UserDaoImpl对象在创建BeanFactory时没有被创建
通过以上案例对比,我们可以得出以下结论:
BeanFactory:SpringIoc容器基本实现,是Spring内部的使用接口,不提供开发人员进行使用。
BeanFactory对象工厂实现的特点是:
构建核心容器时,创建对象采取的策略是延迟加载的方式,什么时候调用getBean根据id获取对象了,什么时候才真正创建对象。适用于多例模式
ApplicationContext:ApplicationContext是BeanFactory接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。
ApplicationContext实现的特点是:
在构建核心容器时,创建对象采取的策略是立即加载的方式,只要一读取完配置文件就马上创建配置文件中的配置对象。适用于单例模式
3、ApplicationContext的三个实现类【理解】
在入门Helloworld案例中,我们实例化spring的工厂对象时,使用的是ClassPathXmlApplicationContext对象,其实在ApplicationContext的继承体系中,除了ClassPathXmlApplicationContext这个实现类以外,还有另外两个子类也可以完成ApplicationContext对象工厂的实例化。
提示:idea中通过选中类,通过ctrl+H可以查看向下派生的继承体现,通过ctrl+alt+u可以查看该类向上的继承体系结构。
在ApplicationContext对象上按ctrl+h查看该类的结构
ClassPathXmlApplicationContext:加载类路径下的配置文件,要求配置文件必须在类路径下(常用)*****
FileSystemXmlApplicationContext:加载磁盘任意路径下的配置文件(必须有访问权限)
AnnotationConfigApplicationContext:读取注解配置容器
4、bean 的作用范围和生命周期 【面试题】
1、单例对象:scope="singleton"一个应用只有一个对象的实例。它的作用范围就是整个应用。生命周期:对象出生:当应用加载,创建容器时,对象就被创建了。对象活着:只要容器在,对象一直活着。 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。2、多例对象:scope="prototype"每次访问对象时,都会重新创建对象实例。生命周期:对象出生:当使用对象时,创建新的对象实例。不使用就不创建对象活着:只要对象在使用中,就一直活着。对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
案例演示:bean的生命 周期
创建StudentService类,在该类中添加:一个构造方法、一个init方法、一个destory方法public class StudentService {//构造方法public StudentService(){System.out.println("创建对象实例");}//初始化方法public void init(){System.out.println("对象被创建了");}//销毁public void destory(){System.out.println("对象被销毁了");}
}
配置spring-config.xml
<!-- 实例化StudentService --><bean id = "studentService"class="cn.woniu.service.StudentService"scope="singleton"init-method="init"destroy-method="destory"></bean>
创建测试方法
我们发现,在===========上面时,对象就已经被创建了,这是单例对象的特点,立即加载,而且执行了studnetService类的init方法,但是我们并没有看到执行destory方法。原因很简单:当测试方法执行完时,线程结束,此时容器销毁,容器销毁意味着创建的单例对象也要销毁,只是此时没来得及打印。要想看到效果,我们需要手动让容器销毁,调用容器的close方法,此时对象就会销毁,即执行destory方法
如果我们把配置文件的对象改成多例模式呢?其它的代码不变
<!-- 实例化StudentService --><bean id = "studentService"class="cn.woniu.service.StudentService"scope="prototype"init-method="init"destroy-method="destory"></bean>
改为多例模式后发现对象是懒加载的方式,即在用到的时候才会创建,而且我们手动关闭容器的时候也不会调用destory方法,原因很简单,多例对象的死亡是由java垃圾回收器回收的,不受容器管理。
四 IOC
IOC容器负责bean管理。什么是bean管理?从两个方面来理解:
- Spring容器负责为java项目创建对象(即IOC)
- Spring容器负责为创建的对象注入属性值(即DI)
Bean管理操作的方式有:
- 基于xml配置文件方式实现
- 基于注解方式实现
- 通过JavaConfig配置bean
IOC概念
IOC: Inversion Of Control 控制反转。
以前:java程序使用对象:开发人员 new 对象 正向控制
spring:java程序使用对象:找Spring的ApplicationContext对象拿对象
翻译:new对象称为开发人员对对象的控制,将以前程序员自己new对象“权利” 交个Spring的spring容器统一管理的现象就是控制的权利反转了。所以,在spring中IOC的主要作用就是利用spring容器完成不同类对象的创建【实体类不会通过spring的ioc完成创建】进而解决程序中new对象时硬编码的问题。
IOC只解决程序间的依赖关系,除此之外没有任何功能
如何使用ioc完成对象创建呢?通常步骤有:
1.配置文件利用<bean></bean>配置你要spring容器管理的对象
2.获取spring工厂对象【或spring容器】
3.调用getBean根据id获取对象即可。
五 DI
DI:Dependency Injection 依赖注入
翻译:IOC只管对象创建,DI负责对象属性赋值
DI常用方式
- setter注入方式
- 构造器注入方式
setter注入方式
Set注入就是在类中提供需要注入成员的 setter方法,通过调用setter完成属性赋值
案例
Person.java代码
@ToString
public class Person {private Integer id;//1private String name;//张三丰public Person() {System.out.println("Person的构造器被调用了");}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;}
}
构造器注入
注入属性值的对象提供了带参构造器,而且还保持类有无参构造器
案例
Person.java添加构造器
public class Person {private Integer id;//1private String name;//张三丰public Person(Integer id, String name) {this.id = id;this.name = name;}public Person() {}
}
使用c命名空间和p命名空间简化构造器赋值和setter赋值【了解】
使用步骤:
- xml文件根标签中引入p和c命名空间
- 使用c:和p:作为前缀实现赋值
不同数据类型的属性注入
Spring不管entity类型,主要对三层架构对象进行ioc和di管理
8种基本类型及对应的包装类及String类型
赋值方式都是通过value属性赋值
使用setter赋值,完整语法
<property name="属性名" value="属性值"/>
使用构造器赋值,完整语法
<constructor-arg name/index/type="形参名" value="属性值"/>
自定义对象类型
使用setter赋值,完整语法
<property name="属性名" ref="引用的对象ID"/>
使用构造器赋值,完整语法
<constructor-arg name/index/type="形参名" ref="引用的对象ID"/>
集合和数组类型【了解】
常见类型:数组、List集合、Set集合、Map集合、Properties集合
利用c命名空间和p命名空间简化属性赋值【了解】
c命名空间简化构造器注入方式,举例:
p命名空间简化setter注入方式
使用步骤:
- xml文件根标签中引入p和c命名空间
- 使用c:和p:作为前缀实现赋值
六 DI自动装配机制【理解】
自动装配是根据自动的装配规则(byName属性名称或byType属性类型),Spring自动将匹配的属性值进行注入的方式。自动装配是spring DI的一种方式。
在Spring中有三种自动装配的方式:
- 在xml中显式配置【理解】
- 在javal类中显示配置【Spring5+的新特性】【重点】
- 隐式的自动装配【注解方式,重点】
利用XML配置完成自动装配【理解】
自动装配常见异常
使用byType进行装配时,如果一个类型可以找到多个Bean对象,就会出现以下异常:
解决方案:利用byName进行装配
七 spring提供注解完成IOC和DI功能【实际开发都是注解完成ioc和di 重点】
IOC注解
@Component,@Repository,@Service,@Controller标记在类上,实现这个类由spring容器负责对象管理
作用类似xml配置
@Component:标注三层架构以外的类,比如全局异常、工具类、redis或minio工具类
spring为@Component衍生了三个注解,三个注解作用跟@Component一模一样,但是从词意来看,阅读性比@Component更好
@Repository:一般用在持久层
@Service:一般用在业务层
@Controller:一般用在控制器层
DI注解
@Value
完成8种基本类型和String类型的属性值注入。@Value的作用等价于以下代码:
<property name="" value='属性值'/>
或
<constructor-arg name/index/type="" value="属性值"/>
@Autowired
完成自定义类型属性注入。@Autowired的作用等价于以下代码:
<bean autowired="byName/byType"/>
@Autowired默认先根据byType,如果byType装配失败,退而其次,使用byName再装配,如果byName也失败,直接抛出异常
跟@Autowired注解一样,也可以为引用类型的对象属性注入值的的注解还有以下两个:
@Resource
@Resource注解与@Autowired注解的作用是一样的,都是用来为bean对象注入值的,唯一的区别是@Resource注解要根据实例化的bean名称为bean对象注入值。@Resource是javax包下注解,不是spring官方注解。
@Resource(value=“byName属性名”)
@Quanifier
byName注入,是spring提供的byName注解,它在给字段注入时不能独立使用,必须和@Autowire 一起使用,表示在自动按照类型注入的基础之上,再按照 Bean 的id 注入;@Quanifier也给方法形参注入,注入时可以独立使用。
语法:
@Quanifier(value=“byName的名称”)
小结注解开发
spring提供的注解,依据注解作用和使用位置不同,划分为三类:
1.注解在类上,作用IOC创建对象的作用:@Component @Repository @Service @Controller2.注解在属性上,作用DI属性注入值的作用:@Resource:byName@Quanifier:默认byName,但是不能独立使用在属性上,必须@Autowired结合使用@AutoWired:默认先byType,byType失败,再byName@Value:注入八种基本类型和String类型3.注解在类上,设置对象单例模式还是多例模式,设置对象使用范围:[了解]@Scope(设置对象使用范围:propotype singleton session request)
使用案例
-
UserDaoImpl.java
//这个类是否会被别的层的类使用,如果会,这个类交给spring容器管理
@Repository
public class UserDaoImpl implements UserDao {
@Override
public int insert() {
System.out.println(“连接mysql数据库,执行insert操作…”);
return 0;
}
} -
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
//DI注入对象,注入方式
@Autowired
private UserDao userDao;
@Override
public void add() {
userDao.insert();
}
} -
UserController.java
/**
- 注解开发里面,如果使用setter注入方式,类中不需要提供setter都可以注入成功
*/
@Controller
public class UserController {
@Autowired
private UserService userService;
public void service(){
userService.add();
}
}
- 注解开发里面,如果使用setter注入方式,类中不需要提供setter都可以注入成功
-
单元测试
@Test
public void service() {
ApplicationContext cnt=new ClassPathXmlApplicationContext(“spring-config.xml”);
UserController controller = cnt.getBean(“userController”, UserController.class);
controller.service();}
当我们再为UserDao接口提供一个新的实现类,代码如下所示:
-
UserDaoImpl2.java
@Repository
public class UserDaoImpl2 implements UserDao {
public UserDaoImpl2() {
System.out.println(“UserDaoImpl2”);
}@Overridepublic int insert() {System.out.println("连接oracle数据库,执行insert操作...");return 0;}
}
重新执行单元测试,会发现控制台爆出异常:
如何解决呢?可以借助@Resource或@Qualifier指定注入对象的名称,代码如下所示:
-
UserServiceImpl.java
@Service
public class UserServiceImpl implements UserService {
@Resource(name = “userDaoImpl2”)//name属性设置注入对象的属性名,如果属性名和name注入的名称一致的,name可以不赋值
private UserDao userDao;
@Override
public void add() {
userDao.insert();
}
}
或
@Service
public class UserServiceImpl implements UserService {@Autowired@Qualifier("userDaoImpl") //默认byName,但是注入属性时不能独立使用,必须和@Autowired一起结合使用private UserDao userDao;@Overridepublic void add() {userDao.insert();}
}
如果,我们希望UserDaoImpl2是一个多例对象,也可以在类上通过@Scope注解注解对象的使用范围:
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Repository
public class UserDaoImpl2 implements UserDao {public UserDaoImpl2() {System.out.println("UserDaoImpl2");}@Overridepublic int insert() {System.out.println("连接oracle数据库,执行insert操作...");return 0;}
}
测试多例模式
@Testpublic void test02() {ApplicationContext cnt=new ClassPathXmlApplicationContext("spring-config.xml");Object o1 = cnt.getBean("userDaoImpl2");Object o2 = cnt.getBean("userDaoImpl2");Object o3 = cnt.getBean("userDaoImpl2");}
控制台输出结果如下图所示:
扩展了解:程序的耦合和解耦
1、藕合和解藕的思路
藕合就是程序间的依赖关系。解藕就是降低程序间的依赖关系。在开发过程中应做到编译期不依赖,运行时再依赖解藕思路:使用反射来创建对象,而不使用new关键字通过读取配置文件来获取要创建的对象全限定名
2、曾经代码的问题
创建java控制台程序
创建Dao类
创建service并调用Dao
创建测试类并调用service
层与层之间的关系通过new来实现调用,并且new对象时需要导入该对象所在的正确的包名,否则报错。这种关系称为藕合关系。
3、工厂类和配置文件解决藕合问题
a、创建properties属性文件
b、创建工厂类
package cn.woniu.utils;import java.io.InputStream;
import java.util.Properties;/*** 对象工厂*/
public class BeanFactory {//定义一个Properties对象private static Properties properties;//使用静态代码块为Properties对象赋值static {try {properties = new Properties();//获取Properties文件流对象InputStream input = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");properties.load(input);} catch (Exception e) {throw new ExceptionInInitializerError("初始化Properties属性出错"+e.getMessage());}}/*** 根据Bean名称获取Bean对象** @param beanName*/public static Object getBean(String beanName) {Object bean = null;try {String beanPath = properties.getProperty(beanName);bean = Class.forName(beanPath).newInstance();} catch (Exception e) {e.printStackTrace();}return bean;}
}
c、使用工厂类调用各层
service调用dao
测试调用service