spring是一个分层的,开源的,全栈式的(full-stack )、JavaEE轻量级框架,以 IOC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。
IOC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)
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)
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();
}
}
上述代码是一个简单的JDBC连接数据库的小案例,存在的问题在注册驱动的时候,我们平时都是用第二种Class.forName(“com.mysql.jdbc.Driver”); ,这是由于反射的作用,可以在程序运行的时候新建处驱动类,而不用在编译时就开始新建出这个类,这样做的好处就是解耦了类和类之间的关系,减低耦合度。像第二种建立驱动类的方法,在编译时,驱动类Driver和DriverManager就会相关联起来,如果一方缺少jiar包,就会导致编译期过不去,也就不能达到减低耦合度的效果。
/**
* 持久层接口
*/
public interface IAccountDao {
public void save();
}
public class IAccountImpl implements IAccountDao{
public void save() {
System.out.println("保存了账户...");
}
}
public class IAccountServiceImpl implements IAccountService{
private IAccountDao dao = new IAccountImpl();
public void save() {
dao.save();
}
}
/**
* 业务层接口
*/
public interface IAccountService {
public void save();
}
测试结果
问题:
发现类和类之间的依赖关系非常密切,如service依赖dao层,如 private IAccountDao dao = new IAccountImpl();
视图层依赖service层,如 IAccountService service = new IAccountServiceImpl();程序的耦合度较高。
解决方案:
将类和类之间的依赖关系使用配置文件进行存放,运用反射原理,在运行期,将依赖建立起来,这样就可以降低了类之间在编译器的依赖关系。
准备配置文件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是同一个,多例的使用场景一般就在这,是为了防止多个线程操作同一个属性,导致类的属性发生多次改变。
但是我们案例中并没有多例场景的,因此并不需要创建多个对象,所以可将多例改为单例。步骤如下:
bean = Class.forName(beanPath).newInstance();
public class BeanFactory {
private static Properties properties;
//定义一个Map容器,存放创建的对象
private static Map beans;
static {
try {
//1.加载配置文件
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("account.properties");
//2.建立Properties对象
properties = new Properties();
//加载配置文件进内存
properties.load(is);
//-----------------------多例--->单例-----------------------------
//实例化容器
beans = new HashMap();
//获取配置对象所有的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);
}
}
配置文件:引入IOC(控制反转)的概念:
通过一张图来理解:
左上角是通过new 关键字来创建对象,右下角是通过一个工厂通过反射来创建对象,他们之间的区别可以从上图就可以看出来:
那这个和控制反转又有什么区别呢?
解释:如果程序选择了第一种方式创建对象,则程序就拥有了主动的控制权,可以主动的向内存申请资源。如果使用第二种工厂方式创建对象,则程序就将自己的主动权交予工厂,由工厂来向内存申请资源创建对象,这种主动权的转移又称为控制反转。
百度百科解释:控制反转(Inversion of Control,英文缩写为loC)把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入(Dependency Injection,简称Dl)和依赖查找(Dependency Lookup)。
明确IOC的作用:
削减计算机之间的耦合,降低程序代码中的依赖关系。
环境搭建
在2的工厂模式解耦的基础上修改,使用将使用spring提供的工厂进行反射创建对象,首先删除BeanFactory工厂类,保留dao层,service层和ui层即可。开始导入依赖:
jar
org.springframework
spring-context
5.0.2.RELEASE
接着在resource下准备配置文件bean.xml(可随意命名)
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 的三个实现类
核心容器引发出的问题
由继承树可以发现我们上面使用的工厂解耦的工厂类BeanFactory是一个顶级的接口,我们上面使用的AppcationContext创建的核心容器和BeanFactory又有什么区别呢?
bean对象的三种创建方式,在2的基础上修改
选择配置方式1创建bean对象
如果IAccountServiceImpl类缺少默认的构造函数,则会爆出异常:
No default constructor found;
方式2:在方式1的基础上注释bean.xml的配置信息。思考,如果想要从某个jar包的类中获取方法返回值的信息,进行对象创建,前提:该类没有默认的构造函数bean对象又该如何创建,并且jar包不可修改源码?
/**
* Created by liuzeyu on 2020/4/19.
* 模拟一个工厂类,该类可能存在于jar包中,我们无法通过修改源码的方式来对其提供默认构造函数
*/
public class InstanceFactory {
public IAccountService getAccountService(){
return new IAccountServiceImpl();
}
}
方式3:如果工厂类静态方法返回值应当如何获取?
public static IAccountService getStaticAccountService(){
return new IAccountServiceImpl();
}
bean对象的作用范围
bean的作用范围由bean标签的scope所指定,单例和多例较常用
通过代码演示:
可见bean对象就被创建了一次,bean的作用范围默认就是单例的。
修改bean的scope属性,使得创建的bean对象是多例的。
再次运行上面图片中的代码
最后什么是global-session呢?请看图:
由于用户访问流量过大,公司服务器采用集群方式,上述则是用户访问集群服务器请求登录,用户首先访问到服务器192.168.0.1,发现其是空闲状态,其余都在忙碌,则将用户的验证码存入session域中,第二次再次访问输入验证码(如果没过期),此时服务器1被无情占用了,但是服务器6已经空闲可使用,但是6上面已经不存在session内的验证码,此时又该怎么办?
答:global-session出场,所有的服务器共享一个session域,这样就不会出现上述的情况了。
bean对象的生命周期
单例对象
总结:对象的使用周期和容器相同
多例对象
测试:
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();
}
}
测试函数: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();
}
}
IOC的作用:降低程序间的耦合程度(依赖程度)
依赖关系的管理:以后都交给spring来维护,在当前类中我们需要用到其它类的对象,都由spring提供,我们只需要在配置文件中说明即可。
依赖注入:依赖关系的维护。
依赖注入的数据有三类:
注入的方式有三种
使用构造函数注入:
为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:用于给指定构造函数中指定的名称的参数赋值
=以上三个用于指定给构造函数中那个参数赋值===
4)value:用于提供基本数据类型和String类型数据
5)ref:用于指定其它的bean类型数据,它是指就是在spring的IOC核心容器中出现过的bean对象
优势:
在获取bean对象时,注入是必须的操作,因为已经没有了默认构造函数,因此注入时必须的,否则无法操作成功。
劣势:
改变了bean对象的实例方式,是我们在创建对象时,如果用不到这些数据,也必须提供。
3.输出结果
使用set函数注入
/**
* 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);
}
}
复杂类型/集合类型的注入问题
package com.liuzeyu.service.impl;
import com.liuzeyu.service.IAccountService;
import java.util.*;
/**
* set注入方法
*/
public class IAccountServiceImpl3 implements IAccountService {
private String[] mystr;
private List mylist;
private Set myset;
private Map myMap;
private Properties myProps;
public void setMystr(String[] mystr) {
this.mystr = mystr;
}
public void setMylist(List mylist) {
this.mylist = mylist;
}
public void setMyset(Set myset) {
this.myset = myset;
}
public void setMyMap(Map 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);
}
}
AAA
BBB
CCC
111
222
333
aaa
bbb
ccc
三七
三八
三九
说明:其中list和set均属于数据类型,因此可以互相替换使用,如: