有一个效应叫知识诅咒:自己一旦知道了某事,就无法想象这件事在未知者眼中的样子。
今天所学的Spring其实是Spring家族中的Spring Framework;
Spring Fra是Spring家族中其他框架的底层基础,学好Spring可以为其他Spring框架的学习打好基础
(1)核心层
Core Container:核心容器,这个模块是Spring最核心的模块,其他的都需要依赖该模块
(2)AOP层
AOP:面向切面编程,它依赖核心层容器,目的是在不改变原有代码的前提下对其进行功能增强
Aspects:AOP是思想,Aspects是对AOP思想的具体实现
(3)数据层
Data Access:数据访问,Spring全家桶中有对数据访问的具体实现技术
Data Integration:数据集成,Spring支持整合其他的数据层解决方案,比如Mybatis
Transactions:事务,Spring中事务管理是Spring AOP的一个具体实现,也是后期学习的重点内容
(4)Web层
这一层的内容将在SpringMVC框架具体学习
(5)Test层
Spring主要整合了Junit来完成单元测试和集成测试
在Spring核心概念这部分中主要包含IOC/DI、IOC容器和Bean,那么问题就来了,这些都是什么呢?
我们想,如果能把框中的内容给去掉,不就可以降低依赖么,但是又会引入新的问题,去掉以后能运行么?
答案肯定是不行,因为bookDao没有赋值为Null,强行运行就会出空指针异常。
所以现在的问题就是,业务层不想new对象,运行的时候又需要这个对象,该咋办呢?
针对这个问题,Spring就提出了一个解决方案:
使用对象时,在程序中不要主动用new产生对象,转换为由外部提供对象
介绍完Spring的IOC和DI的概念后,我们会发现这两个概念的最终目标就是:充分解耦
(1)Spring是使用容器来管理bean对象的,那么管什么?
主要管理项目中所使用到的类对象,比如(Service和Dao)
(2)如何将被管理的对象告知IOC容器?
使用配置文件
(3)被管理的对象交给IOC容器,要想从容器中获取对象,就先得思考如何获取到IOC容器?
Spring框架提供相应的接口
(4)IOC容器得到后,如何从容器中获取bean?
调用Spring框架提供对应接口中的方法
(5)使用Spring导入哪些坐标?
用别人的东西,就需要在pom.xml添加对应的依赖
pom.xml
org.springframework
spring-context
5.2.10.RELEASE
junit
junit
4.12
test
创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类
public interface BookDao {
public void save();
}
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
}
public interface BookService {
public void save();
}
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
resources下添加spring配置文件applicationContext.xml,并完成bean的配置
注意事项:bean定义时id属性在同一个上下文中(配置文件)不能重复
public class App {
public static void main(String[] args) {
//步骤6:获取IOC容器,使用Spring提供的接口完成IOC容器的创建,创建App类,编写main方法
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//步骤7 ctx.getBean("bookDao"); //括号里的是配置文件里的Bean.id
// BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// bookDao.save();
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
测试结果为:
Spring的IOC入门案例已经完成,但是在BookServiceImpl的类中依然存在BookDaoImpl对象的new操作,它们之间的耦合度还是比较高,这块该如何解决,就需要用到下面的DI:依赖注入。
(1)要想实现依赖注入,必须要基于IOC管理Bean
DI的入门案例要依赖于前面IOC的入门案例
(2)Service中使用new形式创建的Dao对象是否保留?
需要删除掉,最终要使用IOC容器中的bean对象
(3)Service中需要的Dao对象如何进入到Service中?
在Service中提供方法,让Spring的IOC容器可以通过该方法传入bean对象
(4)Service与Dao间的关系如何描述?
使用配置文件
在BookServiceImpl类中,删除业务层中使用new的方式创建的dao对象
在BookServiceImpl类中,为BookDao提供setter方法
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
在配置文件中添加依赖注入的配置
注意:配置中的两个bookDao的含义是不一样的==
name="bookDao"中bookDao的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的setBookDao()方法进行对象注入
ref="bookDao"中bookDao的作用是让Spring能在IOC容器中找到id为bookDao的Bean对象给bookService进行注入
综上所述,对应关系如下:
前面提过为bean设置id时,id必须唯一,但是如果由于命名习惯而产生了分歧后,该如何解决?
打开spring的配置文件applicationContext.xml
说明:Ebi全称Enterprise Business Interface,翻译为企业业务接口
public class AppForName {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//此处根据bean标签的id属性和name属性的任意一个值来获取bean对象
BookService bookService = (BookService) ctx.getBean("service4");
bookService.save();
}
}
bean依赖注入的ref属性指定bean,必须在容器中存在
如果不存在,则会报错,如下:
获取bean无论是通过id还是name获取,如果无法获取到,将抛出异常NoSuchBeanDefinitionException
验证思路
同一个bean获取两次,将对象打印到控制台,看打印出的地址值是否一致。
在Spring配置文件中,配置scope属性来实现bean的非单例创建
将scope设置为prototype (非单例)
错误信息从下往上依次查看,因为上面的错误大都是对下面错误的一个包装,最核心错误是在最下面
Caused by: java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.
Caused by 翻译为引起,即出现错误的原因
java.lang.NoSuchMethodException:抛出的异常为没有这样的方法异常
com.itheima.dao.impl.BookDaoImpl.
如果最后一行错误获取不到错误信息,接下来查看第二层:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.itheima.dao.impl.BookDaoImpl.
nested:嵌套的意思,后面的异常内容和最底层的异常是一致的
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.itheima.dao.impl.BookDaoImpl]: No default constructor found;
Caused by: 引发
BeanInstantiationException:翻译为bean实例化异常
No default constructor found:没有一个默认的构造函数被发现
这就要用到Spring中的静态工厂实例化的知识了,具体实现步骤为:
class:工厂类的类全名 ;factory-mehod:具体工厂类中创建对象的方法名
对应关系如下图:
public class AppForInstanceOrder {
public static void main(String[] args) {
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao= (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}
看到这,可能有人会问了,你这种方式在工厂类中不也是直接new对象的,和我自己直接new没什么太大的区别,而且静态工厂的方式反而更复杂,这种方式的意义是什么?
主要的原因是:
在工厂的静态方法中,我们除了new对象还可以做其他的一些业务操作,这些操作必不可少,如:
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");//模拟必要的业务操作
return new OrderDaoImpl();
}}
介绍完静态工厂实例化后,这种方式一般是用来兼容早期的一些老系统,所以了解为主
具体实现步骤为:
(1)在spring的配置文件中添加以下内容:
实例化工厂运行的顺序是:
创建实例化工厂对象,对应的是第一行配置
调用对象中的方法来创建bean,对应的是第二行配置
factory-bean:工厂的实例对象
factory-method:工厂对象中的具体创建对象的方法名,对应关系如下:
factory-mehod:具体工厂类中创建对象的方法名
(2)在AppForInstanceUser运行类,使用从IOC容器中获取bean的方法进行运行测试
publicclassAppForInstanceUser {
publicstaticvoidmain(String[] args) {
ApplicationContextctx=new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDaouserDao= (UserDao) ctx.getBean("userDao");
userDao.save();
}}
实例工厂实例化的方式就已经介绍完了,配置的过程还是比较复杂,所以Spring为了简化这种配置方式就提供了一种叫FactoryBean的方式来简化开发。
具体的使用步骤为:
(1)创建一个UserDaoFactoryBean的类,实现FactoryBean接口,重写接口的方法
这种方式在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。
查看源码会发现,FactoryBean接口其实会有三个方法,分别是:
T getObject() throws Exception;
Class> getObjectType();
default boolean isSingleton() {
return true;
}
方法一:getObject(),被重写后,在方法中进行对象的创建并返回
方法二:getObjectType(),被重写后,主要返回的是被创建类的Class对象
方法三:没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是设置对象是否为单例,默认true,从意思上来看,我们猜想默认应该是单例,如何来验证呢?
思路很简单,就是从容器中获取该对象的多个值,打印 查看是否为同一个对象.验证,会发现默认是单例
通过这一节的学习,需要掌握:
(1)bean是如何创建的呢? 构造方法
(2)Spring的IOC实例化对象的三种方式分别是:
构造方法(常用)
静态工厂(了解)
实例工厂(了解)
FactoryBean(实用)
这些方式中,重点掌握构造方法和FactoryBean即可。
需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下。
首先理解下什么是生命周期?
从创建到消亡的完整过程,例如人从出生到死亡的整个过程就是一个生命周期。
bean生命周期是什么?
bean对象从创建到销毁的整体过程。
bean生命周期控制是什么?
在bean创建后到销毁前做一些事情。
接下来,在上面这个环境中来为BookDao添加生命周期的控制方法,具体的控制有两个阶段:
bean创建之后,想要添加内容,比如用来初始化需要用到资源
bean销毁之前,想要添加内容,比如用来释放用到的资源
针对这两个阶段,我们在BooDaoImpl类中分别添加两个方法,方法名任意
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}
//表示bean初始化对应的操作
public void init(){
System.out.println("init...");
}
//表示bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}
}
在配置文件添加配置,如下:
从结果中可以看出,init方法执行了,但是destroy方法却未执行,这是为什么呢?
Spring的IOC容器是运行在JVM中
运行main方法后,JVM启动,Spring加载配置文件生成IOC容器,从容器获取bean对象,然后调方法执行
main方法执行完后,JVM退出,这个时候IOC容器中的bean还没有来得及销毁就已经结束了
所以没有调用对应的destroy方法
知道了出现问题的原因,具体该如何解决呢?
ApplicationContext中没有close方法
需要将ApplicationContext更换成ClassPathXmlApplicationContext
ClassPathXmlApplicationContextctx=new
ClassPathXmlApplicationContext("applicationContext.xml");
调用ctx的close()方法
ctx.close();
运行程序,就能执行destroy方法的内容
在容器未关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器
调用ctx的registerShutdownHook()方法
ctx.registerShutdownHook();
注意:registerShutdownHook在ApplicationContext中也没有
相同点:这两种都能用来关闭容器
不同点:close()是在调用的时候关闭,registerShutdownHook()是在JVM退出前调用关闭。
分析上面的实现过程,会发现添加初始化和销毁方法,即需要编码也需要配置,实现起来步骤比较多也比较乱。
Spring提供了两个接口来完成生命周期的控制,(了解即可)
好处是可以不用再进行配置init-method和destroy-method
接下来在BookServiceImpl完成这两个接口的使用:
修改BookServiceImpl类,添加两个接口InitializingBean, DisposableBean并实现接口中的两个方法afterPropertiesSet和destroy
在配置文件中的bean标签中添加init-method和destroy-method属性
类实现InitializingBean与DisposableBean接口,这种方式了解下即可。
初始化容器
1.创建对象(内存分配)
2.执行构造方法
3.执行属性注入(set操作)
4.执行bean初始化方法
使用bean
1.执行业务操作
关闭/销毁容器
1.执行bean销毁方法
ConfigurableApplicationContext是ApplicationContext的子类
close()方法 手工关闭
registerShutdownHook()方法 注册失败钩子,先关闭容器再退出虚拟机
强制依赖使用构造器进行,使用setter注入有概率不进行注入(忘记)导致null对象出现
强制依赖指对象在创建的过程中必须要注入指定的参数
可选依赖使用setter注入进行,灵活性强
可选依赖指对象在创建过程中注入的参数可有可无
Spring框架倡导使用构造器,
第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
如果有必要可以两者同时使用,
使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
自己开发的模块推荐使用setter注入!
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
按类型(常用)
按名称
按构造方法
自动装配只需要修改applicationContext.xml配置文件即可:
(1)将
(2)在
首先来实现按照类型注入的配置
注意事项:
需要注入属性的类中对应属性的setter方法不能省略
被注入的对象必须要被Spring的IOC容器管理(必须在配置文件里)
按照类型在Spring的IOC容器中如果找到多个对象,会报NoUniqueBeanDefinitionException
一个类型在IOC中有多个对象,还想要注入成功,这个时候就需要按照名称注入,配置方式为:
注意事项:
按照名称注入中的名称指的是什么?
bookDao是private修饰的,外部类无法直接访问
外部类只能通过属性的set方法进行访问
对外部类来说,setBookDao方法名,去掉set后首字母小写是其属性名
为什么是去掉set首字母小写?
这个规则是set方法生成的默认规则,set方法的生成是把属性名首字母大写前面加set形成的方法名
所以按照名称注入,其实是和对应的set方法有关,但是如果按照标准起名称,属性名和set对应的名是一致的
如果按照名称去找对应的bean对象,找不到则注入Null
当某一个类型在IOC容器中有多个对象,按照名称注入只找其指定名称对应的bean对象,不会报错
两种方式介绍完后,以后用的更多的是按照类型注入。
自动装配用于引用类型依赖注入,不能对简单类型(reason:int 1 杂写bean?)进行操作
使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
使用按名称装配时(byName)必须保障容器中具有指定名称的bean,变量名与配置耦合,不推荐使用
自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
100
200
300
itcast
itheima
boxuegu
chuanzhihui
itcast
itheima
boxuegu
boxuegu
china
henan
kaifeng
说明:
property标签表示setter方式注入,构造方式注入constructor-arg标签内部也可以写、
List的底层也是通过数组实现的,所以和
正常开发很少自己写集合注入
在这一节中,我们通过一个案例来学习对于第三方bean该如何进行配置管理。
以后我们会很多第三方的bean,本次案例使用前面提过的数据源Druid(德鲁伊)和C3P0来配置学习。
pom.xml中添加依赖
com.alibaba
druid
1.1.16
在applicationContext.xml配置文件中添加DruidDataSource的配置
说明:
driverClassName:数据库驱动
url:数据库连接地址
username:数据库连接用户名
password:数据库连接密码
数据库连接的四要素要和自己使用的数据库信息一致。
注意:
数据连接池在配置属性的时候,除了可以注入数据库连接四要素外还可以配置很多其他的属性,具体都有哪些属性用到的时候再去查,一般配置基础的四个,其他都有自己的默认值
Druid和C3P0在没有导入mysql驱动包的前提下,
一个没报错一个报错,说明Druid在初始化的时候没有去加载驱动,而C3P0刚好相反
Druid程序运行虽然没有报错,但是
当调用DruidDataSource的getConnection()方法获取连接的时候,也会报找不到驱动类的错误
resources下创建一个jdbc.properties文件,并添加对应的属性键值对
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root
在applicationContext.xml中开context命名空间namespace
//步骤二
在配置文件中使用context命名空间下的标签来加载properties配置文件
使用${key}来读取properties配置文件中的内容并完成属性注入
//上面跟步骤二一样
http://www.springframework.org/schema/context/spring-context.xsd">
//步骤三
//属性占位符
至此,读取外部properties配置文件中的内容就已经完成。
至此,读取properties配置文件中的内容就已经完成,但是在使用的时候,有些注意事项:
在properties中配置键值对的时候,如果key设置为username
出现问题的原因是
而且环境变量的值会被优先加载,如何查看系统的环境变量?
public static void main (String[] args) throws Exception {
Map env =System.getenv();
System.out.println(env);
}
大家可以自行运行,在打印出来的结果中会有一个USERNAME=XXX[自己电脑的用户名称];
system-properties-mode:设置为NEVER,表示不加载系统属性,就可以解决上述问题。
调整下配置文件的内容,在resources下添加jdbc.properties,jdbc2.properties
修改applicationContext.xml
http://www.springframework.org/schema/context/spring-context.xsd">
说明:
方式一:可以实现,如果配置文件多的话,每个都需要配置
方式二:*.properties代表所有以properties结尾的文件都会被加载,可以解决方式一的问题,但是不标准
方式三:标准的写法,classpath:代表的是从根路径下开始查找,但是只能查询当前项目的根路径
方式四:不仅可以加载当前项目还可加载当前项目所依赖的所有项目的根路径下的properties配置文件
案例中创建ApplicationContext的方式为:
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
这种方式翻译为: 加载类路径下的XML配置文件
除了上面这种方式,Spring还提供了另外一种创建方式为:
ApplicationContext ctx=new FileSystemXmlApplicationContext("applicationContext.xml");
这种方式翻译为: 文件系统下的XML配置文件
使用这种方式 运行,会出现如下错误:
从错误信息中能发现,这种方式是从项目路径下开始查找applicationContext.xml配置文件的,所以需要将其修改为(绝对路径):
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
当项目的位置发生变化后,代码也需要跟着改,耦合度较高,不推荐使用