Spring MVC 学习笔记
2022-04-23 14:21:00 # Java

Spring MVC 学习笔记

Spring MVC简介

Spring MVC是Spring框架中用于Web应用快速开发的一个模块,其中的MVCModel-View-Controller的缩写。它是一个广泛应用于图形化用户交互开发中的设计模式,不仅常见于Web开发,也广泛应用于如SwingJavaFX等桌面开发。作为当今业界最主流的Web开发框架,Spring MVC已经成为当前最热门的开发技能。

IDEA环境配置

https://www.cnblogs.com/zuti666/p/13987082.html

核心概念

  • IoC(Inversion of Control)控制反转

    • 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
  • Spring技术对IoC思想进行了实现

    • Spring提供了一个容器,成为IoC容器,用来充当Ioc思想中的外部
    • IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean

image-20220503134511431

  • DI(Dependency Injection)依赖注入
    • 当需要使用service对象的时候,同样需要用到dao对象,所以在IoC容器中做了一个依赖绑定。
    • 在容器中建立bean与bean之间的依赖关系的整个过程,成为依赖注入

image-20220503134958535

  • 目标: 充分解耦
    • 使用IoC容器管理 bean(IoC)
    • 在IoC容器内将有依赖关系的bean进行关系绑定(DI)
  • 最终效果
    • 使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系

IoC入门案例

IoC入门案例思路分析

  1. 管理什么?(Service与Dao)
  2. 如何将被管理的对象告知IoC容器?(配置)
  3. 被管理的对象交给IoC容器,如何获取到IoC容器?(接口)
  4. IoC容器得到后,如何从容器中获取bean?(接口方法)
  5. 使用Spring导入哪些坐标?(pom.xml)

案例1

BookDaoImpl

1
2
3
4
5
public class BookDaoImpl implements BookDao {
public void save(){
System.out.println("book dao save ...");
}
}

BookServiceImpl

1
2
3
4
5
6
7
8
9
public class BookServiceImpl implements BookService {

private BookDao bookDao = new BookDaoImpl();

public void save(){
System.out.println("book service save ...");
bookDao.save();
}
}

BookDao接口

1
2
3
public interface BookDao {
void save();
}

BookService接口

1
2
3
public interface BookService {
void save();
}

App1

1
2
3
4
5
6
7
8
9
10
public class app {
public static void main(String[] args) {
BookService bookService = new BookServiceImpl();
bookService.save();
}
}
/*运行结果
book service save ...
book dao save ...
*/

案例2

配置Spring

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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">
<!-- 1.导入spring的坐标spring-context-->

<!-- 2、配置bean-->
<!-- bean标签表示配置bean
id属性表述给bean起名字
class属性表示给bean定义类型-->
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl"/>
</beans>

app2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.le1a;

import com.le1a.dao.BookDao;
import com.le1a.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class app2 {
public static void main(String[] args) {
//3、获取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//4、获取bean
// BookDao bookDao = (BookDao) ctx.getBean("bookDap");
// bookDao.save();

//因为IoC容器对service和bean做了绑定,所以无需自己获取bean
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}

总结

1、导入Spring坐标

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>

2、定义Spring管理的类(接口)

1
2
3
public interface BookService{
public void save();
}
1
2
3
4
5
6
7
8
public class BookServiceImpl implements BookService {

private BookDao bookDao = new BookDaoImpl();

public void save(){
bookDao.save();
}
}

3、创建Spring配置文件,配置对应类作为Spring管理的bean

1
2
3
4
5
6
7
8
<?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="bookService" class="com.le1a.service.impl.BookServiceImpl"/>
</beans>

注意!bean定义时id属性在同一个上下文中不能重复

4、初始化IoC容器(Spring核心容器/Spring容器),通过容器获取bean

1
2
3
4
5
6
7
8
9
public class app2 {
public static void main(String[] args) {
//3、获取IoC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//获取资源
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}

DI入门案例

DI入门案例思路分析

  1. 基于IoC管理bean
  2. service中使用new形式创建Dao对象是否保留?(否)
  3. Service中需要的Dao对象如何进入到Service中?(提供方法)
  4. Service与Dao间的关系如何描述?(配置)

案例实现

修改BookServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BookServiceImpl implements BookService {
//5、删除业务层逻辑中使用new的方式创建的dao对象
//private BookDao bookDao = new BookDaoImpl();

private BookDao bookDao;

public void save() {
System.out.println("book service save ...");
bookDao.save();
}
//6、提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}

Spring配置文件中添加server与dao的关系

1
2
3
4
5
6
7
8
9
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>

<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<!-- 配置service与dao的关系
property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean-->
<property name="bookDao" ref="bookDao"/>
</bean>

app2运行结果

image-20220503145247632

如果注释掉property标签,则会抛出NullPointerException异常

image-20220503145340164

总结

1、删除使用new的形式创建对象的代码

1
2
3
4
5
6
7
8
9
10
public class BookServiceImpl implements BookService {
//5、删除业务层逻辑中使用new的方式创建的dao对象
//private BookDao bookDao = new BookDaoImpl();

private BookDao bookDao;

public void save() {
bookDao.save();
}
}

2、提供依赖对象对应的setter方法

1
2
3
4
5
6
7
8
9
10
11
12
public class BookServiceImpl implements BookService {

private BookDao bookDao;

public void save() {
bookDao.save();
}
//6、提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}

3、配置service与dao之间的关系

1
2
3
4
5
6
7
8
9
10
11
12
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd

<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>

<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>

image-20220503150031291

ref的bookDao指的是当前容器对应的bean的名称(也就是这里的id),name的bookDao是现在的属性的名称。

Bean基础配置

image-20220503150704843

Bean别名配置

image-20220503151701582

修改Spring配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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" name="dao" class="com.le1a.dao.impl.BookDaoImpl"/>
<!-- 新增name属性-->

<bean id="bookService" name="service service2" class="com.le1a.service.impl.BookServiceImpl">
<!-- 新增name属性-->
<property name="bookDao" ref="dao"/>
</bean>
</beans>

image-20220503151318720

但是,当如果使用了未被定义的别名时,将会抛出异常,例如这里使用了service4,但是我们并没有定义这个别名

image-20220503151438236

Bean作用范围

image-20220503153307501

1
2
3
4
5
6
7
8
9
10
11
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);
}
}

image-20220503152828979

通过运行结果可以发现,Spring给我们创建的是一个单例Bean,两个bookDao是同一个地址

如果我们想要创建非单例的bean呢?

通过配置来完成,在Spring的配置文件中的bookDao配置中,再添加一个属性scope,一共有两个属性,分别是singletonprototype,默认的是singleton,如果换成prototype,Spring创建的Bean就会是两个不同的地址

image-20220503153157301

image-20220503153213216

  • 为什么bean默认为单例呢?

    • 对于Spring来说,它帮我们管理的bean要放入IoC容器中,如果创建出的bean不是单例的,那么bean的数量将会是无数个,用一次造一个,用一次造一个,所以使用单例bean会提高容器的效率。
  • 适合交给容器进行管理的bean

    • 表现层对象
    • 业务层对象
    • 数据从对象
    • 工具对象
  • 不适合交给容器进行管理的对象

    • 封装实体的域对象

Bean实例化

构造方法(常用)

bean实例化

  • bean本质上就是对象,创建bean使用构造方法完成

配置文件

1
2
<!--方式一:使用构造方法实例化bean-->
<!-- <bean id="bookDao" name="dao" class="com.le1a.dao.impl.BookDaoImpl" scope="prototype"/>-->

AppForInstanceBook

1
2
3
4
5
6
7
8
public class AppForInstanceBook {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

BookDao bookDao =(BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}

BookDaoImpl

1
2
3
4
5
6
7
8
public class BookDaoImpl implements BookDao {
public BookDaoImpl(){
System.out.println("book dao constructor is running ...");
}
public void save(){
System.out.println("book dao save ...");
}
}

image-20220503155257068

说明了造对象都会调用无参构造方法来构造,无论构造方法是public还是private属性。

image-20220503155746206

静态工厂(了解)

配置文件

1
2
<!--    方式二:使用静态工厂实例化bean-->
<bean id="orderDao" class="com.le1a.factory.OrderDaoFactory" factory-method="getOrderDao"/>

静态工厂

1
2
3
4
5
public class OrderDaoFactory {
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}

AppForInstanceOrder

1
2
3
4
5
6
7
8
9
10
11
12
public class AppForInstanceOrder {
public static void main(String[] args) {
// 通过静态工厂创建对象
// OrderDao orderDao = OrderDaoFactory.getOrderDao();
// orderDao.save();

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
orderDao.save();
}
}

实例工厂(了解)与FactoryBean(实用)

配置文件

1
2
3
<!--    方式三: 使用实例工厂实例化bean-->
<bean id="userFactory" class="com.le1a.factory.UserDaoFactory"/>
<bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"/>

实例工厂

1
2
3
4
5
public class UserDaoFactory {
public UserDao getUserDao(){
return new UserDaoImpl();
}
}

AppForInstanceUser

1
2
3
4
5
6
7
8
9
10
11
12
13
public class AppForInstanceUser {
public static void main(String[] args) {
// 创建实例工厂对象
// UserDaoFactory userDaoFactory = new UserDaoFactory();
// 通过实例工厂对象创造对象
// UserDao userDao = userDaoFactory.getUserDao();
// userDao.save();

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}

image-20220503165549334

这个方法名是不固定的每次都需要配置,所以改良一下,使用第四种方法。

UserDaoFactoryBean⭐⭐⭐⭐⭐

1
2
3
4
5
6
7
8
9
10
11
12
public class UserDaoFactoryBean implements FactoryBean<UserDao> {
//代替原始实例工厂中创建对象的方法
@Override
public UserDao getObject() throws Exception {
return new UserDaoImpl();
}

@Override
public Class<?> getObjectType() {
return UserDao.class;
}
}

配置文件

1
2
<!--    方式四: 使用FactoryBean实例化bean-->
<bean id="userDao" class="com.le1a.factory.UserDaoFactoryBean"/>

AppForInstanceUser

1
2
3
4
5
6
7
8
9
public class AppForInstanceUser {
public static void main(String[] args) {

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();

}
}

可以发现通过FactoryBean实例化的Bean也是单例的

image-20220503170203766

如果要实现非单例的话,在UserDaoFactoryBean中新增一个方法

1
2
3
4
@Override
public boolean isSingleton() {
return false;//true为单例,false为非单例
}

image-20220503170624116

image-20220503170712187

Bean的生命周期

  • 生命周期: 从创建到消亡的完整过程
  • bean生命周期: bean从创建到销毁的整体过程
  • bean生命周期的控制: 在bean创建后到销毁前做一些事情

Bean的生命周期控制(一):使用配置

BookDaoImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BookDaoImpl implements BookDao {
@Override
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 ...");
}
}

配置文件

必须得配置了这两个方法,才会被识别为初始化方法和销毁方法

1
2
3
4
5
6
7
8
9
10
<?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.le1a.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>

<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>

AppForLifeCycle

1
2
3
4
5
6
7
public class AppForLifeCycle {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.save();
}
}

BookServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
public class BookServiceImpl implements BookService {
private BookDao bookDao;

@Override
public void save() {
System.out.println("book service ...");
}

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
}

在bean中实现初始化和销毁的方法,并且在配置文件中配置,然后就可以实现bean生命周期的控制。

执行AppForLifeCycle发现初始化方法执行了,但销毁方法并没有被执行

image-20220504133859722

原因是因为现在这个AppForLifeCycle程序是运行在java虚拟机中的,虚拟机启动了,IoC容器加载配置也启动了,然后把Bean初始化了并且拿到了Bean,下一步程序运行完了,虚拟机就退出了,并没有给Bean销毁的机会。解决办法有如下两种:

Bean销毁方法(一):

使用close()方法在虚拟机退出之前把容器给关闭

image-20220504134543454

但是发现ApplicationContext接口并没有实现close()方法,在ClassPathXmlApplicationContext接口的父类AbstractApplicationContext.class中实现了close()方法,所以只需要把ApplicationContext改为ClassPathXmlApplicationContext就可以了

image-20220504135750757

image-20220504140232062

如果想要在程序中正常的关闭容器,使用ClassPathXmlApplicationContext类就可以关了

Bean销毁方法(二):

使用registerShutdownHook()设置关闭钩子,表示在关闭虚拟机之前,先把容器关掉!

image-20220504140738206

两种方式的区别:

  1. close()方法相对暴力,如果close()放在前面,则会导致异常;而registerShutdownHook()则可以放在任意位置

image-20220504140936367

Bean的生命周期控制(二): 接口控制

如果bean的初始化和销操作都需要在配置文件中声明对应的方法名的话,太过于复杂。所以来看看Spring的方法来初始化

BookServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {
private BookDao bookDao;

@Override
public void save() {
System.out.println("book service ...");
}

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}

@Override
public void destroy() throws Exception {//销毁方法
System.out.println("service destroy");
}

@Override
public void afterPropertiesSet() throws Exception {//在Bean初始化属性设置完之后,执行service的初始化方法
System.out.println("service init");
}
}

image-20220504142145264

Bean同样可以继承这两个接口,并实现初始化和销毁的方法,这样就可以不用在spring配置文件中添加init-method="init" destroy-method="destroy"

BookDaoImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class BookDaoImpl implements BookDao , InitializingBean, DisposableBean {
@Override
public void save() {
System.out.println("book dao save ...");
}

@Override
public void destroy() throws Exception {
System.out.println("bean destroy ...");
}

@Override
public void afterPropertiesSet() throws Exception {
System.out.println("bean init ....");
}

总结:

  • 初始化容器
    1. 创建对象(内存分配)
    2. 执行构造方法
    3. 执行属性注入(set操作)
    4. 执行bean初始化方法
  • 使用bean
    1. 执行业务操作
  • 关闭/销毁容器
    1. 执行bean销毁方法

依赖注入方式

  • 思考:向一个类中传递数据的方式有几种?
    1. 普通方法(set方法)
    2. 构造方法
  • 思考:依赖注入描述了容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
    1. 引用类型
    2. 简单类型(基本数据类型和String)
  • 依赖注入方式
    • setter注入
      • 简单类型
      • 引用类型
    • 构造器注入
      • 简单类型
      • 引用类型

setter注入——引用类型

  • 在bean中定义引用类型属性并提供可访问的set方法
1
2
3
4
5
public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {
private BookDao bookDao;
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
  • 配置中使用property标签ref属性注入引用类型对象
1
2
3
4
5
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>

<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>

如果有多个bean的时候,同样的配置就好了

AppForDISet

1
2
3
4
5
6
7
8
public class AppForDISet {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}

配置文件

1
2
3
4
5
6
7
8
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>


<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
<property name="userDao" ref="userDao"/>
</bean>

BookServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class BookServiceImpl implements BookService {
private BookDao bookDao;
private UserDao userDao;

public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}

@Override
public void save() {
System.out.println("book service ...");
bookDao.save();
userDao.save();
}

setter注入——简单类型

简单类型就直接在Bean里面配置set方法,并且在配置文件中配置bean的property标签即可

image-20220504151519821

image-20220504151609530

运行结果如图

image-20220504151719038

image-20220504151953623

构造器注入——引用类型

image-20220504164215387

构造器注入的话就把之前BookServiceImplset方法改为构造方法就好了

1
2
3
4
public BookServiceImpl(BookDao bookDao, UserDao userDao) {
this.bookDao = bookDao;
this.userDao = userDao;
}
1
2
3
4
5
6
7
8
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>

<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>

<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>

构造器注入——简单类型

image-20220504164426602

BookDaoImpl 在Bean中使用构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BookDaoImpl implements BookDao{
private int connectionNum;
private String databaseName;

public BookDaoImpl(int connectionNum, String databaseName) {
this.connectionNum = connectionNum;
this.databaseName = databaseName;
}

public void save() {
System.out.println("book dao save ..."+databaseName+","+connectionNum);
}
}

配置文件中配置constructor-arg标签

1
2
3
4
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>

image-20220504162505178

高耦合解决办法

image-20220504164501005

这里会出现一个耦合度高的问题,因为配置文件中的name属性是指向的set方法/构造器的形参,如果形参发生改变,那么这里的name属性同样需要改变,比较麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--    标准书写 
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
<constructor-arg name="databaseName" value="mysql"/>
<constructor-arg name="connectionNum" value="666"/>
</bean>

<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>


<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
-->

方法一

解决方案就是 不写name属性,写type类型,通过type类型来限制赋值,通过实验发现确实可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--    解决形参名称问题,与形参名不耦合
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
<constructor-arg type="java.lang.String" value="666"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>

<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>


<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>
-->

image-20220504163445902

但是如果两个参数是同样的类型,就会按照顺序赋值,这显然不是我们想要的

image-20220504163752358

方法二

使用参数位置来解决参数匹配问题

1
2
3
4
5
6
7
8
9
10
11
12
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
<constructor-arg index="0" value="666"/>
<constructor-arg index="1" value="mysql"/>
</bean>

<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>


<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
<constructor-arg name="userDao" ref="userDao"/>
</bean>

依赖注入方式选择

  1. 强制依赖使用构造器,使用setter注入有概率不进行注入导致null对象出现
  2. 可选依赖使用setter注入进行,灵活性强
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式对数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入

Spring自动装配