深入浅出 Spring 框架,原来以前的都白学了

1. 为啥要用 Spring
张三是一个编程小白,他每次在 service 层写代码都要自己 new 一堆 Dao 接口的实现类。

有一天正 new 着对象,张三心想:"我这一个 service 都需要 new 好多 Dao ,那如果有一堆 service ,那我不得花费好长时间?"
"有没有一个工具类或者什么框架能帮我管理这些对象?我只需要配置一下,需要的时候它就能自动帮我 new 个对象出来?"
张三陷入了深深的沉思之中。
张三的室友李四也是一个编程小白。
李四呢想给自己的小项目增加一个功能:记录方法执行的时间。结果他脑子一热竟然给所有的方法都增加了一堆打印方法:

过了半个小时,李四终于给项目中所有的方法都复制粘贴上了打印语句。他长舒一口气:"我真是个大聪明!"

张三看了一眼李四的代码,连连鼓掌:"妙啊!咱们宿舍的技术大神!"
旁边的王五实在忍不住了,对张三说:"妙个屁!最近的 Spring 框架课你俩是不是都没去?光顾着打游戏了?我都替你俩答了三次到了!"
李四问王五:"这个Spring 框架学了有用吗?"
王五:"不仅能解决张三说的管理对象的问题,还能帮你解决记录日志的问题。配置完 Spring ,你只需要定义一个切面类,根本不需要在一堆类上面复制粘贴一堆代码。"
张三摸摸后脑勺笑着说:"原来 Spring 框架那么好用,我以后再也不逃课了。我这就去翻课本学习 Spring 框架去。"
2. Spring 简介
Spring 是一个轻量级的 Java 开发框架。Spring 的核心是控制反转(IOC)和面向切面编程(AOP)。
Spring 主要有如下优点:
1.解耦
2.支持面向切面编程
3.便于集成其他框架
3. 环境搭建
1.创建 Maven 项目
File -> New -> Project -> Maven




UserServiceImpl
publicclassUserServiceImplimplementsUserService{@Overridepublicvoidprint(){ System.out.println("hello world"); }}
4.创建配置文件
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
5.测试
@TestpublicvoidtestSpring(){// 1、获取工厂ApplicationContext act =newClassPathXmlApplicationContext("/applicationContext.xml");// 2、通过工厂类获得对象UserService userService = (UserService)act.getBean("userService");// 3.调用方法userService.print();}
测试结果:

4. IOC
4.1 IOC 简介
IOC,全称 Inversion of Control,意思是控制反转。它是 Spring 框架中的一种思想。
控制反转就是将对象的控制权从程序中的代码转移到了 Spring 的工厂,通过 Spring 的工厂完成对象的创建以及赋值。

也就是说之前是我们自己 new 对象、给对象中的成员变量赋值。现在是让 Spring 来帮助我们创建对象、给成员变量赋值。

4.2 Spring 核心内容描述
1.配置文件
Spring 的配置文件可以放到项目中的任意一个地方,也可以随意命名,但是建议使用:applicationContext.xml。
你可以将这个配置文件看成一个装有一堆 bean 标签的容器。
2.bean 标签
Spring 工厂创建的对象,叫做 bean,所以一个 bean 标签代表一个对象。
bean 标签中必须要有 class 属性,它的值是一个类的全限定名(包名+类名)。
除了 class 属性,bean 标签还可以设置 id 、name 、scope属性。
id:
id 必须以字母开头,相当于这个 bean 的身份证号,是唯一的。
如果这个 bean 只使用一次,id 可以省略不写。
如果这个 bean 需要被其他 bean 引用,或者这个 bean 要使用很多次,则必须要有 id 属性。
如果只配置 class 属性,Spring 框架会给每一个 bean 配置一个默认的 id:"全限定名#1"。
例如:
com.xxl.service.impl.UserServiceImpl#1
name:
name 相当于这个 bean 的别名,它可以配置多个,例如:
scope:
scope 属性可以控制简单对象的创建次数,它有两个值:
1.singleton:每次只会创建唯一⼀个简单对象,默认值。
2.prototype:每⼀次都会创建新的对象。
例如:
3.ApplicationContext
ApplicationContext 是 Spring 的工厂,主要用来创建对象。
Spring 通过读取配置文件创建工厂。

因为 Spring 的工厂会占用大量内存,所以一个程序一般只会创建一个工厂对象。
4.工厂常用方法
1.根据 id 获取对象
UserServiceuserService = (UserService)act.getBean("userService");
2.根据 id 和类名获取对象
UserService userService = (UserService)act.getBean("userService",UserService.class);
3.只根据类名获取对象
UserService userService = (UserService)act.getBean(UserService.class);
4.获取配置文件中所有 bean 标签的 id 值
String[] beanDefinitionNames = act.getBeanDefinitionNames();for(StringbeanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName);}
结果:

5.判断是否存在指定 id 或者 name 的 bean
act.containsBean("userService")
6.判断是否存在指定 id 的 bean,只能用来判断 id
act.containsBeanDefinition("userService")
5.创建对象
Spring 是如何创建对象的呢?
工厂和反射。
首先说下反射,我们可以通过一个类的全限定名获取 Class 对象,然后再通过 Class 实例化一个对象:
ClassserviceClass = Class.forName("com.xxl.service.impl.UserServiceImpl");UserServiceuserService = (UserService)serviceClass.newInstance();
Spring 配置文件中 bean 标签的 id 和类的全限定名一一对应,所以 Spring 工厂的 getBean 方法其实就是先根据 bean 的 id 获取该类的全限定名,然后再利用反射根据类的全限定名创建对象并返回。

4.3 IOC 优点
解耦。
说起解耦之前先说下耦合:耦合是指代码之间的关联性太强,我如果改了这一段代码,可能会影响到一堆代码。
那创建对象哪里有耦合了?其实就是new关键字带来的耦合。
如果你发现一个接口的实现类需要修改,你需要手动改动程序中的代码,比如修改 new 关键字后面的实现类,这样可能会影响到其他的代码。
但是使用了 Spring 之后,我们只需要修改配置文件中 bean 标签的 class 属性对应的类的全限定名,不用修改程序中的代码,这样就做到了解耦。
解耦就是解除不同代码之间的关联性、依赖性。
5. DI
DI 全称 Dependency Injection,意思是依赖注入,它是 IOC 的具体实现。
依赖就是说我需要你,比如 Service 层依赖 Dao 层,注入就是赋值。
依赖注入:使用 Spring 的工厂和配置文件为一个类的成员变量赋值。
没有使用 Spring 的依赖注入我们是这样赋值的:
User user =newUser();user.setName("张三");
如果设置有误,就需要手动修改代码,代码耦合度较高,而依赖注入的出现就是为了解耦。
Spring 的依赖注入包含两种方式:
5.1 set 注入
set 注入:Spring 调用 Set 方法通过配置文件为成员变量赋值。
1.创建对象,为属性添加 set/get 方法
publicclassUser{privateString name;privateintage;publicStringgetName(){returnname; }publicvoidsetName(String name){this.name = name; }publicintgetAge(){returnage; }publicvoidsetAge(intage){this.age = age; }}
2.修改配置文件
3.测试
// 1、获取工厂ApplicationContext act =newClassPathXmlApplicationContext("/applicationContext.xml");// 2、通过工厂类获得对象User user = (User)act.getBean("user");System.out.println("姓名:"+user.getName());System.out.println("性别:"+user.getAge());
测试结果:

从上面的例子可以看出 Set 注入就是在 property 标签中为属性赋值。spring 可以为 JDK 内置的数据类型进行赋值,也可以为用户自定义的数据类型进行赋值。
5.1.1 JDK 内置数据类型
1.基本类型
2.List 集合
157999999181578888881915766666620
3.Set 集合
157999999181578888881915766666620
4.Map 集合
name知否君age23
5.数组
157999999181578888881915766666620
6.Properites
value1value2
5.1.2 用户自定义数据类型
1.为成员变量添加 set/get 方法
publicclassUserServiceImplimplementsUserService{privateUserDao userDao;publicUserDaogetUserDao(){returnuserDao; }publicvoidsetUserDao(UserDao userDao){this.userDao = userDao; }@Overridepublicvoidprint(){ userDao.print(); }}
2.bean 标签使用 ref 属性
3.测试
@TestpublicvoidtestSpring(){// 1、获取工厂ApplicationContext act =newClassPathXmlApplicationContext("/applicationContext.xml");// 2、通过工厂类获得对象UserService userService = (UserService)act.getBean("userService");// 3.调用方法userService.print();}
测试结果:

解释:
上面的例子中,因为 userDao 是 userService 的一个成员变量,所以在配置文件中需要使用 property 标签,ref 指向了 userDao 这个对象,然后调用 userDao 的 set 方法为 userDao 赋值。

4.自动注入
我们还可以使用 bean 标签的 autowire 属性为自定义变量自动赋值。当类中引用类型的属性名和 bean 标签的 id 值相同时,我们可以使用 byName。例如:
当类中引用类型的全限定名和 bean 标签的 class 属性的值相同,或者是子类、实现类,我们可以使用 byType。例如:
5.2 构造注入
构造注入:Spring 调用构造方法通过配置文件为成员变量赋值。
1.为类添加构造方法
publicclassUser{privateString name;privateintage;publicUser(String name,intage){this.name = name;this.age = age; }publicStringgetName(){returnname; }publicvoidsetName(String name){this.name = name; }publicintgetAge(){returnage; }publicvoidsetAge(intage){this.age = age; }}
2.修改配置文件
在 bean 标签中使用 constructor-arg 标签。
3.测试
@TestpublicvoidtestSpring(){// 1、获取工厂ApplicationContext act =newClassPathXmlApplicationContext("/applicationContext.xml");// 2、通过工厂类获得对象User user= (User)act.getBean(User.class); System.out.println("姓名:"+user.getName()); System.out.println("年龄:"+user.getAge());}
测试结果:

5.3 注入总结
注入就是通过 Spring 的配置文件为类的成员变量赋值。在实际开发中,我们一般采用 Set 方式为成员变量赋值。