avatar


15.Spring Framework [1/2]

本章主要讨论:

  1. IoC(Inverse Of Control,控制反转)
  2. DI(Dependency Injection,依赖注入)

下一章《16.Spring Framework [2/2]》主要讨论:AOP(Aspect Oriented Programming,面向切面编程)。

概述

官网:https://spring.io

通过官网,我们会发现有:Spring FrameworkSpring BootSpring Cloud等诸多Spring相关的技术。在本章,我们讨论Spring Framework,这也是其他Spring框架的基础。

IoC

引例

我们先通过一个例子,来引出什么是IoC。
如下,是我们写的一个保存图书信息的代码,一个业务层接口,一个业务层的实现,一个数据层的接口,一个数据层的实现,再加一个主入口。

业务层接口:

1
2
3
4
5
package com.kakawanyifan.service;

public interface BookService {
public void save();
}

业务层实现:

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.dao.impl.BookDaoImpl;
import com.kakawanyifan.service.BookService;

public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
public void save() {
bookDao.save();
}
}

数据层接口:

1
2
3
4
5
package com.kakawanyifan.dao;

public interface BookDao {
public void save();
}

数据层实现:

1
2
3
4
5
6
7
8
9
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;

public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("save book");
}
}

主入口:

1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import com.kakawanyifan.service.impl.BookServiceImpl;

public class App {
public static void main(String[] args) {
BookService bookService = new BookServiceImpl();
bookService.save();
}
}

在上述代码中,业务层需要调用数据层的方法,所以我们在业务层创建一个数据层的对象。

1
private BookDao bookDao = new BookDaoImpl();

那么,如果我们现在不想用BookDaoImpl这个实现类呢?
例如,我们有一个实现类BookDaoImpl2nd

1
2
3
4
5
6
7
8
9
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;

public class BookDaoImpl2nd implements BookDao {
public void save() {
System.out.println("save book 2nd");
}
}

我们想用这个实现类。
那么,需要将new BookDaoImpl();改成new BookDaoImpl2nd();

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.dao.impl.BookDaoImpl2nd;
import com.kakawanyifan.service.BookService;

public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl2nd();
public void save() {
bookDao.save();
}
}

这样,代码的耦合度较高。

针对该问题,Spring提出了一个解决方案:
使用对象时,不主动创建对象,改为由第三方提供对象。
这就是IoC。

定义

IoC(Inversion of Control,控制反转)。
在使用对象时,从主动创建对象,改为由第三方提供对象,此过程中对象创建控制权转移到了第三方,这就是控制反转。

那么,第三方是谁呢?
Spring提供了一个容器,负责对象的创建、初始化等一系列工作,这就是IoC中的第三方。
这个容器也被称为IoC容器,被IoC容器创建或管理的对象即所谓的Bean。

例子

我们基于IoC来改进上述的代码。

一、在POM文件中添加Spring相关的包

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
</dependencies>

二、添加Spring的配置文件

在这里我们把配置文件命名为applicationContext.xml
添加方法如下:

添加Spring配置文件

三、在配置文件applicationContext.xml中完成Bean的配置

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

四、获取IoC容器,并从容器中获取对象,进行方法调用

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
运行结果:
1
save book

这样,我们就做到了,使用对象时,不主动创建对象,改为由第三方提供对象。

DI

定义

在上文,业务层的运行需要依赖数据层。IoC容器中虽然有业务层的对象和数据层的对象,但是这两个对象之间没有任何关系,业务层的对象还是在自行创建数据层对象。所以还需要把IoC容器中的数据层的对象交给业务层的对象,即绑定业务层对象和数据层对象之间的关系。

在容器中建立Bean与Bean之间的依赖关系的整个过程,就是DI(Dependency Injection,依赖注入)。

例子

一、去除BookServiceImpl中的new,并提供set方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;

public class BookServiceImpl implements BookService {
private BookDao bookDao;

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

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

二、在配置文件中添加依赖注入的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl">
<!--配置server与dao的关系-->
<!--property标签表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>

</beans>

解释说明:

  • name="bookDao"bookDao是成员变量的名字。
  • ref="bookDao"bookDao是Bean的id。

特别的,如果我们将setBookDao改为其他名字,会报错。即,set方法的名字必须是"set + 成员变量首字母大写"。

Bean

在上文,我们已经提到了Bean。在这里,我们做更系统的讨论。

配置

格式

1
2
3
4
5
6
7
8
<beans>
<bean />


<bean>

</bean>
</beans>

id和class

  • id,Bean的id。在容器中使用id获取对应的Bean,在一个容器中id唯一。
  • class,Bean的类型。全类名。

示例代码:

1
2
3
4
<bean id="bookDao" class="com.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>

提一个问题,class属性能不能写接口的全类名呢?
不能,因为接口是没办法创建对象的。

name

name,用于配置别名,一个Bean可以配置多个别名,可以用逗号,、分号;或空格 分隔。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
<?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="bookDaoAlias bda" class="com.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="bookService" name="bookEbi" class="com.kakawanyifan.service.impl.BookServiceImpl">
<property name="bookDao" ref="bda"/>
</bean>

</beans>

通过别名来获取Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookEbi");
bookService.save();
}
}

scope

scope,用于配置单例或非单例等

  • sigleton,单例(默认单例)。
    Bean的实例化个数:1个
    Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
    Bean的生命周期:对象创建,当应用加载,创建容器时,对象就被创建了;对象运行,只要容器在,对象一直活着;对象销毁,当应用卸载,销毁容器时,对象就被销毁了。
  • prototype,非单例。
    Bean的实例化个数:多个
    Bean的实例化时机:当调用getBean()方法时实例化Bean
    Bean的生命周期:对象创建,当使用对象时,创建新的对象实例;对象运行,只要对象在使用中,就一直活着;对象销毁,当对象长时间不用时,被Java的垃圾回收器回收了。

(关于Bean的生命周期,我们会在下文进行讨论。)

此外,还有三种WEB项目中的生命周期。

  • request,WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中。
  • session,WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中。
  • global session,WEB项目中,应用在Portlet环境,如果没有Portlet环境,那么global session相当于session

单例

默认单例。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
<?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.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AppTestSingleton {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService1 = (BookService) ctx.getBean("bookService");
BookService bookService2 = (BookService) ctx.getBean("bookService");

System.out.println(bookService1);
System.out.println(bookService2);
}
}

运行结果:

1
2
com.kakawanyifan.service.impl.BookServiceImpl@64bf3bbf
com.kakawanyifan.service.impl.BookServiceImpl@64bf3bbf

解释说明:
如运行结果所示,两次打印的都是同一个对象,即单例。

非单例

scope="prototype",非单例。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
<?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.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl" scope="prototype">
<property name="bookDao" ref="bookDao"/>
</bean>

</beans>
1
2
3
4
5
6
7
8
9
package com.kakawanyifan.service;

import com.kakawanyifan.dao.BookDao;

public interface BookService {
public void save();

public BookDao getBookDaoTest();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;

public class BookServiceImpl implements BookService {
private BookDao bookDao;

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

public BookDao getBookDaoTest(){
return this.bookDao;
}

public void save() {
bookDao.save();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AppTestPrototype {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService1 = (BookService) ctx.getBean("bookService");
BookService bookService2 = (BookService) ctx.getBean("bookService");

System.out.println(bookService1);
System.out.println(bookService2);

System.out.println(bookService1.getBookDaoTest());
System.out.println(bookService1.getBookDaoTest());
}
}

运行结果:

1
2
3
4
com.kakawanyifan.service.impl.BookServiceImpl@192b07fd
com.kakawanyifan.service.impl.BookServiceImpl@64bfbc86
com.kakawanyifan.dao.impl.BookDaoImpl@64bf3bbf
com.kakawanyifan.dao.impl.BookDaoImpl@64bf3bbf

如运行结果所示,BookServiceImpl是非单例的。但是BookServiceImpl的成员变量bookDao,依旧是默认的单例。

思考

  1. 为什么Bean默认为单例?
    避免了Bean对象的频繁创建与销毁,实现了Bean的复用,提高性能。

  2. Bean在容器中是单例的,会不会产生线程安全问题?
    可能会产生线程安全问题。
    正如我们在《7.多线程 [1/2]》的讨论,线程安全问题的内在原因或者根本原因有三点:

    • 多个线程在操作共享的数据
    • 多个线程对共享数据有写操作
    • 操作共享数据的线程执行的操作有多个

    所以,如果Bean中的某个成员变量存在上述情况的话,就会产生线程不安全。
    也正因为如此,我们一般只把表现层对象、业务层对象、数据层对象、工具对象等交给容器管理;对于封装实例的域对象等,不会交给容器管理。

(在《17.SpringMVC》,我们还会讨论Controller,这其实也是一种Bean,也是默认单例。)

创建

Bean也是对象,所以创建Bean的方式,也是就是创建对象的方式。
创建对象的方式有三种:

  1. 构造方法
  2. 静态工厂
  3. 实例工厂

创建Bean的方式也是这三种。

构造方法

《2.面向对象》,我们就讨论过构造方法。在这里,我们通过举例子的方式,直接讨论在Spring中的应用。

示例代码:

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

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;

public class BookServiceImpl implements BookService {

private BookDao bookDao;

private BookServiceImpl(){
System.out.println("private BookServiceImpl Constructor");
}

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

public void save() {
bookDao.save();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
}
}

运行结果:

1
private BookServiceImpl Constructor

解释说明:
BookServiceImpl中有一个无参的构造方法,调用该构造方法会打印private BookServiceImpl Constructor。现在打印了private BookServiceImpl Constructor,也说明Spring容器在创建对象的时候也用的是构造方法。

但有一个疑问,BookServiceImpl的构造方法是private的啊!这是因为Spring利用了反射。
(关于反射,我们在《9.类的加载与反射》有过讨论。)

特别的,如果我们将构造方法,设置为一个有参的构造方法,则会出现告警。示例代码:

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

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;

public class BookServiceImpl implements BookService {

private BookDao bookDao;

private BookServiceImpl(int i){
System.out.println("private BookServiceImpl Constructor");
}

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

public void save() {
bookDao.save();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
}
}

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'bookService' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.kakawanyifan.service.impl.BookServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.kakawanyifan.service.impl.BookServiceImpl.<init>()
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1323)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1218)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:556)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:516)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:342)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1109)
at com.kakawanyifan.App.main(App.java:11)
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.kakawanyifan.service.impl.BookServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.kakawanyifan.service.impl.BookServiceImpl.<init>()
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:83)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1315)
... 7 more
Caused by: java.lang.NoSuchMethodException: com.kakawanyifan.service.impl.BookServiceImpl.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:78)
... 8 more

解释说明:
如告警信息所示,Spring默认使用的是类的无参构造方法,如果Bean中没有默认无参构造函数,将会创建失败。

在一个类中,默认会有一个无参的构造方法。但是如果我们自己提供了构造方法,则不会有默认的无参构造方法了。
所以,我们在《2.面向对象》说:“无论是否使用,都手工书写无参数构造方法”。

针对这个现象,解决方法:

  • 除了我们"手工书写无参数构造方法"
  • 还可以通过依赖注入的方式,使用有参构造方法。
    这个会在下文讨论。

静态工厂

什么是静态工厂

我们通过举例子的方式,讨论什么是静态工厂。

一、静态工厂类
注意getBookService()static的。

1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import com.kakawanyifan.service.impl.BookServiceImpl;

public class BookServiceFactory {
public static BookService getBookService(){
System.out.println("public BookService getBookService");
return new BookServiceImpl();
}
}

二、通过静态工厂获取对象

1
2
3
4
5
6
7
8
9
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;

public class App {
public static void main(String[] args) {
BookService bookService = BookServiceFactory.getBookService();
}
}

在上文代码中,我们看到,通过静态工厂的方法创建对象,实际上也是在new对象,那和我们自己new,有什么区别呢?
我们可以在静态方法中,在new对象之前或之后,执行其他的操作。例如:

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

import com.kakawanyifan.service.BookService;
import com.kakawanyifan.service.impl.BookServiceImpl;

public class BookServiceFactory {
public static BookService getBookService(){

// new 对象之前各种操作
// ......
// new 对象之前各种操作

BookServiceImpl rtn = new BookServiceImpl();

// new 对象之后各种操作
// ......
// new 对象之后各种操作

return rtn
}
}

Spring中的静态工厂

利用Bean中的两个属性:

  • class:工厂类的全类名
  • factory-mehod:工厂类中创建对象的方法名

示例代码

一、配置

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" factory-method="getBookService" class="com.kakawanyifan.BookServiceFactory"/>

</beans>

二、从IoC容器中获取对象

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
}
}

实例工厂

什么是实例工厂

有了上文静态工厂的例子,实例工厂就很好理解了。即工厂不是静态的,是需要创建工厂实例的。

一、实例工厂类
注意,此时方法不是被static修饰的。

1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import com.kakawanyifan.service.impl.BookServiceImpl;

public class BookServiceFactory {
public BookService getBookService(){
System.out.println("public BookService getBookService");
return new BookServiceImpl();
}
}

二、通过实例工厂获取对象

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;

public class App {
public static void main(String[] args) {
//创建实例工厂对象
BookServiceFactory bookServiceFactory = new BookServiceFactory();
//通过实例工厂对象创建对象
BookService bookService = bookServiceFactory.getBookService();
}
}

Spring中的实例工厂

Spring中实例工厂运行的顺序是:

  1. 创建实例工厂对象。
    例如:

    1
    <bean id="bookServiceFactory" class="com.kakawanyifan.BookServiceFactory"/>
  2. 调用对象中的方法来创建Bean。
    例如:

    1
    <bean id="bookService" factory-method="getBookService" factory-bean="bookServiceFactory"/>
    • factory-bean是实例工厂的对象
    • factory-method是创建对象的方法名。

示例代码

一、配置

1
2
3
4
5
6
7
8
9
<?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="bookServiceFactory" class="com.kakawanyifan.BookServiceFactory"/>
<bean id="bookService" factory-method="getBookService" factory-bean="bookServiceFactory"/>

</beans>

二、从IoC容器中获取对象

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
}
}

FactoryBean

在Spring中,还有一种实例工厂的简化方式:FactoryBean
使用方法很简单,只需要实现FactoryBean接口,重写接口的方法。而对于配置,只需要和构造方法中的一样,只配置id和class两个属性。

示例代码

一、创建一个类,实现FactoryBean接口,并重写接口的方法

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

import com.kakawanyifan.service.BookService;
import com.kakawanyifan.service.impl.BookServiceImpl;
import org.springframework.beans.factory.FactoryBean;

public class BookServiceFactoryBean implements FactoryBean<BookService> {
//代替原始实例工厂中创建对象的方法
public BookService getObject() throws Exception {
System.out.println("public BookService getObject");
return new BookServiceImpl();
}
//返回所创建类的Class对象
public Class<?> getObjectType() {
return BookService.class;
}
}

二、配置

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.kakawanyifan.BookServiceFactoryBean"/>

</beans>

三、从IoC容器中获取Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
}
}

这种方式在利用Spring去整合其他框架的时候会被用到。

通过查看FactoryBean的源码,我们会发现一个方法isSingleton(),设置对象是否为单例,默认true。如果我们想设置为非单例,可以重写该方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

public interface FactoryBean<T> {

String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

@Nullable
T getObject() throws Exception;

@Nullable
Class<?> getObjectType();

default boolean isSingleton() {
return true;
}

}

生命周期

概述

什么是生命周期?
从创建到消亡的完整过程。

Bean的生命周期是什么?
Bean从创建到销毁的整体过程。

Bean的生命周期控制是什么?
在Bean创建后到销毁前做一些事情。
例如,在Bean创建之前,初始化需要用到资源;在Bean销毁之后,用来释放用到的资源。

题外话: 除了Bean有生命周期,一些基金公司的产品管理系统,也被命名为生命周期系统,指一个产品从创建到退出的整个过程。

设置

Bean的生命周期控制所依赖的属性有:

  • init-method:初始化方法的方法名
  • destroy-method:销毁方法的方法名

示例代码

一、添加初始化和销毁方法,方法名任意。

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

import com.kakawanyifan.service.BookService;

public class BookServiceImpl implements BookService {
public void save() {
System.out.println("saving...");
}

// 表示对Bean的初始化对应的操作
public void init(){
System.out.println("init...");
}

// 表示对Bean销毁前对应的操作
public void destory(){
System.out.println("destory...");
}
}

二、配置

  • init-method:初始化方法的方法名
  • destroy-method:销毁方法的方法名
1
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl" init-method="init" destroy-method="destory"/>

三、从IoC容器中获取Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
//获取IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
}
}

运行结果

1
init...

有没有问题?
init方法确实是执行了,但是destroy方法没有执行。
这是因为,Spring的IoC容器是运行在JVM中;运行main方法后,JVM启动,Spring加载配置文件生成IoC容器,从容器获取Bean,然后调方法执行;main方法执行完后,JVM退出,这个时候IoC容器中的Bean还没有来得及销毁就已经结束了;所以没有调用对应的destroy方法。

解决方法有:

  1. close关闭容器
  2. 注册钩子关闭容器

close关闭容器

ApplicationContext中没有close方法,我们需要将ApplicationContext更换成ClassPathXmlApplicationContext

1
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

然后,调用close()方法

1
ctx.close();

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
ctx.close();
}
}

运行结果:

1
2
init...
destory...

注册钩子关闭容器

钩子的思路是,在容器关闭之前,提前设置好回调函数,让JVM在退出之前回调此函数来关闭容器。

通过registerShutdownHook()方法,来设置回调函数。需要注意的是,registerShutdownHook在ApplicationContext中也没有。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
BookService bookService = (BookService) ctx.getBean("bookService");
}
}

运行结果:

1
2
init...
destory...

InitializingBean和DisposableBean

Spring提供了两个接口来完成生命周期的控制,以简化我们的开发。
这两个接口和需要重写的方法分别是:

  • InitializingBean
    • afterPropertiesSet方法
  • DisposableBean
    • destroy方法

与上文实现FactoryBean接口一样。对于配置,只需要配置id和class两个属性。

示例代码

一、实现InitializingBean和DisposableBean

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;

public void save() {
System.out.println("saving...");
}

public void setBookDao(BookDao bookDao) {
System.out.println("set...");
this.bookDao = bookDao;
}

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

public void destroy() throws Exception {
System.out.println("destory...");
}
}

二、配置

1
2
3
4
5
6
7
8
9
10
11
<?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.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>

</beans>

三、从IoC容器中获取Bean

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
ctx.registerShutdownHook();
BookService bookService = (BookService) ctx.getBean("bookService");
}
}
运行结果:
1
2
3
set...
init...
destory...

注意看运行结果,先打印的是set...,然后才是init...
InitializingBean接口中的afterPropertiesSet方法,翻译过来为属性设置之后。对于BookServiceImpl来说,bookDao是一个属性,setBookDao方法是Spring的IoC容器为其注入属性的方法。
所以,先执行了setBookDao方法,再执行afterPropertiesSet方法。

DI

DI,Dependency Injection,依赖注入。

在讨论依赖注入之前,我们先讨论两个问题。

  1. 向一个类中传递数据有几种方法?
    两种:Setter方法、构造方法。
  2. 传递的数据类型有哪些:
    1. 有对象的
      即不包括String的引用数据类型
      这里我们称之为引用类型
    2. 没有对象的
      即基本数据类型及其包装类以及String
      这里我们统称为简单类型

Setter方法注入

引用类型

关于Setter方法注入引用类型,我们在上文已经讨论过了,使用的是<property name="" ref=""/>
其中ref是指向Spring的IoC容器中的另一个Bean对象的。

简单类型

简单类型的注入方法为<property name="" value=""/>
对于value中的值,Spring在注入的时候会自动转换;对于布尔类型的,大小写皆可;对于无法转换的,会报错。

示例代码

一、在BookDaoImpl类中声明对应的简单数据类型的属性,并提供对应的Setter方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;

public class BookDaoImpl implements BookDao {

private int iVal;
private Integer integerVal;

private double dVal;
private Double doubleVal;

private boolean bVal;
private Boolean booleanVal;

private String string;

public int getiVal() {
return iVal;
}

public void setiVal(int iVal) {
this.iVal = iVal;
}

public Integer getIntegerVal() {
return integerVal;
}

public void setIntegerVal(Integer integerVal) {
this.integerVal = integerVal;
}

public double getdVal() {
return dVal;
}

public void setdVal(double dVal) {
this.dVal = dVal;
}

public Double getDoubleVal() {
return doubleVal;
}

public void setDoubleVal(Double doubleVal) {
this.doubleVal = doubleVal;
}

public boolean isbVal() {
return bVal;
}

public void setbVal(boolean bVal) {
this.bVal = bVal;
}

public Boolean getBooleanVal() {
return booleanVal;
}

public void setBooleanVal(Boolean booleanVal) {
this.booleanVal = booleanVal;
}

public String getString() {
return string;
}

public void setString(String string) {
this.string = string;
}

public void print() {
System.out.println(iVal);
System.out.println(integerVal);
System.out.println(dVal);
System.out.println(doubleVal);
System.out.println(bVal);
System.out.println(booleanVal);
System.out.println(string);
}
}

二、在applicationContext.xml配置文件中使用property标签注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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.kakawanyifan.dao.impl.BookDaoImpl">

<property name="iVal" value="1"/>
<property name="integerVal" value="2"/>

<property name="dVal" value="1.0"/>
<property name="doubleVal" value="2.0"/>

<property name="bVal" value="true"/>
<property name="booleanVal" value="FALSE"/>

<property name="string" value="abc"/>

</bean>

</beans>

三、通过IoC容器获取Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
bookDao.print();
}
}
运行结果:
1
2
3
4
5
6
7
1
2
1.0
2.0
true
false
abc

构造方法注入

引用类型

在Bean标签中,配置constructor-arg子标签,该子标签有两个属性:

  • name:构造方法中形参的参数名
  • ref:其他Bean的id

示例代码

一、含参的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;

public class BookServiceImpl implements BookService {
private BookDao bookDao;

public BookServiceImpl(BookDao bookDao){
this.bookDao = bookDao;
}

public void save() {
System.out.println(this.bookDao);
}
}

二、配置

1
2
3
4
5
6
7
8
9
10
11
<?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.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>

</beans>

三、从IoC容器获取Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
运行结果:
1
com.kakawanyifan.dao.impl.BookDaoImpl@3c0f93f1

多个引用类型

对于多个引用类型,构造方法中有多个参数,在Bean标签中,有多个contructor-arg标签,contructor-arg标签的配置顺序可以任意。

示例代码

一、含有多个参数的构造方法

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

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.dao.UserDao;
import com.kakawanyifan.service.BookService;

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(this.bookDao);
System.out.println(this.userDao);
}
}

二、配置

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" class="com.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.kakawanyifan.dao.impl.UserDaoImpl"/>
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl">
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>

</beans>

三、从IoC容器获取Bean
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
运行结果:
1
2
com.kakawanyifan.dao.impl.BookDaoImpl@544fe44c
com.kakawanyifan.dao.impl.UserDaoImpl@31610302

简单类型

同样依赖constructor-arg的标签,其中一个属性依旧是name,但另一个属性不是ref,而是value

示例代码

一、入参为简单类型的构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;

public class BookDaoImpl implements BookDao {

private int iVal;
private Integer integerVal;

private double dVal;
private Double doubleVal;

private boolean bVal;
private Boolean booleanVal;

private String string;

public BookDaoImpl(int iVal, Integer integerVal, double dVal, Double doubleVal, boolean bVal, Boolean booleanVal, String string) {
this.iVal = iVal;
this.integerVal = integerVal;
this.dVal = dVal;
this.doubleVal = doubleVal;
this.bVal = bVal;
this.booleanVal = booleanVal;
this.string = string;
}

@Override
public String toString() {
return "BookDaoImpl{" +
"iVal=" + iVal +
", integerVal=" + integerVal +
", dVal=" + dVal +
", doubleVal=" + doubleVal +
", bVal=" + bVal +
", booleanVal=" + booleanVal +
", string='" + string + '\'' +
'}';
}

public void save() {

}
}

二、配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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.kakawanyifan.dao.impl.BookDaoImpl">
<constructor-arg name="iVal" value="1"/>
<constructor-arg name="integerVal" value="1"/>
<constructor-arg name="dVal" value="2.0"/>
<constructor-arg name="doubleVal" value="2.0"/>
<constructor-arg name="bVal" value="true"/>
<constructor-arg name="booleanVal" value="false"/>
<constructor-arg name="string" value="abc"/>
</bean>

</beans>

三、从IoC容器获取Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.dao.BookDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao.toString());
}
}
运行结果:
1
BookDaoImpl{iVal=1, integerVal=1, dVal=2.0, doubleVal=2.0, bVal=true, booleanVal=false, string='abc'}

其他注入方式

除了按name属性,也有其他的注入方式,例如:

一、type,按照类型注入。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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.kakawanyifan.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="1"/>
<constructor-arg type="java.lang.Integer" value="2"/>
<constructor-arg type="double" value="3.0"/>
<constructor-arg type="java.lang.Double" value="4.0"/>
<constructor-arg type="boolean" value="true"/>
<constructor-arg type="java.lang.Boolean" value="false"/>
<constructor-arg type="java.lang.String" value="abc"/>
</bean>
</beans>

二、index,按照索引下标注入,下标从0开始。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.kakawanyifan.dao.impl.BookDaoImpl">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="2"/>
<constructor-arg index="2" value="3.0"/>
<constructor-arg index="3" value="4.0"/>
<constructor-arg index="4" value="true"/>
<constructor-arg index="5" value="false"/>
<constructor-arg index="6" value="abc"/>
</bean>
</beans>

注入集合

在上文,我们讨论了注入简单类型和注入引用类型,但是还有一种数据类型"集合"。
在一个集合中既可以装简单类型也可以装引用数据类型,对于集合,在Spring中该如何注入呢?

首先,我们复习一下,集合有哪些?

方法

注入List类型数据
利用list标签

1
2
3
4
5
6
7
8
<property name="list">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
<value>ddd</value>
</list>
</property>

注入Set类型数据
利用set标签

1
2
3
4
5
6
7
8
<property name="set">
<set>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
<value>ccc</value>
</set>
</property>

注入Map类型数据
利用map标签和entry标签

1
2
3
4
5
6
7
<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="jiangxi"/>
<entry key="city" value="nanchang"/>
</map>
</property>

注入Properties类型数据
利用props标签和prop标签

1
2
3
4
5
6
7
<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">jiangxi</prop>
<prop key="city">nanchang</prop>
</props>
</property>

注入数组类型数据
利用array标签

1
2
3
4
5
6
7
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>

注意:

  1. property标签表示Setter方式注入,如果是构造方法注入,可以在constructor-arg标签内部写listsetmap等标签。
  2. 如果在集合中要添加引用类型,只需要把value标签改成ref标签。

例子

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.UserDao;

import java.util.*;

public class UserDaoImpl implements UserDao {

private int[] array;
private List<String> list;
private Set<String> set;
private Map<String,String> map;
private Properties properties;

public void setArray(int[] array) {
this.array = array;
}

public void setList(List<String> list) {
this.list = list;
}

public void setSet(Set<String> set) {
this.set = set;
}

public void setMap(Map<String, String> map) {
this.map = map;
}

public void setProperties(Properties properties) {
this.properties = properties;
}

@Override
public String toString() {
return "UserDaoImpl{" +
"array=" + Arrays.toString(array) +
", list=" + list +
", set=" + set +
", map=" + map +
", properties=" + properties +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?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.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.kakawanyifan.dao.impl.UserDaoImpl">
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>

<property name="list">
<list>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
<value>ddd</value>
</list>
</property>

<property name="set">
<set>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
<value>ccc</value>
</set>
</property>

<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="jiangxi"/>
<entry key="city" value="nanchang"/>
</map>
</property>

<property name="properties">
<props>
<prop key="country">china</prop>
<prop key="province">jiangxi</prop>
<prop key="city">nanchang</prop>
</props>
</property>
</bean>

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
System.out.println(userDao.toString());
}
}

运行结果:

1
UserDaoImpl{array=[100, 200, 300], list=[aaa, bbb, ccc, ddd], set=[aaa, bbb, c c c], map={country=china, province=jiangxi, city=nanchang}, properties={province=jiangxi, city=nanchang, country=china}}

自动装配

概述

IoC容器根据Bean所依赖的资源在容器中自动查找并注入的过程被称为自动装配。

自动装配的方式有:

  1. 按类型
  2. 按名称
  3. 按构造方法

注意:自动装配优先级低于上文的手动注入(Setter方法注入和构造方法注入),如果两者同时出现,自动装配失效。

按类型

按类型是最常用的一种方式。

autowire="byType"

该方式需要注意的是:

  1. 需要注入属性的类中对应属性的Setter方法不能省略
  2. 被注入的对象必须要被Spring的IoC容器管理
  3. 如果找到多个对象,会报NoUniqueBeanDefinitionException
  4. 如果没有找到对象,会注入null

示例代码

一、在bean标签中添加autowire="byType"

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 class="com.kakawanyifan.dao.impl.BookDaoImpl"/>
<!--autowire属性:开启自动装配,通常使用按类型装配-->
<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl" autowire="byType"/>

</beans>

二、需要注入属性的类中对应属性的set方法不能省略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;

public class BookServiceImpl implements BookService {
private BookDao bookDao;

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

public void save() {
System.out.println(this.bookDao);
}
}

三、通过IoC容器获取Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();
}
}
运行结果:
1
com.kakawanyifan.dao.impl.BookDaoImpl@add0edd

按名称

autowire="byName"

示例代码

一、在bean标签中添加autowire="byName"

1
2
3
4
5
6
7
8
9
10
11
<?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.kakawanyifan.dao.impl.BookDaoImpl"/>
<bean id="bookDao2nd" class="com.kakawanyifan.dao.impl.BookDaoImpl"/>

<bean id="bookService" class="com.kakawanyifan.service.impl.BookServiceImpl" autowire="byName"/>

</beans>

二、通过IoC容器获取Bean

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

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) ctx.getBean("bookService");
bookService.save();

System.out.println(ctx.getBean("bookDao"));
System.out.println(ctx.getBean("bookDao2nd"));

}
}

运行结果:

1
2
3
com.kakawanyifan.dao.impl.BookDaoImpl@11dc3715
com.kakawanyifan.dao.impl.BookDaoImpl@11dc3715
com.kakawanyifan.dao.impl.BookDaoImpl@69930714

解释说明:
正如运行结果所示,根据名称匹配,注入的是id为"bookDao"的Bean,而不是"bookDao2nd"。

管理第三方Bean

上文讨论的,都是基于我们自己写的类。如果要管理第三方jar包中的类呢?
实际上,和我们管理我们自己写的类没有太大区别。
不过是需要引入第三方的jar包,全类名用第三方的全类名。

例子

在这里,我们分别以DruidHikari为例。

Druid

一、添加导入druid以及数据库连接的依赖

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.12</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>

二、配置第三方Bean
applicationContext.xml配置文件中添加DruidDataSource的配置

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/s"/>
<property name="username" value="root"/>
<property name="password" value="MySQL@2022"/>
</bean>

</beans>

三、从IoC容器中获取Bean
示例代码:

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

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;

public class App {
public static void main(String[] args) throws SQLException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("druidDataSource");
ResultSet resultSet = dataSource.getConnection().prepareStatement("select VERSION()").executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString(1));
}
}
}

运行结果:

1
8.0.29

Hikari

Hikari,类似。

示例代码:

1
2
3
4
5
<bean id="hikariDataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/s"/>
<property name="username" value="root"/>
<property name="password" value="MySQL@2022"/>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.kakawanyifan;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;

public class App {
public static void main(String[] args) throws SQLException {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = (DataSource) ctx.getBean("hikariDataSource");
ResultSet resultSet = dataSource.getConnection().prepareStatement("select VERSION()").executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString(1));
}
}
}

运行结果:

1
8.0.29

加载properties

在上文中,我们将数据库连接的要素,写在Spring的配置文件中,这样不利于后期维护。
现在,我们将这些值提取到一个外部的properties配置文件中,Spring从配置文件中读取属性值来配置。

方法步骤

一、准备properties配置文件
在resources下创建一个jdbc.properties文件,并添加对应的属性键值对。

1
2
3
jdbc.url=jdbc:mysql://127.0.0.1:3306/s
jdbc.username=root
jdbc.password=MySQL@2022

二、开启context命名空间

在applicationContext.xml中开context命名空间

开启context命名空间

注意箭头所指的部分:

  • xmlns:context="http://www.springframework.org/schema/context"
  • http://www.springframework.org/schema/context
  • http://www.springframework.org/schema/context/spring-context.xsd"

三、加载properties配置文件
使用context命名空间下的标签来加载properties配置文件

1
<context:property-placeholder location="jdbc.properties"/>

四、完成属性注入

使用${key}来读取properties配置文件中的内容并完成属性注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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="hikariDataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

</beans>

系统的环境变量

现象

我们再来看一个。

假设存在配置文件user.properties

1
USER=u1s1

加载配置文件,注入属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="user.properties"/>

<bean id="userDao" class="com.kakawanyifan.dao.impl.UserDaoImpl">
<property name="u" value="${USER}" />
</bean>

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.UserDao;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao {

private String u;

public String getU() {
return u;
}

public void setU(String u) {
this.u = u;
}

@Override
public void print() {
System.out.println(this.u);
}
}

从IoC容器获取Bean

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.print();
}
}

运行结果:

1
kaka

出问题了!
应该打印u1s1,结果打印了kaka

原因

context:property-placeholder标签会加载系统的环境变量,而且系统的环境变量的值会被优先加载。

我们可以通过如下的方法看看我们系统的环境变量。
示例代码:

1
2
3
4
public static void main(String[] args) throws Exception{
Map<String, String> env = System.getenv();
System.out.println(env);
}

运行结果:

1
【部分运行结果略】   USER=kaka   【部分运行结果略】

解决

context标签的system-properties-mode属性设置为NEVER,表示不加载系统属性,即可解决上述问题。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="user.properties" system-properties-mode="NEVER"/>

<bean id="userDao" class="com.kakawanyifan.dao.impl.UserDaoImpl">
<property name="u" value="${USER}" />
</bean>

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.dao.impl.UserDaoImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDaoImpl userDaoImpl = (UserDaoImpl) ctx.getBean("userDao");
System.out.println(userDaoImpl.getU());
}
}

运行结果:

1
u1s1

加载多个properties

我们有多种方式加载多个properties。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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结尾的文件都会被加载。
  • 方式三,classpath:代表的是从根路径下开始查找,但是只能查询当前项目的根路径。【标准的写法】
  • 方式四,不仅可以加载当前项目还可以加载当前项目所依赖的所有项目的根路径下的properties配置文件。

核心容器

在这里所说的核心容器,我们可以简单的理解为ApplicationContext
在上文已经用到过,现在我们进行更详细的讨论。

容器的创建

在上文我们创建ApplicationContext的方式为:

1
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

这种方式,被称为"类路径下的XML配置文件"。

除此之外,Spring还提供了另外一种创建方式"文件系统下的XML配置文件",是相对项目地址而言的。

1
ApplicationContext ctx = new FileSystemXmlApplicationContext("s_ioc/src/main/resources/applicationContext.xml");

注意,此时context标签的location属性也要以相对于项目而言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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="s_ioc/src/main/resources/user.properties" system-properties-mode="NEVER"/>

<bean id="userDao" class="com.kakawanyifan.dao.impl.UserDaoImpl">
<property name="u" value="${USER}" />
</bean>

</beans>

这种方式虽然也能实现,但是当项目的位置发生变化后,代码也需要跟着改,耦合度太高,不推荐使用。

Bean的获取

在上文,我们获取Bean的方式如下:

1
BookDao bookDao = (BookDao) ctx.getBean("bookDao");

这种方式存在的问题是每次获取的时候都需要进行类型转换。

除此之外,获取Bean的方式还有:

  1. BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
    这种方式可以解决类型强转问题,但是多了一个参数。
  2. BookDao bookDao = ctx.getBean(BookDao.class);
    这种方式类似上文依赖注入中的按类型注入,必须要确保IoC容器中该类型对应的Bean有且只有一个。

BeanFactory

用法

我们还可以使用BeanFactory来创建IoC容器,用法如下:

1
2
Resource resources = new ClassPathResource("applicationContext.xml");
BeanFactory bf = new XmlBeanFactory(resources);

示例代码

一、配置

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="testBean" class="com.kakawanyifan.TestBean" />

</beans>
1
2
3
4
5
6
7
package com.kakawanyifan;

public class TestBean {
public TestBean(){
System.out.println("testBean...");
}
}

二、从IoC容器中获取

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

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class AppForBeanFactory {
public static void main(String[] args) {
Resource resources = new ClassPathResource("applicationContext.xml");
System.out.println("resources");
BeanFactory bf = new XmlBeanFactory(resources);
System.out.println("bf");
TestBean testBean = (TestBean) bf.getBean("testBean");
System.out.println("testBean");
}
}
运行结果:
1
2
3
bf
testBean...
testBean

延迟加载

我们和ApplicationContext比较一下。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan;


import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("ctx");
TestBean testBean = (TestBean) ctx.getBean("testBean");
System.out.println("testBean");
}
}

运行结果:

1
2
3
testBean...
ctx
testBean

正如运行结果所示:

  • BeanFactory是延迟加载,只有在获取Bean的时候才会去创建Bean
  • ApplicationContext是立即加载,容器加载的时候就会创建Bean

如果ApplicationContext也想延迟加载,需要在Bean标签上添加属性lazy-init="true"。示例代码:

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="testBean" class="com.kakawanyifan.TestBean" lazy-init="true"/>

</beans>

import

实际开发中,Spring的配置内容非常多,这就导致Spring配置很繁杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载

示例代码:

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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

【部分代码略】

<import resource="applicationContext-xxx.xml"/>

【部分代码略】

</beans>
  • "applicationContext-xxx.xml的结构相同,也需要最外层<beans>标签。
  • 如果需要引入多个XML配置,那么写多个import标签。

注解

注解配置Bean

Spring原始注解主要是替代<bean>的配置,在这里我们以@Component注解为例。

注意:

  • 如果@Component注解如果不起名称,会有一个默认值就是当前类名首字母小写
  • 除了@Component注解,还有三个衍生注解@Controller@Service@Repository
    通过查看源码会发现:
    三个注解
    这三个注解和@Component注解的作用是一样的,但这三个注解可以方便我们区分出类是属于表现层业务层还是数据层

Spring的原始注解还有

注解 说明
@Component 使用在类上用于实例化Bean
@Controller 使用在web层类上用于实例化Bean
@Service 使用在service层类上用于实例化Bean
@Repository 使用在dao层类上用于实例化Bean
@Autowired 使用在字段上用于根据类型依赖注入
@Qualifier 结合@Autowired一起使用用于根据名称进行依赖注入
@Resource 相当于@Autowired+@Qualifier,按照名称进行注入
@Value 注入普通属性
@Scope 标注Bean的作用范围
@PostConstruct 使用在方法上标注该方法是Bean的初始化方法
@PreDestroy 使用在方法上标注该方法是Bean的销毁方法

示例代码

一、去除applicationContext.xml中,相关类的Bean标签,并在类上标好注解@Component

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;
import org.springframework.stereotype.Component;

@Component("bookDao")
public class BookDaoImpl implements BookDao {

public void save() {
System.out.println("saving...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;
import org.springframework.stereotype.Component;

@Component
public class BookServiceImpl implements BookService {
private BookDao bookDao;

public void save() {
System.out.println(this.bookDao);
}
}

提一个问题,@Component可以添加在接口上吗?
不可以,因为接口是无法创建对象的。

二、配置Spring的注解包扫描
为了让Spring框架能够扫描到写在类上的注解,需要在配置文件applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法。

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: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:component-scan base-package="com.kakawanyifan"/>

</beans>

三、运行
示例代码:

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


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

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
bookDao.save();

BookService bookService = ctx.getBean("bookServiceImpl",BookService.class);
System.out.println(bookService);
}
}
运行结果:
1
2
saving...
com.kakawanyifan.service.impl.BookServiceImpl@6d60fe40

纯注解开发

上面已经可以使用注解来配置Bean,但是依然有用到配置文件。纯注解开发,是指完全包不用配置文件。

此时,需要用到的注解有:

  • @Configuration:用于设定当前类为配置类
  • @ComponentScan:用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
    1
    @ComponentScan({"com.kakawanyifan.service","com.kakawanyifan.dao"})

此时,初始化容器对象的方法为:

1
2
//加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

其中:AnnotationConfigApplicationContext,加载配置类;而之前,我们用的是ClassPathXmlApplicationContext,加载配置文件。

示例代码

一、删除applicationContext.xml,创建一个配置类,利用注解@Configuration,标识该类为配置类,在配置类上添加包扫描注解@ComponentScan

1
2
3
4
5
6
7
8
9
10
package com.kakawanyifan;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("com.kakawanyifan")
public class SpringConfig {

}

二、通过ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);获取容器,并运行

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


import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
bookDao.save();

BookService bookService = ctx.getBean("bookServiceImpl",BookService.class);
System.out.println(bookService);
}
}
运行结果:
1
2
saving...
com.kakawanyifan.service.impl.BookServiceImpl@2c767a52

Bean

注解配置Bean的作用范围和生命周期,和属性的对应关系如下:

注解和配置

作用范围

@scope:Bean的作用范围,默认值singleton(单例),可选值prototype(非单例)。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.dao.UserDao;
import com.kakawanyifan.service.BookService;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
//@Scope设置bean的作用范围
@Scope("prototype")
public class BookServiceImpl implements BookService {

public void save() {
System.out.println(this.bookDao);
System.out.println(this.userDao);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.kakawanyifan;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

BookDao bookDao = (BookDao) ctx.getBean("bookDao");
System.out.println(bookDao);

BookService bookService = ctx.getBean(BookService.class);
System.out.println(bookService);

BookService bookService2 = ctx.getBean("bookServiceImpl",BookService.class);
System.out.println(bookService2);
}
}

运行结果:

1
2
3
com.kakawanyifan.dao.impl.BookDaoImpl@2890c451
com.kakawanyifan.service.impl.BookServiceImpl@482cd91f
com.kakawanyifan.service.impl.BookServiceImpl@123f1134

生命周期

  • @PostConstruct,设置该方法为初始化方法。
  • @PreDestroy,设置该方法为销毁方法。

如果@PostConstruct@PreDestroy注解如果找不到,需要导入下面的jar包。

1
2
3
4
5
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>

因为,从JDK9以后,javax.annotation被移除了,这两个注解刚好就在这个包中。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;
import org.springframework.stereotype.Repository;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("book dao save ...");
}

//在构造方法之后执行,替换 init-method
@PostConstruct
public void init() {
System.out.println("init ...");
}

//在销毁方法之前执行,替换 destroy-method
@PreDestroy
public void destroy() {
System.out.println("destroy ...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan;


import com.kakawanyifan.dao.BookDao;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
bookDao.save();
ctx.close();
}
}

运行结果:

1
2
3
init ...
book dao save ...
destroy ...

依赖注入

自动装配

Spring为了使用注解简化开发,并没有提供"构造方法注入"、"Setter方法注入"所对应的注解。
只提供了自动装配的注解@Autowired

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;
import org.springframework.stereotype.Repository;

@Repository
public class BookDaoImpl implements BookDao {

public void save() {
System.out.println("save...");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.kakawanyifan.service.impl;

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BookServiceImpl implements BookService {

@Autowired
private BookDao bookDao;

@Override
public void save() {
bookDao.save();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import com.kakawanyifan.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookService bookService = ctx.getBean(BookService.class);
bookService.save();
}
}

运行结果:

1
save...

@Autowired可以写在属性上,也可也写在Setter方法上,最简单的处理方式是 写在属性上并将Setter方法删掉

为什么可以将Setter方法删掉?
自动装配基于反射设计创建对象并通过暴力反射为私有属性进行设值,所以此处无需提供setter方法。
(普通反射只能获取public修饰的内容,暴力反射除了获取public修饰的内容还可以获取private修改的内容,具体可以参考《9.类的加载与反射》的讨论)

所以,其实依赖注入,严格意义上,应该有三种方法,Setter方法、构造方法和自动装配(暴力反射)。

(不过在某些版本的IDEA上,删掉Setter方法,IDEA会有提示。)

按名称

再按类型注入,那么如果BookDao接口如果有多个实现类,"应该"会报错。

示例代码
一、给两个"BookDao"取名

1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;
import org.springframework.stereotype.Repository;

@Repository("bookDao")
public class BookDaoImpl implements BookDao {
public void save() {
System.out.println("save...");
}
}
1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;
import org.springframework.stereotype.Repository;

@Repository("bookDao2nd")
public class BookDaoImpl2nd implements BookDao {
public void save() {
System.out.println("save...2nd...");
}
}

二、运行

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;


import com.kakawanyifan.service.BookService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookService bookService = ctx.getBean("bookService", BookService.class);
bookService.save();
}
}

运行结果:

1
book dao save ...

这也没报错啊。
@Autowired默认按照类型自动装配,如果IoC容器中同类的Bean找到多个,就按照变量名和Bean的名称匹配。因为变量名叫bookDao,而容器中也有一个booDao,所以可以成功注入。

但是,如果我们指定要注入"bookDao2nd"呢?
这就需要使用到@Qualifier来指定注入哪个名称的bean对象。
注意,@Qualifier不能独立使用,必须和@Autowired一起使用。

示例代码:

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

import com.kakawanyifan.dao.BookDao;
import com.kakawanyifan.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("bookService")
public class BookServiceImpl implements BookService {

@Autowired
@Qualifier("bookDao2nd")
private BookDao bookDao;

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

简单类型注入

@Value注解,将需要注入的值写入注解的参数中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;

@Repository("bookDao2nd")
public class BookDaoImpl2nd implements BookDao {

@Value("科学发展")
private String bookName;

public void save() {
System.out.println("save " + bookName );
}
}

运行结果:

1
save 科学发展

读取properties

  • @PropertySource加载properties配置
  • @Value读取配置文件中的内容

注意:

  • 如果读取的properties配置文件有多个,可以使用@PropertySource的属性来指定多个
    1
    @PropertySource({"jdbc.properties","xxx.properties"})
  • @PropertySource注解属性中 不支持通配符* ,运行会报错。
  • @PropertySource注解属性中可以把classpath:加上,代表从当前项目的根路径找文件
  • @PropertySource不仅仅可以写在Spring的配置类上,可以写在任意会被加载的类上。
  • 如果读取中文出现了乱码,可以配置属性encoding="utf-8"

示例代码

一、resource下新建properties文件

book.properties

1
name = 科学发展

二、使用注解@PropertySource加载properties配置,使用@Value读取配置文件中的内容

为了避免出现中文乱码,encoding="utf-8"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.kakawanyifan.dao.impl;

import com.kakawanyifan.dao.BookDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Repository;

@Repository("bookDao2nd")
@PropertySource(value="book.properties",encoding="utf-8")
public class BookDaoImpl2nd implements BookDao {

@Value("${name}")
private String bookName;

public void save() {
System.out.println("save " + bookName );
}
}

运行结果:

1
save 科学发展

注意,该方法会利用暴力反射,即使我们写个Setter方法,也不会被利用。

管理第三方Bean

@Bean注解

所依赖的注解是@Bean

我们以Druid数据源的管理为例。

一、在配置类中添加一个方法,该方法的返回值就是要创建的Bean对象类型,并@Bean注解标注。

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

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
@ComponentScan("com.kakawanyifan")
public class SpringConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/s");
ds.setUsername("root");
ds.setPassword("MySQL@2022");
return ds;
}
}
  • 注意,不能使用DataSource ds = new DruidDataSource(),因为DataSource接口中没有对应的setter方法来设置属性。

二、从IoC容器中获取对象并打印

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

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;

public class App {
public static void main(String[] args) throws SQLException {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource dataSource = ctx.getBean(DataSource.class);
ResultSet resultSet = dataSource.getConnection().prepareStatement("select VERSION()").executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString(1));
}
}
}

运行结果:

1
8.0.29

引入外部配置类

如果把所有的第三方Bean都配置到Spring的配置类中,不利于代码阅读和分类管理。
我们考虑按照类别将这些Bean配置到不同的配置类中。
对于数据源的Bean,我们新建一个JdbcConfig配置类,并把数据源配置到该类下。

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

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class JdbcConfig {
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/s");
ds.setUsername("root");
ds.setPassword("MySQL@2022");
return ds;
}
}

现在的问题是,这个配置类如何能被Spring配置类加载到,并创建DataSource对象在IoC容器中?
有两个方法:

  1. 使用包扫描引入
  2. 使用@Import引入

关于"使用包扫描引入",我们不作过多的讨论,只是保证扫描包的时候,一定能扫描到"JdbcConfig",并且"JdbcConfig"上有@Configuration注解。

注意,一定是@Configuration注解,不能是@Component等其他注解。在《23.SpringBoot [3/3]》讨论Bean的加载方式的时候,我们会讨论。

主要讨论@Import引入。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.kakawanyifan;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;


@Configuration
@ComponentScan("com.kakawanyifan")
@Import(JdbcConfig.class)
public class SpringConfig {

}

注意:

  • @Import注解的参数可以是一个数组,引入多个配置类。
    1
    @Import({JdbcConfig.class,MybatisConfig.class})
  • @Import注解在配置类中只能写一次,这一点个import标签不一样。
    下面的方式是不允许的
    1
    2
    3
    4
    5
    6
    @Configuration
    @Import(JdbcConfig.class)
    @Import(Xxx.class)
    public class SpringConfig {

    }

注入资源

简单类型

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.kakawanyifan;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}

引用类型

引用类型注入只需要为Bean定义方法设置形参即可,容器会根据类型自动装配对象。

例如,在下面的例子中,我们想为dataSource注入引用类型bookDao。示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.kakawanyifan;

import com.alibaba.druid.pool.DruidDataSource;
import com.kakawanyifan.dao.BookDao;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(BookDao bookDao){
bookDao.save();
DruidDataSource ds = new DruidDataSource();
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}

运行结果:

1
2
book dao save ...
8.0.29

特别的,如果我们想根据名称注入呢?@Qualifier注解。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.kakawanyifan;

import com.alibaba.druid.pool.DruidDataSource;
import com.kakawanyifan.dao.BookDao;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(@Qualifier("bookDao2nd") BookDao bookDao){
bookDao.save();
DruidDataSource ds = new DruidDataSource();
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}

运行结果:

1
2
save...2nd...
8.0.29

小结

小结

整合MyBatis

MyBatis项目

假设存在一个MyBatis项目,其目录结构如下:

目录结构

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.kakawanyifan</groupId>
<artifactId>sm</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
</dependencies>

</project>

mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="MySQL@2022"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.kakawanyifan.mapper"></package>
</mappers>
</configuration>

User

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan.pojo;

public class User {
private int id;
private String username;
private String password;
private String gender;
private String addr;

【Getter和Setter代码略】

}

UserMapper

1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan.mapper;

import com.kakawanyifan.pojo.User;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper {
@Select("select * from user;")
List<User> selectAll();
}

MyBatisDemo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.kakawanyifan;

import com.kakawanyifan.mapper.UserMapper;
import com.kakawanyifan.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class MyBatisDemo {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSession sqlSession = sqlSessionFactory.openSession();

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.selectAll();
System.out.println(users);

sqlSession.close();
}
}

通过注解整合

Spring与Mybatis的整合,需要做两件事:

  1. Spring要管理MyBatis中的SqlSessionFactory。
  2. Spring要管理Mapper接口的扫描。

具体的步骤为:

一、添加所需要的依赖

除了Spring自身需要的依赖,还有:

  1. mybatis-spring:由MyBatis提供的,和Spring进行整合的依赖
  2. spring-jdbc,如果缺少该依赖,会报如下的错误
    1
    java.lang.ClassNotFoundException: org/springframework/dao/support/DaoSupport
  3. druid,连接池,我们也可以换成其他的连接池。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.22.RELEASE</version>
    </dependency>

    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.7</version>
    </dependency>

    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.23</version>
    </dependency>

    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.12</version>
    </dependency>

二、创建数据库连接配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.kakawanyifan;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class JdbcConfig {
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;

@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}

三、创建MyBatis配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.kakawanyifan;

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

public class MybatisConfig {

@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
//设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}

//定义bean,返回MapperScannerConfigurer对象
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage("com.kakawanyifan.mapper");
return mapperScannerConfigurer;
}
}

解释说明:

  • sqlSessionFactory方法对应mybatis-config.xmlenvironments(数据库连接)部分。
  • mapperScannerConfigurer方法对应mybatis-config.xml<mappers>-<package>部分

四、创建Spring的主配置类,读取properties文件,引入数据库连接配置类、Mybatis配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.kakawanyifan;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;

@Configuration
@ComponentScan("com.kakawanyifan")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {

}

五、运行

1
2
3
4
5
6
7
8
9
10
11
12
package com.kakawanyifan;

import com.kakawanyifan.mapper.UserMapper;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
UserMapper userMapper = ctx.getBean(UserMapper.class);
System.out.println(userMapper.selectAll());
}
}
运行结果:
1
[User{id=1, username='张三', password='333', gender='男', addr='北京'}, User{id=2, username='李四', password='456', gender='女', addr='天津'}, User{id=3, username='王五', password='789', gender='男', addr='西安'}]

小结

  • SpringConfig配置类
    • 导入JdbcConfig配置类
    • 导入MybatisConfig配置类
  • JdbcConfig配置类
    • 定义数据源(加载properties配置项:driverurlusernamepassword)
  • MybatisConfig配置类
    • 定义SqlSessionFactoryBean
    • 定义映射配置

XML方式

如果我们不想利用注解,而是利用配置文件整合呢?

将注解"翻译"成配置文件。

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?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:component-scan base-package="com.kakawanyifan"/>
<context:property-placeholder location="jdbc.properties"/>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<bean id="SqlSessionFactoryBean" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- <property name="configLocation" value="mybatis-config.xml"></property> -->
</bean>

<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.kakawanyifan.mapper"></property>
</bean>

</beans>

mybatis-config.xml文件,也可以删去。
其内容:

1
2
3
4
5
<configuration>
<mappers>
<package name="com.kakawanyifan.mapper"></package>
</mappers>
</configuration>

都在

1
2
3
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.kakawanyifan.mapper"></property>
</bean>

配置过了。

整合Junit

环境准备

我们在上文整合MyBatis的例子上继续,为了Junit的例子更标准,我们加上Service层。

示例代码:

1
2
3
4
5
6
7
8
9
package com.kakawanyifan.service;

import com.kakawanyifan.pojo.User;

import java.util.List;

public interface UserService {
List<User> getAll();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.kakawanyifan.service.impl;

import com.kakawanyifan.mapper.UserMapper;
import com.kakawanyifan.pojo.User;
import com.kakawanyifan.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {

@Autowired
UserMapper userMapper;

@Override
public List<User> getAll() {
return userMapper.selectAll();
}
}
1
2
3
4
5
6
7
8
9
10
11
package com.kakawanyifan;

import com.kakawanyifan.service.UserService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class App {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = ctx.getBean(UserService.class);
System.out.println(userService.getAll());
}

方法步骤

一、添加依赖

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.22.RELEASE</version>
<scope>test</scope>
</dependency>
  • 注意:spring-test的版本号需与spring-context的版本号保持一致。

二、编写测试类

示例代码:

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

import com.kakawanyifan.SpringConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})
public class UserServiceTest {

@Autowired
UserService userService;

@Test
public void testGetAll(){
System.out.println(userService.getAll());
}

}
  • @RunWith注解指定运行器
    • Junit运行后是基于Spring环境运行的,所以Spring提供了一个专用的类运行器,这个务必要设置,这个类运行器就在Spring的测试专用包中提供的,导入的坐标就是这个东西SpringJUnit4ClassRunner
  • @ContextConfiguration注解来指定配置类或者配置文件。
    • 如果使用的是注解配置类,@ContextConfiguration(classes = 配置类.class)
    • 如果使用的是配置文件,@ContextConfiguration(locations={配置文件名,...})

整合Servlet

思路

在上文,我们的ApplicationContext是通过"new"的方式获取的,例如:

  • 通过配置文件方式

    1
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
  • 通过配置类方式

    1
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);

在Servlet项目中,可以使用ServletContextListener监听应用的启动,我们可以在应用启动时,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。

实现

ContextLoaderListener

上文我们的分析,不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。

所以我们需要做的只有两件事:

  1. 在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)。
  2. 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext

spring-web

导入Spring集成Servlet的依赖

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.24</version>
</dependency>

当然,Servlet以及Spring相关的依赖也还是需要的。

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.24</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>

配置ContextLoaderListener监听器

web.xml配置文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

</web-app>

实际上,很少有用Spring去整合Servlet的,即所谓的"spring-web"。
更多的,是我们在《17.SpringMVC》讨论的SpringMVC。
关于将classpath:applicationContext.xml,改成用配置类的方式,我也暂未找到实现方法。

通过工具获得应用上下文对象

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

import com.kakawanyifan.service.ServiceDemo;
import com.kakawanyifan.service.impl.ServiceDemoImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(urlPatterns = "/find")
public class HttpServletDemo extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
ServiceDemo serviceDemo = applicationContext.getBean(ServiceDemoImpl.class);
serviceDemo.p();
}
}
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/10815
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区