本章主要讨论:
IoC(Inverse Of Control,控制反转) DI(Dependency Injection,依赖注入) 下一章《16.Spring Framework [2/2]》 主要讨论:AOP(Aspect Oriented Programming,面向切面编程)。
概述
官网:https://spring.io 。
通过官网,我们会发现有:Spring Framework
、Spring Boot
和Spring 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
。 添加方法如下:
三、在配置文件applicationContext.xml
中完成Bean的配置
1 2 3 4 5 <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) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); BookService bookService = (BookService) ctx.getBean("bookService" ); bookService.save(); } }
运行结果:
这样,我们就做到了,使用对象时,不主动创建对象,改为由第三方提供对象。
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; 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 id ="bookDao" class ="com.kakawanyifan.dao.impl.BookDaoImpl" /> <bean id ="bookService" class ="com.kakawanyifan.service.impl.BookServiceImpl" > <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) { 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
,依旧是默认的单例。
思考
为什么Bean默认为单例?
避免了Bean对象的频繁创建与销毁,实现了Bean的复用,提高性能。
Bean在容器中是单例的,会不会产生线程安全问题?
可能会产生线程安全问题。
正如我们在《7.多线程 [1/2]》 的讨论,线程安全问题的内在原因或者根本原因有三点:
多个线程在操作共享的数据
多个线程对共享数据有写操作
操作共享数据的线程执行的操作有多个
所以,如果Bean中的某个成员变量存在上述情况的话,就会产生线程不安全。
也正因为如此,我们一般只把表现层对象、业务层对象、数据层对象、工具对象等交给容器管理;对于封装实例的域对象等,不会交给容器管理。
(在《17.SpringMVC》 ,我们还会讨论Controller,这其实也是一种Bean,也是默认单例。)
创建
Bean也是对象,所以创建Bean的方式,也是就是创建对象的方式。
创建对象的方式有三种:
构造方法
静态工厂
实例工厂
创建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) { 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) { 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 () { BookServiceImpl rtn = new BookServiceImpl(); 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) { 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 <bean id ="bookServiceFactory" class ="com.kakawanyifan.BookServiceFactory" />
调用对象中的方法来创建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) { 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(); } 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) { 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..." ); } public void init () { System.out.println("init..." ); } 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) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" ); BookService bookService = (BookService) ctx.getBean("bookService" ); } }
运行结果
有没有问题?
init方法确实是执行了,但是destroy方法没有执行。
这是因为,Spring的IoC容器是运行在JVM中;运行main方法后,JVM启动,Spring加载配置文件生成IoC容器,从容器获取Bean,然后调方法执行;main方法执行完后,JVM退出,这个时候IoC容器中的Bean还没有来得及销毁就已经结束了;所以没有调用对应的destroy方法。
解决方法有:
close关闭容器
注册钩子关闭容器
close关闭容器
在ApplicationContext
中没有close方法,我们需要将ApplicationContext
更换成ClassPathXmlApplicationContext
。
1 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml" );
然后,调用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(); } }
运行结果:
注册钩子关闭容器
钩子的思路是,在容器关闭之前,提前设置好回调函数,让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" ); } }
运行结果:
InitializingBean和DisposableBean
Spring提供了两个接口来完成生命周期的控制,以简化我们的开发。
这两个接口和需要重写的方法分别是:
InitializingBean
DisposableBean
与上文实现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,依赖注入。
在讨论依赖注入之前,我们先讨论两个问题。
向一个类中传递数据有几种方法?
两种:Setter方法、构造方法。
传递的数据类型有哪些:
有对象的
即不包括String的引用数据类型
这里我们称之为引用类型
没有对象的
即基本数据类型及其包装类以及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 >
注意:
property
标签表示Setter方式注入,如果是构造方法注入,可以在constructor-arg
标签内部写list
、set
、map
等标签。
如果在集合中要添加引用类型,只需要把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所依赖的资源在容器中自动查找并注入的过程被称为自动装配。
自动装配的方式有:
按类型
按名称
按构造方法
注意:自动装配优先级低于上文的手动注入(Setter方法注入和构造方法注入),如果两者同时出现,自动装配失效。
按类型
按类型是最常用的一种方式。
autowire="byType"
该方式需要注意的是:
需要注入属性的类中对应属性的Setter方法不能省略
被注入的对象必须要被Spring的IoC容器管理
如果找到多个对象,会报NoUniqueBeanDefinitionException
如果没有找到对象,会注入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" /> <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包,全类名用第三方的全类名。
例子
在这里,我们分别以Druid
和Hikari
为例。
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 )); } } }
运行结果:
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 )); } } }
运行结果:
加载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
命名空间
注意箭头所指的部分:
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 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(); } }
运行结果:
出问题了!
应该打印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()); } }
运行结果:
加载多个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的方式还有:
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
这种方式可以解决类型强转问题,但是多了一个参数。
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" ); } }
运行结果:
延迟加载
我们和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 ("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 ..." ); } @PostConstruct public void init () { System.out.println("init ..." ); } @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(); } }
运行结果:
@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(); } }
运行结果:
这也没报错啊。
@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 ); } }
运行结果:
读取properties
@PropertySource
加载properties配置
@Value
读取配置文件中的内容
注意:
如果读取的properties配置文件有多个,可以使用@PropertySource
的属性来指定多个1 @PropertySource ({"jdbc.properties" ,"xxx.properties" })
@PropertySource
注解属性中 不支持通配符*
,运行会报错。
@PropertySource
注解属性中可以把classpath:
加上,代表从当前项目的根路径找文件
@PropertySource
不仅仅可以写在Spring的配置类上,可以写在任意会被加载的类上。
如果读取中文出现了乱码,可以配置属性encoding="utf-8"
。
示例代码
一、resource下新建properties文件
book.properties
二、使用注解@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 ); } }
运行结果:
注意,该方法会利用暴力反射,即使我们写个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 )); } } }
运行结果:
引入外部配置类
如果把所有的第三方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容器中?
有两个方法:
使用包扫描引入
使用@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; } }
运行结果:
小结
整合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的整合,需要做两件事:
Spring要管理MyBatis中的SqlSessionFactory。
Spring要管理Mapper接口的扫描。
具体的步骤为:
一、添加所需要的依赖
除了Spring自身需要的依赖,还有:
mybatis-spring
:由MyBatis提供的,和Spring进行整合的依赖spring-jdbc
,如果缺少该依赖,会报如下的错误1 java.lang.ClassNotFoundException: org/springframework/dao/support/DaoSupport
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 public MapperScannerConfigurer mapperScannerConfigurer () { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer(); mapperScannerConfigurer.setBasePackage("com.kakawanyifan.mapper" ); return mapperScannerConfigurer; } }
解释说明:
sqlSessionFactory
方法对应mybatis-config.xml
的environments
(数据库连接)部分。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配置项:driver
、url
、username
、password
)
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 > </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
供使用者获得应用上下文对象。
所以我们需要做的只有两件事:
在web.xml中配置ContextLoaderListener
监听器(导入spring-web坐标)。
使用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(); } }