Spring是一个轻量级的开源框架,它以IOC(控制反转)和AOP(面向切面编程)为核心,Spring诞生的根本目的是解决软件应用开发的复杂性。其本质是一个帮助我们管理对象及其依赖关系的容器,除此之外它还提供了事务控制,动态监督,异常处理等能力,方便解耦,便于简化开发。我们这里只要先知道Spring是干什么的就可以了,接下来我们会详细介绍它的IOC和AOP原理以及基本使用。
现在我们想编写一个用户服务系统,提供一个用于保存账户的方法,用户在UI类中调用相应的方法来实现保存账户的操作。
(1)Dao层IUserDao接口及其实现类IUserDaoImpl
public interface IUserDao {
public void saveAccount();
}
public class IUserDaoImpl implements IUserDao {
@Override
public void saveAccount() {
System.out.println("账户保存了...");
}
}
(2)Service层IUserService接口及其实现类IUserServiceImpl
public interface IUserService {
public void saveAccount();
}
public class IUserServiceImpl implements IUserService {
private IUserDao iUserDao = new IUserDaoImpl();
@Override
public void saveAccount() {
iUserDao.saveAccount();
}
}
(3)用户交互UI类IUserUi
public class IUserUi {
public static void main(String[] args) {
IUserService service = new IUserServiceImpl();
service.saveAccount();
}
}
(1)程序之间存在高耦合,系统不同的模块之间通过大量的new等关键字建立了复杂的关系,这使得我们在修改或者拓展系统业务时,特别是代码量大的项目中,需要不断修改源码,常常面临着令人头皮发麻的连锁反应。
(2)存在严重的编译期依赖。如果我们缺少某些模块,则整个项目将会在编译期就无法通过,另一个侧面也反映了项目中存在高耦合,因此我们应该实现运行期依赖,即在运行期间动态地建立模块之间的关系,而不是写死的。
(1)降低模块之间的耦合度,减少new关键字的使用。这里我们自然而然的想到了工厂模式,可以通过工厂来生产我们所需要的类,从而避免了new关键字的频繁使用。
(2)减少编译期依赖,不把代码写死。我们这里想一下在jdbc中,我们是通过如下方式去注册驱动的:
Class.forName("com.mysql.jdbc.Driver");//此处传入的只是一个字符串而已
此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除mysql的驱动jar包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。同时,也产生了一个新的问题,mysql驱动的全限定类名字符串是在java类中写死的,一旦要改还是要修改源码。所以解决这个问题也很简单,那就是使用配置文件配置。所以综上,我们可以通过解析配置文件,利用反射去动态地维护对象。
(1)基于上述的思路分析,首先我们创建工厂类BeanFactory来根据我们传入的对象名生产我们所需要的各种对象,从而消除new关键字的使用。
(2)那么工厂类怎么通过对象名来动态地创建对象呢?当然是通过反射机制来动态地创建对象,进一步降低耦合度
(3)为了不频繁的创建对象降低系统效率,同时也为了不把代码写死,我们把需要由工厂生产的类都写入配置文件中,让工厂类中的方法通过读取配置文件,把这些对象都创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
(4)那么存哪去呢?我们选择Map来存储创建出来的对象,因为我们不仅需要存,还需要取。因此,我们这里就把这个存放所有对象的Map集合称为我们的容器。这时工厂就变成了专门初始化容器,并负责给我们从容器中获取指定对象的类。
(1)编写配置文件bean.properties (key=value)
userService=com.sdust.service.impl.IUserServiceImpl
userDao=com.sdust.dao.impl.IUserDaoImpl
(2)编写工厂类BeanFactory
public class BeanFactory {
//定义一个Properties对象
private static Properties props;
//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map container;
//使用静态代码块解析配置文件,初始化容器
static {
try{
//实例化对象
props = new Properties();
//获取properties文件的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
//实例化容器
container = new HashMap();
//取出配置文件中所有的Key
Enumeration keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
//取出每个Key
String key = keys.nextElement().toString();
//根据key获取value
String beanPath = props.getProperty(key);
//反射创建对象
Object value = Class.forName(beanPath).newInstance();
//把key和value存入容器中
container.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
}
/**
* 根据bean的名称获取对象(单例对象)
* @param beanName
* @return
*/
public static Object getBean(String beanName){
return container.get(beanName);
}
/**
* 根据Bean的名称获取bean对象(多例对象)
* @param beanName
* @return
public static Object getBean(String beanName){
Object bean = null;
try {
String beanPath = props.getProperty(beanName);
bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象
}catch (Exception e){
e.printStackTrace();
}
return bean;
}*/
}
(3)优化原始代码
public class IUserUi {
public static void main(String[] args) {
//IUserService service = new IUserServiceImpl();
IUserService service = (IUserService) BeanFactory.getBean("userService");
service.saveAccount();
}
}
运行结果无误
(1)原始代码设计中,我们是直接的主动的去获取我们所需要的对象,对象如何产生使用是直接在我们的具体控制范围内的,代码是僵硬的,写死的。
(2)优化后的代码设计中,我们获取对象时,需要跟工厂要,有工厂为我们查找或者创建对象。是被动的。对象的控制权从我们这里转移到了工厂和容器中,这种思想被称为控制反转,这也是Spring的重要核心之一。
public class IUserUi {
public static void main(String[] args) {
//1.获取spring核心容器对象,解析配置文件
/**
* spring核心容器ApplicationContext有三个实现类:
* 1.ClassPathXmlApplicationContext : 他负责加载本项目 类路径 下的配置文件(推荐)
* 2.FileSystemXmlApplicationContext : 他负责加载任意 磁盘路径 下的配置文件(必须有访问权限)
* 3.AnnotationConfigApplicationContext : 他用于加载注解配置
*/
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-beans.xml");
//2.根据key获取bean对象
IUserDao dao = (IUserDao)ac.getBean("userDao");
IUserService service = ac.getBean("userService",IUserService.class);
System.out.println(dao);
System.out.println(service);
}
}
(1)使用默认构造函数创建
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
(2)使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
/**
* 模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
*/
public class InstanceFactory {
public IUserService getAccountService(){
return new IUserServiceImpl();
}
}
(3)使用工厂中的 静态方法 创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
**
* 模拟一个静态工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
*/
public class StaticFactory {
public static IUserService getAccountService(){
return new IUserServiceImpl();
}
}
(1)作用范围
bean标签的scope属性用于指定bean的作用范围。取值如下( 常用的就是单例的和多例的):
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
(2)生命周期
单例对象:
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象;
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
IOC的主要作用是降低程序间的耦合(依赖关系),但不能完全消除依赖关系,所以我们将依赖关系以后都交给spring来维护和管理, 在当前类需要用到其他类的对象时,由spring为我们提供注入,我们只需要在配置文件中说明即可。这种依赖关系的维护就称之为依赖注入(DI)。
能注入的数据:有三类
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean)
复杂类型/集合类型
注入的方式:有三种
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供(后面讲)
(1) 构造函数注入
<1>修改IUserServiceImpl代码,编写构造函数
public class IUserServiceImpl implements IUserService {
private String serviceName;
private Integer serviceId;
private IUserDao userDao;
public IUserServiceImpl(String serviceName,Integer serviceId,IUserDao userDao){
this.serviceName = serviceName;
this.serviceId = serviceId;
this.userDao = userDao;
}
@Override
public void saveAccount() {
userDao.saveAccount();
System.out.println(serviceId + "----" + serviceName);
}
}
<2>修改配置文件,配置构造函数注入
构造函数注入使用的标签是 < constructor-arg > ,标签出现的位置在bean标签的内部,其中标签中的属性如下所示:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型 index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始 name:用于指定给构造函数中指定名称的参数赋值(常用的) =============以上三个用于指定给构造函数中哪个参数赋值=============================== value:直接赋值。用于提供基本类型和String类型的数据 ref:引用对象赋值。用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
(2)Set方法注入
<1>修改IUserServiceImpl代码,生成set方法
public class IUserServiceImpl implements IUserService {
private String serviceName;
private Integer serviceId;
private IUserDao userDao;
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public void setServiceId(Integer serviceId) {
this.serviceId = serviceId;
}
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
@Override
public void saveAccount() {
userDao.saveAccount();
System.out.println(serviceId + "----" + serviceName);
}
}
<2>修改配置文件,配置set方法注入
set方法注入(更常用的方式)涉及的标签为 < property > ,出现的位置在bean标签的内部 ,其标签的属性如下所示:
name:用于指定注入时所调用的set方法名称 value:直接赋值.用于提供基本类型和String类型的数据 ref:引用对象赋值。用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象