引入:单体服务器
"单体服务器的开发"通常指的是在一个单一的服务器上构建和部署整个应用程序或系统的开发过程。在这种模式下,所有的应用程序组件和服务都运行在同一台服务器上,与其他服务器相对孤立。这与分布式系统的开发模式形成对比,分布式系统将应用程序的不同部分分布在多台服务器上,通过网络进行通信和协同工作。
随着云计算和微服务架构的兴起,许多开发团队逐渐转向使用分布式系统和微服务的方式,以更好地应对复杂性和提高系统的可伸缩性。微服务架构将应用程序拆分成小的、自治的服务,每个服务都可以独立部署、扩展和维护,从而提高灵活性和可靠性。
Spring框架主要的优势是在简化开发和框架整合上
简化开发: Spring框架中提供了两个大的核心技术:
1 Spring的简化操作都是基于这两块内容,所以这也是Spring学习中最为重要的两个知识点。
2 事务处理属于Spring中AOP的具体应用,可以简化项目中的事务管理,也是Spring技术中的一大亮点。
框架整合: Spring在框架整合这块已经做到了极致,它可以整合市面上几乎所有主流框架,比如:
Hibernate
Hibernate是一个用于Java平台的对象关系映射(ORM)框架。它提供了一种将Java应用程序中的对象与关系型数据库中的表之间进行映射的方法。通过Hibernate,开发者可以使用面向对象的方式来处理数据库操作,而不必直接编写SQL语句
MyBatis-Plus
MyBatis-Plus在MyBatis的基础上提供了更多的便利性和功能,可以减少一些重复性工作,简化开发流程,同时提高了代码的可读性
Struts
Struts是一个用于构建基于Java的Web应用程序的开源框架。它提供了一种模型-视图-控制器(MVC)的架构,用于将应用程序的不同部分分离开来,以促进代码的组织、维护和扩展
(Struts 2是Struts框架的继任者,Struts 2在设计和功能上进行了一些重要的改进和扩展)
IOC → 整合Mybatis(IOC的具体应用) → AOP → 声明式事务(AOP的具体应用)
Spring能用以开发web、微服务以及分布式系统等,光这三块就已经占了JavaEE开发的九成多
Spring发展到今天已经形成了一种开发的生态圈, Spring提供了若干个项目,每个项目用于完成特定的功能
这些项目和模块共同构成了Spring全家桶,为Java开发者提供了全面的解决方案,涵盖了从基本的核心框架到分布式系统开发的各个方面
在这些框架中,需要重点关注Spring Framework
、SpringBoot
和SpringCloud
Spring Framework: Spring框架,是Spring中最早最核心的技术,也是所有其他技术的基础。
SpringBoot: Spring是来简化开发,而SpringBoot是来帮助Spring在简化的基础上能更快速进行开发。
SpringCloud: 这个是用来做分布式之微服务架构的相关开发。
最新的是4版本,所以接下来主要研究的是4的架构图
(1)核心层
(2)AOP层
(3)数据层
(4)Web层
(5)Test层
学习路线
主要包含**IOC/DI、IOC容器和Bean**
在程序中不要主动使用new产生对象,转换为由外部提供对象(达到解耦效果)
创建控制权发生变化,由内部变为外部(IoC 控制反转)
对象与对象之间存在一定的**依赖关系,如上面的Service对象中有Dao对象,两者存在一定的绑定**
容器建立的对象与对象之间需要绑定关系(DI 依赖注入)
这样,在使用Service对象的时候,可以直接使用里面存在的Dao
概念小结
什么IOC/DI思想?
什么是IOC容器?
Spring创建了一个容器用来存放所创建的对象,这个容器就叫IOC容器
什么是Bean?
容器中所存放的一个个对象就叫Bean或Bean对象
IOC →(Inversion of Control)控制反转
步骤1:创建Maven项目
步骤2:添加Spring的依赖jar包
在pom.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
添加spring-context
才可以得到配置文件的相关结构
其初始化基础结构
步骤3:添加案例中需要的类
创建BookService,BookServiceImpl,BookDao和BookDaoImpl四个类
注意这个时候还没有添加注入,在BookServiceImpl中**仍采用new的方法创建了一个对象**
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();
}
}
步骤4:添加spring配置文件
resources下添加spring配置文件applicationContext.xml,并完成bean的配置
步骤5:在配置文件中完成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标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.baidu.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl"/>
</beans>
注意事项:bean定义时id属性在同一个上下文中(配置文件)不能重复
步骤6:获取IOC容器
使用Spring提供的接口完成IOC容器的创建,创建App类,编写main方法
通过.xml文件名完成文件的创建
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
步骤7:从容器中获取对象进行方法调用
通过id名获取具体的Bean对象
public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
// BookDao bookDao = (BookDao) ctx.getBean("bookDao");
// bookDao.save();
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
步骤8:运行程序
测试结果为:
采用DI:依赖注入可以进一步降低耦合度,除去new的操作
DI(Dependency Injection)依赖注入
当IOC容器中创建好service和dao对象后,程序能正确执行么?
什么是依赖注入?
IOC容器中哪些bean之间要建立依赖关系呢?
这个**需要程序员根据业务需求提前建立好关系**,如业务层需要依赖数据层,service就要和dao建立依赖关系
步骤1: 去除代码中的new
在BookServiceImpl类中,删除业务层中使用new的方式创建的dao对象
public class BookServiceImpl implements BookService {
//删除业务层中使用new的方式创建的dao对象
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
步骤2:为属性提供setter方法
在BookServiceImpl类中,为BookDao提供setter方法
不需要new方法而是通过配置文件注入
这个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;
}
}
步骤3:修改配置完成注入
在配置文件中添加依赖注入的配置
<?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标签标示配置bean
id属性标示给bean起名字
class属性表示给bean定义类型
-->
<bean id="bookDao" class="com.baidu.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
注意:配置中的两个bookDao的含义是不一样的
bookDao
的作用是让Spring的IOC容器在获取到名称后,将首字母大写,前面加set找对应的setBookDao()
方法进行对象注入bookDao
的作用是让Spring能在IOC容器中找到id为bookDao
的Bean对象给bookService
进行注入步骤4:运行程序
运行,测试结果为:
class属性能不能写接口类全名(如BookDao),因为接口是没办法创建对象的
name: 为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔
步骤1:配置别名
打开spring的配置文件applicationContext.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">
<!--name:为bean指定别名,别名可以有多个,使用逗号,分号,空格进行分隔-->
<bean id="bookService" name="service service4 bookEbi" class="com.baidu.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" name="dao" class="com.baidu.dao.impl.BookDaoImpl"/>
</beans>
说明:Ebi全称Enterprise Business Interface,翻译为企业业务接口
步骤2:根据名称容器中获取bean对象
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();
}
}
步骤3:运行程序
测试结果为:
重要
scope:为bean设置作用范围,可选值为单例singloton,非单例prototype
spring默认是创建单例(singleton),即容器中对于同一对象创建的**多个实例在内存中的地址是一样的**,即获得的都是同一个对象
演示如下:
public class AppForScope {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao1);
System.out.println(bookDao2);
}
}
输出结果:
<!--scope:为bean设置作用范围,可选值为单例singloton,非单例prototype-->
<bean id="bookDao" name="dao" class="com.baidu.dao.impl.BookDaoImpl" scope="prototype"/>
输出结果:
- 为什么bean默认为单例?
- bean为单例的意思是在Spring的IOC容器中只会有该类的一个对象
- bean对象只有一个就避免了对象的频繁创建与销毁,达到了bean对象的复用,性能高
- bean在容器中是单例的,会不会产生线程安全问题?
- 如果对象是**有状态对象,即该对象有成员变量可以用来存储数据的**,因为所有请求线程共用一个bean对象,所以会存在线程安全问题。
- 如果对象是**无状态对象,即该对象没有成员变量没有进行数据存储的**,因方法中的局部变量在方法调用完成后会被销毁,所以不会存在线程安全问题。
- 哪些bean对象适合交给容器进行管理?(这些对象都可以反复用)
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
- 哪些bean对象不适合交给容器进行管理?
- 封装实例的域对象,因为会引发线程安全问题,所以不适合。
重要
利用构造方法来创建bean的(底层用的是反射,能访问到类中的私有构造方法)
需要注意的一点是,构造方法在类中默认会提供,但是如果重写了构造方法,默认的就会消失,在使用的过程中需要注意,如果需要重写构造方法,最好把默认的构造方法也重写下,不然会报错:
java.lang.NoSuchMethodException:某个类的构造方法<init>()
抛出的异常为:没有这样的方法异常
参考上面 bean是如何创建的
创建静态工厂的核心代码:
//静态工厂创建对象
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
public class AppForInstanceOrder {
public static void main(String[] args) {
//通过静态工厂创建对象
OrderDao orderDao = OrderDaoFactory.getOrderDao();
orderDao.save();
}
}
配置文件:
<bean id="orderDao" class="com.baidu.factory.OrderDaoFactory" factory-method="getOrderDao"/>
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();
}
}
实用场景:
在创建对象之前添加其它业务内容
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
System.out.println("factory setup....");//模拟必要的业务操作
return new OrderDaoImpl();
}
}
示例工厂的核心代码:
//没有static关键字!!
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}
public class AppForInstanceUser {
public static void main(String[] args) {
//创建实例工厂对象
UserDaoFactory userDaoFactory = new UserDaoFactory();
//通过实例工厂对象创建对象
UserDao userDao = userDaoFactory.getUserDao();
userDao.save();
}
配置文件:
<bean id="userFactory" class="com.baidu.factory.UserDaoFactory"/>
<bean id="userDao" factory-method="getUserDao" factory-bean="userFactory"/>
factory-bean: 工厂的实例对象
factory-method: 工厂对象中的具体创建对象的方法名
运行顺序及对应关系:
创建实例化工厂对象(第一行配置)
调用对象中的方法来创建bean(第二行配置)
从容器中获取对象:
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
Spring为了简化上述配置方式提供了FactoryBean
的方式来简化开发
重要
步骤:
(1) 创建一个UserDaoFactoryBean
的类,实现FactoryBean接口,重写接口的方法
这两个重写的方法为:
方法一:getObject(),被重写后,在方法中进行对象的创建并返回
方法二:getObjectType(), 被重写后,主要返回的是被创建类的Class对象
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return UserDao.class;
}
}
(2) 在Spring的配置文件中进行配置
<bean id="userDao" class="com.baidu.factory.UserDaoFactoryBean"/>
(3) AppForInstanceUser运行类不用做任何修改,直接运行
public class AppForInstanceUser {
public static void main(String[] args) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
这种方式在Spring去整合其他框架的时候会被用到,所以这种方式需要大家理解掌握。
查看源码会发现,FactoryBean接口其实会有三个方法,分别是:
T getObject() throws Exception;
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
方法三: boolean isSingleton() 没有被重写,因为它已经给了默认值,从方法名中可以看出其作用是**设置对象是否为单例,默认true**
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...");
}
}
配置文件中
<bean id="bookDao" class="com.baidu.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>
运行方法
public class AppForLifeCycle {
public static void main( String[] args ) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
输出结果
这里为什么没有执行destory方法?
close关闭容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.close();
注意:
ApplicationContext中没有close方法
需要将ApplicationContext更换成**ClassPathXmlApplicationContext**
运行结果:
注册钩子关闭容器
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.close();
注意: registerShutdownHook在ApplicationContext中也没有,需要将ApplicationContext更换成ClassPathXmlApplicationContext
运行结果:
两种方法的对比
相同点: 这两种都能用来关闭容器
不同点: close()是在调用的时候关闭,registerShutdownHook()是在JVM退出前调用关闭。
添加两个接口InitializingBean
, DisposableBean
并实现接口中的两个方法afterPropertiesSet
和destroy
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
public void destroy() throws Exception {
System.out.println("service destroy");
}
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}
运行代码
public class AppForLifeCycle {
public static void main( String[] args ) {
ApplicationContext ctx = new
ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
运行结果:
小细节:
对于InitializingBean接口中的afterPropertiesSet方法,翻译过来为属性设置之后
,从方法名分析,setBookDao方法先执行后,即属性设置完成之后,在进行初始方法,即afterPropertiesSet
bookDao的注入,仍是通过配置文件实现的
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
(1) 关于Spring中对bean生命周期控制提供了两种方式:
(2) 对于bean的生命周期控制在bean的整个生命周期中所处的位置如下:
(3)关闭容器的两种方式:
Spring就是基于上面这些知识点,为我们提供了两种注入方式,分别是:
<property name="" ref=""/>
<property name="" value=""/>
核心setter代码:
public class BookServiceImpl implements BookService {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}
核心配置文件:
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.baidu.dao.imipl.BookDaoImpl"/>
步骤1:声明属性并提供setter方法
步骤2:配置文件中进行注入配置
在applicationContext.xml配置文件中使用property标签注入
步骤3:运行程序
步骤1:声明属性并提供setter方法
核心示例代码:
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public void setConnectionNum(int connectionNum) {
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName) {
this.databaseName = databaseName;
}
public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
步骤2:配置文件中进行注入配置
在applicationContext.xml配置文件中使用property标签注入
value:后面跟的是简单数据类型,对于参数类型,Spring在注入的时候会自动转换(将引号中的内容转换成对应的简单数据类型)
两个property注入标签的顺序可以任意
配置文件示例:
<?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="bookDao" class="com.baidu.dao.impl.BookDaoImpl">
<property name="databaseName" value="mysql"/>
<property name="connectionNum" value="10"/>
</bean>
<bean id="userDao" class="com.baidu.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>
</beans>
如果写成类型不匹配,如:
<property name="connectionNum" value="abc"/>
会报错。
情景:相关的setter方法删除掉,添加带有参数的构造方法
步骤1:删除setter方法并提供构造方法
核心代码:
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public BookServiceImpl(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
步骤2:配置文件中进行配置构造方式注入
标签<constructor-arg>
中
name属性对应的值为**构造函数中方法形参的参数名**,必须要保持一致。
ref属性指向的是spring的IOC容器中**其他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 id="bookDao" class="com.baidu.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
</beans>
步骤3:运行程序
public class AppForDIConstructor {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
步骤1: 提供多个属性的构造函数(并在相关方法两个属性都调用)
public class BookServiceImpl implements BookService{
private BookDao bookDao;
private UserDao userDao;
public BookServiceImpl(BookDao bookDao,UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
userDao.save();
}
}
步骤2: 配置文件中配置多参数注入
<?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="bookDao" class="com.baidu.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.baidu.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>
**说明:**这两个<contructor-arg>
的配置顺序可以任意
步骤3: 运行程序
public class AppForDIConstructor {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
运行结果:
步骤1:添加多个简单属性并提供构造方法
public class BookDaoImpl implements BookDao {
private String databaseName;
private int connectionNum;
public BookDaoImpl(String databaseName, int connectionNum) {
this.databaseName = databaseName;
this.connectionNum = connectionNum;
}
public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}
步骤2:配置完成多个属性构造器注入
<?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="bookDao" class="com.baidu.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>
<bean id="userDao" class="com.baidu.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
</beans>
**说明:**这两个<contructor-arg>
的配置顺序可以任意
步骤3:运行程序
public class AppForDIConstructor {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
这两部分的耦合度很高,参数名发生变化后,配置文件中的name属性也需要跟着变
方式一: 删除name属性,添加type属性,按照类型注入
<bean id="bookDao" class="com.baidu.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="10"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>
方式二: 删除type属性,添加index属性,按照索引下标注入,下标从0开始
<bean id="bookDao" class="com.baidu.dao.impl.BookDaoImpl">
<constructor-arg index="1" value="100"/>
<constructor-arg index="0" value="mysql"/>
</bean>
两种参数的注入方式,该如何选择呢?
回顾
setter注入
简单数据类型
<bean ...>
<property name="" value=""/>
</bean>
引用数据类型
<bean ...>
<property name="" ref=""/>
</bean>
构造器注入
简单数据类型
<bean ...>
<constructor-arg name="" index="" type="" value=""/>
</bean>
引用数据类型
<bean ...>
<constructor-arg name="" index="" type="" ref=""/>
</bean>
依赖注入的方式选择上
在前面所说的配置的基础上进一步简化操作
IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
注意:
自动装配用于引用类型依赖注入,不能对简单类型进行操作
自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效
核心:
(1)将<property>
标签删除
(2)在<bean>
标签中添加autowire属性
<bean class="com.baidu.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl" autowire="byType"/>
注意:
需要注入属性的类中对应属性的setter方法不能省略
public class BookServiceImpl implements BookService{
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
}
被注入的对象必须要被Spring的IOC容器管理
<bean class="com.baidu.dao.impl.BookDaoImpl"/>
按照类型在Spring的IOC容器中如果找到多个对象,会报NoUniqueBeanDefinitionException
一个类型在IOC中有多个对象,就需要按照名称注入,配置方式为:
<?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 class="com.baidu.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.baidu.service.impl.BookServiceImpl" autowire="byName"/>
</beans>
注意:
这里的名称指的是什么?
除了引入数据类型和简单数据类型,**集合**这种数据类型,在Spring中该如何注入呢?
常见集合数据类型:
数组
List
Set
Map
Properties
Properties
类被设计用来处理配置文件,其中键和值都是字符串类型。它提供了一种简单的方式来读取和写入属性文件。
public class BookDaoImpl implements BookDao {
private int[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;
public void save() {
System.out.println("book dao save ...");
System.out.println("遍历数组:" + Arrays.toString(array));
System.out.println("遍历List" + list);
System.out.println("遍历Set" + set);
System.out.println("遍历Map" + map);
System.out.println("遍历Properties" + properties);
}
//setter....方法省略,自己使用工具生成
}
配置文件:
<?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="bookDao" class="com.baidu.dao.impl.BookDaoImpl">
</bean>
</beans>
下面的所以配置方式,都是在bookDao的bean标签中使用<property>
进行注入
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>
<property name="list">
<list>
<value>list_v1</value>
<value>list_v2</value>
<value>list_v3</value>
<value>list_v4</value>
</list>
</property>
<property name="set">
<set>
<value>set_1</value>
<value>set_2</value>
<value>set_3</value>
<value>set_4</value>
</set>
</property>
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
public class AppForDICollection {
public static void main( String[] args ) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}
运行结果:
property标签**表示setter方式注入**,构造方式注入constructor-arg标签内部也可以写<array>
、<list>
、<set>
、<map>
、<props>
标签
<bean id="exampleBeanList" class="com.example.ExampleBean">
<constructor-arg>
<list>
<value>item1</value>
<value>item2</value>
<value>item3</value>
</list>
</constructor-arg>
</bean>
List的底层也是通过数组实现的,所以**<list>
和<array>
标签是可以混用**
集合中要添加**引用类型**,只需要把<value>
标签改成<ref>
标签,这种方式用的比较少
<!-- 定义两个简单的Bean -->
<bean id="student1" class="com.example.Student">
<property name="name" value="Alice"/>
</bean>
<bean id="student2" class="com.example.Student">
<property name="name" value="Bob"/>
</bean>
<!-- 构造器注入,注入引用类型的集合 -->
<bean id="exampleBeanRefCollection" class="com.example.ExampleBean">
<constructor-arg>
<list>
<!-- 使用<ref>标签引用其他Bean -->
<ref bean="student1"/>
<ref bean="student2"/>
</list>
</constructor-arg>
</bean>
管理第三方jar包中的类
这里第三方的bean以**数据源Druid(德鲁伊)
和C3P0
**为例
Druid
和C3P0
都是 Java 中常用的数据库连接池,用于管理数据库连接,提高数据库访问的性能和效率。它们都支持连接池的基本功能,如连接的创建、管理、释放以及一些额外的功能,如连接的监控和统计。
对于技术的坐标的导入:
Maven Repository: Search/Browse/Explore (mvnrepository.com)
步骤1:导入druid
的依赖
pom.xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
步骤2:配置第三方bean
在applicationContext.xml配置文件中添加DruidDataSource
的配置
<?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">
<!--管理DruidDataSource对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</bean>
</beans>
说明:
driverClassName
:数据库驱动url
:数据库连接地址username
:数据库连接用户名password
:数据库连接密码步骤3:从IOC容器中获取对应的bean对象
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
}
}
步骤4:运行程序
打印如下结果: 说明第三方bean对象已经被spring的IOC容器进行管理
思考&回顾:
这里的第三方类是指什么?
DruidDataSource
如何注入数据库连接四要素?
setter注入
如何发现是setter注入的?
Ctrl+Z 查看源码;
找到其构造方法,发现一个为空参数构造,一个只传入了一个参数,不能完成注入操作
Ctrl+F12,查找其setter方法:
定位到这个方法,可以推断,是根据这个方法,完成了注入
同样可以发现其他相关函数:
使用Spring的IOC容器来管理C3P0连接池对象
步骤1:导入C3P0
的依赖
pom.xml中添加依赖
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
步骤2:配置第三方bean
在applicationContext.xml配置文件中添加配置
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
<property name="maxPoolSize" value="1000"/>
</bean>
注意:
com.mysql.jdbc.Driver
,而C3P0刚好相反;Druid程序运行虽然没有报错,但是当调用DruidDataSource的getConnection()方法获取连接的时候,也会报找不到驱动类的错误运行结果:
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
}
}
将一些常量提取到个外部的properties配置文件中
步骤1:准备properties配置文件
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
步骤2:开启context
命名空间
在applicationContext.xml中开**context命名空间**
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
如何开辟新的命名空间?
前面加:
xmlns:context="http://www.springframework.org/schema/context"
下面加:
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
步骤3:加载properties配置文件
在配置文件中使用context
命名空间下的标签来加载properties配置文件
<context:property-placeholder location="jdbc.properties"/>
步骤4:完成属性注入
使用${key}
来读取properties配置文件中的内容并完成属性注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:property-placeholder location="jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
至此,读取外部properties配置文件中的内容就已经完成。
问题引入:
如果key设置为username
username=root666
在xml中注入属性时
<bean id="bookDao" class="com.baidu.dao.impl.BookDaoImpl">
<property name="name" value="${username}"/>
</bean>
运行后,在控制台打印的却不是root666
,而是自己电脑的用户名
问题分析:
出现问题的原因是<context:property-placeholder/>
标签会加载系统的环境变量,而且**环境变量的值会被优先加载**
查看系统的环境变量
package com.baidu; import java.util.Map; public class AppSystemProperties { public static void main(String[] args) { Map<String, String> env = System.getenv(); for (Map.Entry<String, String> entry : env.entrySet()) { System.out.println(entry.getKey() + " = " + entry.getValue()); } } }
输出:
USERDOMAIN_ROAMINGPROFILE = OLIVER的LEGIONB EFC_93204 = 1 PROCESSOR_LEVEL = 6 SESSIONNAME = Console ALLUSERSPROFILE = C:\ProgramData PROCESSOR_ARCHITECTURE = AMD64 PSModulePath = C:\Program Files\WindowsPowerShell\Modules;C:\Windows\system32\WindowsPowerShell\v1.0\Modules SystemDrive = C: MAVEN_HOME = D:\Maven\apache-maven-3.9.6-bin\apache-maven-3.9.6 USERNAME = Oliver_Cheung # 输出的USERNAME↑↑↑↑↑↑↑ ProgramFiles(x86) = C:\Program Files (x86) CUDA_PATH_V10_2 = D:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2 PATHEXT = .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC DriverData = C:\Windows\System32\Drivers\DriverData ProgramData = C:\ProgramData ProgramW6432 = C:\Program Files HOMEPATH = \Users\Oliver_Cheung MYSQL_HOME = D:\MySQL_installer\mysql-5.7.24-winx64 PROCESSOR_IDENTIFIER = Intel64 Family 6 Model 186 Stepping 2, GenuineIntel ProgramFiles = C:\Program Files PUBLIC = C:\Users\Public windir = C:\Windows =:: = ::\ ZES_ENABLE_SYSMAN = 1 LOCALAPPDATA = C:\Users\Oliver_Cheung\AppData\Local USERDOMAIN = OLIVER的LEGIONB LOGONSERVER = \\OLIVER的LEGIONB JAVA_HOME = D:\JDK OneDrive = C:\Users\Oliver_Cheung\OneDrive APPDATA = C:\Users\Oliver_Cheung\AppData\Roaming NODE_PATH = D:\Node_JS\node_global\node_modules NVTOOLSEXT_PATH = C:\Program Files\NVIDIA Corporation\NvToolsExt\ CommonProgramFiles = C:\Program Files\Common Files Path = D:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\bin;D:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\libnvvp;D:\JDK\bin;D:\Node_JS;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;D:\Program Files\Bandizip\;D:\Node_JS\node_global\node_modules;C:\Program Files\NVIDIA Corporation\Nsight Compute 2019.5.0\;D:\MySQL_installer\mysql-5.7.24-winx64\bin;D:\python_3.10.4\Scripts\;D:\python_3.10.4\;C:\Users\Oliver_Cheung\AppData\Local\Microsoft\WindowsApps;D:\Node_JS\node_global;D:\Program Files\JetBrains\PyCharm2023.2.5\bin;;D:\Users\Oliver_Cheung\AppData\Local\Programs\Microsoft VS Code\bin;D:\Maven\apache-maven-3.9.6-bin\apache-maven-3.9.6\bin; NVCUDASAMPLES10_2_ROOT = D:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.2 PyCharm = D:\Program Files\JetBrains\PyCharm2023.2.5\bin; OS = Windows_NT COMPUTERNAME = OLIVER的LEGIONB NVCUDASAMPLES_ROOT = D:\ProgramData\NVIDIA Corporation\CUDA Samples\v10.2 CUDA_PATH = D:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2 PROCESSOR_REVISION = ba02 CommonProgramW6432 = C:\Program Files\Common Files ComSpec = C:\Windows\system32\cmd.exe SystemRoot = C:\Windows TEMP = C:\Users\OLIVER~1\AppData\Local\Temp HOMEDRIVE = C: USERPROFILE = C:\Users\Oliver_Cheung TMP = C:\Users\OLIVER~1\AppData\Local\Temp CommonProgramFiles(x86) = C:\Program Files (x86)\Common Files NUMBER_OF_PROCESSORS = 20 IDEA_INITIAL_DIRECTORY = D:\Program Files\JetBrains\IntelliJ IDEA 2023.2.5\bin Process finished with exit code 0
问题解决:
system-properties-mode
:设置为**NEVER,表示不加载系统属性**
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
当然还有一个解决方案就是避免使用username
作为属性的key
问题引入:
调整下配置文件的内容,在resources下添加jdbc.properties
,jdbc2.properties
,内容如下:
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
jdbc2.properties
username=root666
问题解决:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--方式一 -->
<context:property-placeholder location="jdbc.properties,jdbc2.properties" system-properties-mode="NEVER"/>
<!--方式二-->
<context:property-placeholder location="*.properties" system-properties-mode="NEVER"/>
<!--方式三 -->
<context:property-placeholder location="classpath:*.properties" system-properties-mode="NEVER"/>
<!--方式四-->
<context:property-placeholder location="classpath*:*.properties" system-properties-mode="NEVER"/>
</beans>
*.properties
代表所有以properties结尾的文件都会被加载,可以解决方式一的问题,但是不标准classpath:
代表的是从根路径下开始查找,但是只能查询当前项目的根路径什么为核心容器?
把它简单的理解为ApplicationContext,我们进一步来详细了解这个类
类路径下的XML配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
文件系统下的XML配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\workspace\\spring\\spring_10_container\\src\\main\\resources\\applicationContext.xml");
方式一,就是目前案例中获取的方式:
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
进行类型转换
方式二:
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
参数又多加了一个
方式三:
BookDao bookDao = ctx.getBean(BookDao.class);
类似我们之前所学习依赖注入中的按类型注入。必须要确保IOC容器中该类型对应的bean对象只能有一个。
在IntelliJ IDEA中,**双击Shift键是用于打开"Search Everywhere"(搜索所有地方)**功能的快捷键
Ctrl+h 打开某个类的类型继承图
结构层次关系:
容器类也是从无到有根据需要一层层叠加上来的。
使用BeanFactory来创建IOC容器的具体实现方式为:
public class AppForBeanFactory {
public static void main(String[] args) {
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean(BookDao.class);
bookDao.save();
}
}
为了更好的看出BeanFactory
和ApplicationContext
之间的区别,在BookDaoImpl添加如下构造函数:
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("constructor");
}
public void save() {
System.out.println("book dao save ..." );
}
}
如果不去获取bean对象,打印会发现:
BeanFactory是**延迟加载**,只有在获取bean对象的时候才会去创建
ApplicationContext是立即加载,容器加载的时候就会创建bean对象
ApplicationContext要想成为延迟加载,只需要按照如下方式进行配置
<?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="bookDao" class="com.baidu.dao.impl.BookDaoImpl" lazy-init="true"/>
</beans>
类型继承图**
[外链图片转存中…(img-FR2v4pff-1703147141496)]
结构层次关系:
容器类也是从无到有根据需要一层层叠加上来的。
使用BeanFactory来创建IOC容器的具体实现方式为:
public class AppForBeanFactory {
public static void main(String[] args) {
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);
BookDao bookDao = bf.getBean(BookDao.class);
bookDao.save();
}
}
为了更好的看出BeanFactory
和ApplicationContext
之间的区别,在BookDaoImpl添加如下构造函数:
public class BookDaoImpl implements BookDao {
public BookDaoImpl() {
System.out.println("constructor");
}
public void save() {
System.out.println("book dao save ..." );
}
}
如果不去获取bean对象,打印会发现:
BeanFactory是**延迟加载**,只有在获取bean对象的时候才会去创建
ApplicationContext是立即加载,容器加载的时候就会创建bean对象
ApplicationContext要想成为延迟加载,只需要按照如下方式进行配置
<?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="bookDao" class="com.baidu.dao.impl.BookDaoImpl" lazy-init="true"/>
</beans>