spring 框架学习安排
目录结构
1. 第一天:
1. spring 框架概述
1. spring 是什么
spring是一个分层的,开源的,全栈式的(full-stack )、JavaEE轻量级框架,以 <mark>IOC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程</mark>为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。
2. spring 的两大核心
IOC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)
3. 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)
4. spring 的优势(由浅如深,慢慢体会)
- 方便解耦,简化开发
通过 Spring提供的 IoC容器,可以将对象间的依赖关系交由 Spring进行控制,避免硬编码所造 成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可 以更专注于上层的应用。 - AOP编程的支持
通过 Spring的 AOP 功能,方便进行面向切面的编程,许多不容易用传统OOP 实现的功能可以
通过 AOP 轻松应付。 - 声明式事务的支持
可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理, 提高开发效率和质量。 - 方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可 做的事情。 - 方便集成各种优秀框架
Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持 - 降低 JavaEE API的使用难度
Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的 使用难度大为降低。 - Java源码是经典学习范例
Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以 及对 Java技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
5. spring 的体系结构
2. 程序的耦合及解耦
1. 曾经案例中存在的问题
public class TestJdbc {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
//1.注册驱动
// DriverManager.registerDriver(new Driver());//编译时和驱动类关联
Class.forName("com.mysql.jdbc.Driver"); // 常用:运行时和驱动类关联,降低类之间的耦合度
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql:///spring_eesy", "root", "809080");
//3.获取执行SQL预处理对象
PreparedStatement pstm = conn.prepareStatement("select * from account");
//4.执行SQL
ResultSet rst = pstm.executeQuery();
//遍历
while(rst.next()){
String name = rst.getString("name");
System.out.println(name);
}
rst.close();
conn.close();
pstm.close();
}
}
- 程序的耦合:程序间的依赖关系
- 包括
- 类之间的依赖关系
- 方法之间的依赖关系
- 包括
- 解耦:降低程序间的依赖关系
- 实际开发中
- 应该做到:编译时不依赖,运行时依赖
- 解耦的思路:
- 通过读取配置文件的过程来创建对象的全限定类名,使用反射到创建对象,避免使用new关键字
上述代码是一个简单的JDBC连接数据库的小案例,存在的问题在注册驱动的时候,我们平时都是用第二种Class.forName(“com.mysql.jdbc.Driver”); ,这是由于反射的作用,可以在程序运行的时候新建处驱动类,而不用在编译时就开始新建出这个类,这样做的好处就是解耦了类和类之间的关系,减低耦合度。像第二种建立驱动类的方法,在编译时,驱动类Driver和DriverManager就会相关联起来,如果一方缺少jiar包,就会导致编译期过不去,也就不能达到减低耦合度的效果。
2. 工厂模式解耦
- 模拟三层架构
-
dao层
/** * 持久层接口 */ public interface IAccountDao { public void save(); }
public class IAccountImpl implements IAccountDao{ public void save() { System.out.println("保存了账户..."); } }
-
service层
public class IAccountServiceImpl implements IAccountService{ private IAccountDao dao = new IAccountImpl(); public void save() { dao.save(); } }
/** * 业务层接口 */ public interface IAccountService { public void save(); }
-
视图层
-
测试结果
<mark>问题:</mark>
发现类和类之间的依赖关系非常密切,如service依赖dao层,如 private IAccountDao dao = new IAccountImpl();
视图层依赖service层,如 IAccountService service = new IAccountServiceImpl();程序的耦合度较高。
<mark>解决方案:</mark>
将类和类之间的依赖关系使用配置文件进行存放,运用反射原理,在运行期,将依赖建立起来,这样就可以降低了类之间在编译器的依赖关系。
-
- 降低三层架构的耦合度(反射)
-
准备配置文件account.properties,存放dao层的实现类和service层的实现类的全限定类名
accountService=com.liuzeyu.service.impl.IAccountServiceImpl accountDao=com.liuzeyu.dao.impl.IAccountImpl
-
准备工具类BeanFactory,用户加载配置文件中的信息进内存
public class BeanFactory { private static Properties properties; static { try { //1.加载配置文件 InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("account.properties"); //2.建立Properties对象 properties = new Properties(); //加载配置文件进内存 properties.load(is); } catch (IOException e) { throw new ExceptionInInitializerError("加载配置文件失败"); } } public static Object getBean(String beanName){ Object bean = null; try { String beanPath = properties.getProperty(beanName); bean = Class.forName(beanPath).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return bean; } }
-
改造service层,视图层建立对象的方法
视图层:public class UITest { public static void main(String[] args) { IAccountService service = (IAccountService)BeanFactory.getBean("accountService"); service.save(); } }
service层:
public class IAccountServiceImpl implements IAccountService{ private IAccountDao dao = (IAccountDao)BeanFactory.getBean("accountDao"); public void save() { dao.save(); } }
-
测试结果
-
- 仔细分析上述案例存在的缺陷
经测试可以分析出来,这是一个多例(多个对象被创建)的案例。如何分析呢?-
视图层多次创建service实现类,发现创建出来的都是不同的对象
存在的问题:但是由于service层是线程安全,如何看出是线程安全的? -
假设方法里面有可以修改类属性的操作 ,i
可以发现每次创建的service是同一个,多例的使用场景一般就在这,是为了防止多个线程操作同一个属性,导致类的属性发生多次改变。 -
但是我们案例中并没有多例场景的,因此并不需要创建多个对象,所以可将多例改为单例。步骤如下:
- 将类BeanFactory中的bean对象创建一次后存起来
bean = Class.forName(beanPath).newInstance();
- 操作静态代码块
配置文件:public class BeanFactory { private static Properties properties; //定义一个Map容器,存放创建的对象 private static Map<String,Object> beans; static { try { //1.加载配置文件 InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("account.properties"); //2.建立Properties对象 properties = new Properties(); //加载配置文件进内存 properties.load(is); //-----------------------多例--->单例----------------------------- //实例化容器 beans = new HashMap<String,Object>(); //获取配置对象所有的key Enumeration keys = properties.keys(); //遍历枚举 while(keys.hasMoreElements()){ //取出每个key String key = keys.nextElement().toString(); //根据key获取value值 String beanPath = properties.getProperty(key); //反射创建对象 Object value = Class.forName(beanPath).newInstance(); beans.put(key,value); } } catch (IOException e) { throw new ExceptionInInitializerError("加载配置文件失败"); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } // public static Object getBean(String beanName){ // Object bean = null; // try { // String beanPath = properties.getProperty(beanName); // bean = Class.forName(beanPath).newInstance(); // // } catch (InstantiationException e) { // e.printStackTrace(); // } catch (IllegalAccessException e) { // e.printStackTrace(); // } catch (ClassNotFoundException e) { // e.printStackTrace(); // } // return bean; // } //根据bean的名称获取对象 public static Object getBean(String beanName){ return beans.get(beanName); } }
这样就可以加载一次Class就创建对象存入准备好容器,实现单例模式。
- 将类BeanFactory中的bean对象创建一次后存起来
-
3. IOC解耦和spring中的IOC
1. IOC(控制反转)
引入IOC(控制反转)的概念:
通过一张图来理解:
左上角是通过new 关键字来创建对象,右下角是通过一个工厂通过反射来创建对象,他们之间的区别可以从上图就可以看出来:
- 如果使用new关键字则是程序通过new直接访问内存中的对象资源
- 如果使用工厂,想要获取内存中的资源,就必须经过工厂提供资源,即这个工厂通过加载配置文件反射创建出对象
那这个和控制反转又有什么区别呢?
解释:如果程序选择了第一种方式创建对象,则程序就拥有了主动的控制权,可以主动的向内存申请资源。如果使用第二种工厂方式创建对象,则程序就将自己的主动权交予工厂,由工厂来向内存申请资源创建对象,这种主动权的转移又称为控制反转。
百度百科解释:控制反转(Inversion of Control,英文缩写为loC)把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入(Dependency Injection,简称Dl)和依赖查找(Dependency Lookup)。
<mark>明确IOC的作用:</mark>
削减计算机之间的耦合,降低程序代码中的依赖关系。
2.spring中基于XML的IOC环境搭建
-
环境搭建
-
在2的工厂模式解耦的基础上修改,使用将使用spring提供的工厂进行反射创建对象,首先删除BeanFactory工厂类,保留dao层,service层和ui层即可。开始导入依赖:
<packaging>jar</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency> </dependencies>
-
接着在resource下准备配置文件bean.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,把对象交给spring来管理--> <bean id="accountDao" class="com.liuzeyu.dao.impl.IAccountImpl"/> <bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl"/> </beans>
-
UI中开始测试工厂创建的对象
public class UITest { public static void main(String[] args) { IAccountService service = new IAccountServiceImpl(); //1.获取spring核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取bean对象 //2.1方式1获取 IAccountService aservice = (IAccountService) ac.getBean("accountService"); //2.2方式2获取 IAccountDao adao = ac.getBean("accountDao", IAccountDao.class); System.out.println(aservice); System.out.println(adao); } }
-
-
ApplicationContext 的三个实现类
- ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话是加载不了的(更常用)。
- FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件,但必须拥有访问权限。
- AnnotationConfigApplicationContext:用于读取注解创建容器的。
查看实现类:
-
核心容器引发出的问题
由继承树可以发现我们上面使用的工厂解耦的工厂类BeanFactory是一个顶级的接口,我们上面使用的AppcationContext创建的核心容器和BeanFactory又有什么区别呢?- AppcationContext:它在构建核心容器时,创建对象的策略采用立即加载的方式,只要配置文件已加载成功,就马上创建配置文件的配置对象,为避免多线程带来的安全问题,通常适用于单例模式。
- BeanFactory,它在构建核心容器时,创建对象的方式采用延迟加载的方式,也就是说,什么时候根据id获取对象,什么时候才真正创建对象,该方法避免了多线程带来的安全问题,所以适用于多例模式。
- debug方式测试立即加载和延迟加载:
- 在service的实习类中添加一个构造函数:
开始debug
发现是执行完创建核心容器就立即输出了 “IAccountService创建了” - BeanFactory创建对象
发现加载配置文件后并未输出“IAccountService创建了”,而是运行到getBean函数后才创建的IAccountService对象
- 在service的实习类中添加一个构造函数:
3.spring中三种创建bean对象的方法
-
bean对象的三种创建方式,在2的基础上修改
-
方式1:删除dao层及相关的代码
-
选择配置方式1创建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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--bean对象的三种创建方式 --> <!--方式1: 配置bean标签,添加id和class属性,没有添加其他的属性,则是采用默认的构造函数 创建bean对象,如果类中没有这一默认的构造函数,则无法创建对象--> <bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl"/> </beans>
如果IAccountServiceImpl类缺少默认的构造函数,则会爆出异常:
No default constructor found;
-
方式2:在方式1的基础上注释bean.xml的配置信息。思考,如果想要从某个jar包的类中获取方法返回值的信息,进行对象创建,前提:该类没有默认的构造函数bean对象又该如何创建,并且jar包不可修改源码?
- 模拟一个工厂类,返回IAccountService
/** * Created by liuzeyu on 2020/4/19. * 模拟一个工厂类,该类可能存在于jar包中,我们无法通过修改源码的方式来对其提供默认构造函数 */ public class InstanceFactory { public IAccountService getAccountService(){ return new IAccountServiceImpl(); } }
- bean.xml配置
<!--方式2:使用普通工厂的方式创造对象(使用某个类中的方法创建对象,并存入spring容器)--> <bean id="InstanceFactory" class="com.liuzeyu.factory.InstanceFactory"/> <bean id="accountService" factory-bean="InstanceFactory" factory-method="getAccountService"/>
- 模拟一个工厂类,返回IAccountService
-
方式3:如果工厂类静态方法返回值应当如何获取?
- 为模拟工厂类添加静态方法
public static IAccountService getStaticAccountService(){ return new IAccountServiceImpl(); }
- bean.xml配置
<!--方式3:使用普通工厂的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)--> <bean id="accountService" class="com.liuzeyu.factory.InstanceFactory" factory-method="getStaticAccountService"/>
- 为模拟工厂类添加静态方法
-
-
bean对象的作用范围
bean的作用范围由bean标签的scope所指定,单例和多例较常用- singleton:单例(默认)
- prototype:多例
- request:作用与web应用的请求范围
- session:作用与web应用的会话范围
- global-session:作用于集群服务器的session会话范围,当不是集群时,它就是单纯的session范围
通过代码演示:
可见bean对象就被创建了一次,bean的作用范围默认就是单例的。
修改bean的scope属性,使得创建的bean对象是多例的。<bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl" scope="prototype"/>
再次运行上面图片中的代码
<mark>最后什么是global-session呢?请看图:</mark>
由于用户访问流量过大,公司服务器采用集群方式,上述则是用户访问集群服务器请求登录,用户首先访问到服务器192.168.0.1,发现其是空闲状态,其余都在忙碌,则将用户的验证码存入session域中,第二次再次访问输入验证码(如果没过期),此时服务器1被无情占用了,但是服务器6已经空闲可使用,但是6上面已经不存在session内的验证码,此时又该怎么办?
答:global-session出场,所有的服务器共享一个session域,这样就不会出现上述的情况了。
-
bean对象的生命周期
-
单例对象
- 出生:随着配置文件加载后,容器创建bean对象也就创建
- 活着:只要容器还在,bean对象就一直都在
- 死亡:容器销毁,bean对象销毁
总结:对象的使用周期和容器相同
-
多例对象
- 出生:什么时候使用对象,spring框架就为我们创建
- 活着:对象只要在使用过程中就一直活着
- 死亡:当对象长时间不用,并没有对象引用时,又Java的GC回收
测试:
- 为service的实现类添加init(),destory()方法
- 添加bean属性init-method,destroy-method
<bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl" scope="prototype" init-method="init" destroy-method="destory"/>
- 测试UITest函数
- 多例:
public class UITest { public static void main(String[] args) { //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取bean对象 //2.1方式1获取 IAccountService aservice = (IAccountService) ac.getBean("accountService"); ClassPathXmlApplicationContext ca= (ClassPathXmlApplicationContext)ac; ca.close(); } }
发现destory函数并未执行,所以bean对象也并未销毁,最后由GC回收。 - 单例:
测试函数:<bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl" scope="prototype" init-method="init" destroy-method="destory"/>
public class UITest { public static void main(String[] args) { //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取bean对象 //2.1方式1获取 IAccountService aservice = (IAccountService) ac.getBean("accountService"); ClassPathXmlApplicationContext ca= (ClassPathXmlApplicationContext)ac; ca.close(); } }
可见单例对象是可以通过close关闭的。
- 多例:
-
4. 依赖注入(Dependency Injection)
IOC的作用:降低程序间的耦合程度(依赖程度)
依赖关系的管理:以后都交给spring来维护,在当前类中我们需要用到其它类的对象,都由spring提供,我们只需要在配置文件中说明即可。
依赖注入:依赖关系的维护。
依赖注入的数据有三类:
- 基本数据类型和String
- 其它bean类型(在配置文件中或注解配置过的bean)
- 复杂类型/集合类型
注入的方式有三种
- 第一种:使用构造函数提供
- 使用set方法提供
- 使用注解提供
-
使用构造函数注入:
-
为service的实现类添加带参构造
package com.liuzeyu.service.impl; import com.liuzeyu.service.IAccountService; import java.util.Date; public class IAccountServiceImpl implements IAccountService{ //添加属性,如果是经常变化的数据,并不适用于注入的方式 private String name; private Integer id; private Date birthday; public IAccountServiceImpl(String name, Integer id, Date birthday) { this.name = name; this.id = id; this.birthday = birthday; } public void save() { System.out.println("save()..."+"姓名:"+name+" id:"+id+" 生日:"+birthday); } }
-
此时bean.xml应如何修改才能创建对象呢?
构造函数的注入:
使用标签:construct-arg
使用位置:bean标签内部
标签中的属性:
1)type:用户指定要注入的数据的数据类型,该数据类型也是构造函数中某些参数的数据类型
2)index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,该索引从0开始
3)name:用于给指定构造函数中指定的名称的参数赋值
=<mark><mark><mark><mark><mark><mark><mark><mark><mark>以上三个用于指定给构造函数中那个参数赋值</mark></mark></mark></mark></mark></mark></mark></mark></mark>===
4)value:用于提供基本数据类型和String类型数据
5)ref:用于指定其它的bean类型数据,它是指就是在spring的IOC核心容器中出现过的bean对象
优势:
在获取bean对象时,注入是必须的操作,因为已经没有了默认构造函数,因此注入时必须的,否则无法操作成功。
劣势:
改变了bean对象的实例方式,是我们在创建对象时,如果用不到这些数据,也必须提供。
3.输出结果
-
-
使用set函数注入
- 修改service实现类
/** * set注入方法 */ public class IAccountServiceImpl2 implements IAccountService { //添加属性,如果是经常变化的数据,并不适用于注入的方式 private String name; private Integer id; private Date birthday; public void setName(String name) { this.name = name; } public void setId(Integer id) { this.id = id; } public void setBirthday(Date birthday) { this.birthday = birthday; } public void save() { System.out.println("save()..."+"姓名:"+name+" id:"+id+" 生日:"+birthday); } }
- 修改bean.xml文件
涉及到的标签property
出现的位置,bean标签内部
标签的属性:
1)name:注入所调用的set方法的名称,其实是所关联JavaBean的属性。
2)value:提供基本类型和String类型。
3)ref:其它的bean类型数据,用于指向spring的IOC创建出的bean对象 。
优势:创建对象时没有明确的限制,可以直接使用默认构造函数
劣势:如果要使某个成员必须有值,则获取对象时有可能没set进去,因为它没有了默认构造函数的约定<?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="now" class="java.util.Date"/> <bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl2"> <property name="name" value="liuzeyu"/> <property name="id" value="37"/> <property name="birthday" ref="now"/> </bean> </beans>
- 修改service实现类
-
复杂类型/集合类型的注入问题
- 准备service的实现类
package com.liuzeyu.service.impl; import com.liuzeyu.service.IAccountService; import java.util.*; /** * set注入方法 */ public class IAccountServiceImpl3 implements IAccountService { private String[] mystr; private List<String> mylist; private Set<String> myset; private Map<String,String> myMap; private Properties myProps; public void setMystr(String[] mystr) { this.mystr = mystr; } public void setMylist(List<String> mylist) { this.mylist = mylist; } public void setMyset(Set<String> myset) { this.myset = myset; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public void setMyProps(Properties myProps) { this.myProps = myProps; } public void save() { System.out.println(Arrays.toString(mystr)); System.out.println(mylist); System.out.println(myset); System.out.println(myMap); System.out.println(myProps); } }
- 修改bean.xml文件
说明:其中list和set均属于数据类型,因此可以互相替换使用,如:<?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="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl3"> <property name="mystr"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <property name="mylist"> <list> <value>111</value> <value>222</value> <value>333</value> </list> </property> <property name="myset"> <set> <value>aaa</value> <value>bbb</value> <value>ccc</value> </set> </property> <property name="myMap"> <map> <entry key="37" value="liuzeyu"/> <entry key="36" value="wuzhitao"/> <entry key="39" value="qiuyuxiang"/> </map> </property> <property name="myProps"> <props> <prop key="37">三七</prop> <prop key="38">三八</prop> <prop key="39">三九</prop> </props> </property> </bean> </beans>
但是set和list却不可以互换。
Map和Property也可以互换,如
- 准备service的实现类