Spring官网
Spring学习资料
Spring学习目录
- Spring的IOC和DI
- Spring的AOP
- Spring的事务控制
- 源码
一、概念
Spring Framework 是一个开源的Java SE/EE 全栈(full-stack)的应用程序框架,以 IoC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,面向切面编程)为内核,还能整合开源世界的众多著名的第三方框架和类库。
Spring的源码是经典学习范例,对java的设计模式灵活运用,有能力可以研究。
作用
- 方便解耦,简化开发
- AOP编程支持
- 声明式事务的支持
- 方便程序的测试
- 方便集成各种优秀框架
- 降低JavaEE Api的使用难度
二、耦合
耦合是指程序间的依赖关系,包括类之间的依赖、方法间的依赖
解耦就是降低程序间的依赖关系
实际开发中做到:编译期不依赖,运行时才依赖
下面举个例子
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | public class JdbcDemo01 {public static void main(String[] args) throws Exception {
 
 
 
 Class.forName("com.mysql.cj.jdbc.Driver");
 
 
 
 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false", "root", "root");
 
 PreparedStatement ps = conn.prepareStatement("select * from account");
 
 ResultSet rs = ps.executeQuery();
 
 while(rs.next()){
 System.out.println(rs.getString("name"));
 }
 
 rs.close();
 ps.close();
 conn.close();
 }
 }
 
 | 
对于传统的开发工程,我们可以参照下面这种结构,ui调用service,service调用dao,其中如果没有dao的实现类,那么就不能new对象,整个工程就会报错,无法编译,这就是存在耦合问题。
2.1 工厂模式解耦
为了降低耦合,我们可以通过工厂模式,读取配置文件来创建对象,从而降低耦合度。

对于类之间依赖,解耦的思路
- 通过工厂模式,使用反射来创建对象,避免使用new关键字。- 不能像上面那样添加字符串,那就写死了,如果mysql换成oracle,岂不是又得改一遍?所以需要配合文件
 
- 通过读取配置文件来获取要创建的对象全限定类名
创建生产bean工厂
| 12
 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
 
 | public class BeanFactory  {private static Properties props;
 
 static{
 try {
 props=new Properties();
 InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
 props.load(is);
 } catch (Exception e) {
 throw new ExceptionInInitializerError("读取配置文件失败!");
 }
 }
 
 
 
 
 
 
 
 public static <T>T getBean(String beanName){
 T bean = null;
 String beanPath=props.getProperty(beanName);
 try {
 bean = (T) Class.forName(beanPath).newInstance();
 } catch (InstantiationException e) {
 e.printStackTrace();
 } catch (IllegalAccessException e) {
 e.printStackTrace();
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 return bean;
 }
 }
 
 | 
然后将其他获取bean的new方法,都换成通过工厂来获取
| 12
 3
 
 | AccountService as = (AccountService)BeanFactory.getBean("accountService");
 AccountDao accountDao= (AccountDao) BeanFactory.getBean("accountDao");
 
 | 
通过这么处理,即使删除掉了AccountDaoImpl,那么也是可以编译的,不报错,但是运行时,会有找不到文件的异常弹出。

工厂模式中也存在问题,进行如下修改。
AccountDaoImpl.java
| 12
 3
 4
 5
 6
 7
 8
 
 | public class AccountDaoImpl implements AccountDao {private int i=1;
 @Override
 public void saveAccount() {
 i++;
 System.out.println("保存数据"+i);
 }
 }
 
 | 
Client.java
| 12
 3
 4
 5
 6
 7
 8
 
 | public class Client {public static void main(String[] args) {
 for(int i=0;i<10;i++){
 AccountService as = (AccountService)BeanFactory.getBean("accountService");
 as.saveAccount();
 }
 }
 }
 
 | 
但是每次输出的结果都是,“保存数据2”,可知,每执行一次getBean都创建了AccountService对象,这个对象是多例的。
多例对象,会在内存中被创建多次,执行效率就低了;单例对象,在内存中只会被创建一次,就比如Servlet,执行效率高。
在该工厂中,其实用单例或者多例都可以,那从效率考虑,肯定是用单例对象更好。
2.2 工厂生产单例对象
对工厂进行修改,在BeanFactory加载时,实例化对象作为value,存储到map中,获取时直接通过map获取,这样就到做到单例了。
| 12
 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
 
 | public class BeanFactory {
 private static Properties props;
 
 
 private static Map<String,Object> beans;
 
 
 static {
 System.out.println("静态代码块执行..");
 try {
 props = new Properties();
 InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
 props.load(is);
 
 beans = new HashMap<String,Object>();
 
 Enumeration<Object> keys = props.keys();
 
 while (keys.hasMoreElements()){
 String key = keys.nextElement().toString();
 String beanPath = props.getProperty(key);
 
 
 Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
 beans.put(key,value);
 }
 }catch(Exception e){
 throw new ExceptionInInitializerError("初始化properties失败!");
 }
 System.out.println("静态代码块执行完毕!");
 }
 
 
 
 
 
 
 public static Object getBean(String beanName){
 Object o = beans.get(beanName);
 System.out.println("获取"+beanName+"..此时beans的大小:"+beans.size()+"..此时要获取的内容:"+o.toString());
 return o;
 }
 
 }
 
 | 
注意
这个地方卡了我快7个小时,最后也没整明白。我把疑问放到b站了。
在使用过程中,成员变量直接调用BeanFactory获取AccountDao(或者默认构造方法中进行实例)时,都会导致静态代码块未执行完毕,就执行静态方法。但是在java12的环境下,直接异常,而在java8下,就能运行,虽然结果不是预期那样。
最后,我将获取AccountDao放到重写的方法中,才有了预期的结果。但我纳闷的是,为什么不能直接成员变量实例化呢?
下面是最后的解决方案,只需要修改此处即可。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | public class AccountServiceImpl implements AccountService {
 
 private AccountDao accountDao;
 
 @Override
 public void saveAccount() {
 
 accountDao = (AccountDaoImpl) BeanFactory.getBean("accountDao");
 accountDao.saveAccount();
 }
 }
 
 | 
三、IOC
3.1 控制反转IOC
直接通过new,获取对象,这种方式是主动的,它可以直接控制获取的是什么对象。
| 1
 | AccountDao accountDao=new AccountDaoImpl();
 | 
通过上面解耦的例子,我们获取对象,是向工厂获取,这种方式是被动的,他的控制在于getBean后面传递的参数。
| 1
 | AccountDao accountDao=(AccountDaoImpl)BeanFactory.getBean("accountDao");
 | 
这种被动接收的方式获取对象的思想就是控制反转,带来的好处,就是降低了程序间的依赖关系,也就是耦合。

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度。
其中最常见的方式
- 依赖注入(Dependency Injection,简称DI)
- 依赖查找(Dependency Lookup)
在实际开发中如果自己实现控制反转,会消耗大量时间精力,所以,我们可以通过spring来实现ioc。
第一个spring项目
从spring官网的库中,下载spring5.3.4的源码

在maven的pom中,添加spring-context的依赖,就会出现spring框架所需要的的核心jar包。

至于为什么要添加spring-context的依赖,而不是添加别的依赖。通过下图的依赖关系,我们可以知道,spring-context依赖了其他的jar包,所以我们在导入spring-context的时候,maven会将其他关联的一并导入。

创建xml配置文件spring.xml。格式参考官网,ctrl+f直接搜索xmlns即可。
| 12
 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
 https://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="accountService" class="top.meethigher.demo03.service.impl.AccountServiceImpl"></bean>
 <bean id="accountDao" class="top.meethigher.demo03.dao.impl.AccountDaoImpl"></bean>
 </beans>
 
 | 
Spring可以通过ApplicationContext下的实现类,来实现读取xml配置,获取核心容器,从而获取bean对象。常用的实现类如下
- ClassPathXmlApplicationContext:加载类路径下的配置文件,要求配置文件必须在类路径下
- FileSystemXmlApplicationContext:加载磁盘任意路径下的配置文件(必须有访问权限)
- AnnotationConfigApplicationContext:读取注解
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | public class SpringClient {
 
 
 
 public static void main(String[] args) {
 
 ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
 
 
 AccountServiceImpl accountService = (AccountServiceImpl) ac.getBean("accountService");
 AccountDaoImpl accountDao = (AccountDaoImpl) ac.getBean("accountDao");
 
 System.out.println(accountService);
 System.out.println(accountDao);
 }
 }
 
 | 
3.2 创建对象
创建对象时机
核心容器的两个接口引发的问题
- ApplicationContext- 它在创建核心容器时,创建对象采取的策略是,立即加载。也就是说,只要一读取配置文件,马上创建配置文件中的对象。
- 适用单例对象
 
- BeanFactory- 它在创建核心容器时,创建对象采取的策略是,延迟加载。也就是说,什么时候要获取对象,什么时候才开始创建对象。
- 适用多例对象
 
对于这两个的测试,可以通过打断点来验证,具体步骤采用视频比较直观。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 
 | public class SpringClient {
 
 
 
 public static void main(String[] args) {
 
 
 
 
 
 
 
 
 
 
 
 
 Resource resource=new ClassPathResource("spring.xml");
 BeanFactory factory = new XmlBeanFactory(resource);
 AccountService accountService = (AccountService) factory.getBean("accountService");
 System.out.println(accountService);
 }
 }
 
 | 
但是在实际开发中,还是ApplicationContext使用用的更多,也更智能,他可以根据配置文件来决定是否要延迟加载。
BeanFactory只是当做一个演示。通过下面的关系图,可以知道其实BeanFactory属于底层接口,所以有些功能也并不完善,并不推荐用。

获取bean三种方式
Spring对bean的管理细节
- 创建bean的三种方式
- bean对象的作用范围
- bean对象的生命周期
第一种方式:使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数,创建bean对象。此时,若没有默认构造函数(比如只有一个有参构造函数),则对象无法创建。
第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
创建InstanceFactory
| 12
 3
 4
 5
 
 | public class InstanceFactory {public AccountService getAccountService(){
 return new AccountServiceImpl();
 }
 }
 
 | 
我们想将InstanceFactory中创建的AccountService对象存入spring容器,可以使用第二种方式。
| 12
 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
 https://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="instanceFactory" class="top.meethigher.demo04.factory.InstanceFactory"></bean>
 
 <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
 </beans>
 
 | 
第三种方式:使用工厂中的静态方法创建对象(使用某个类的静态方法创建对象,并存入spring容器)
创建StaticFactory
| 12
 3
 4
 5
 
 | public class StaticFactory {public static AccountService getAccountService(){
 return new AccountServiceImpl();
 }
 }
 
 | 
使用第三种方式获取accountService
| 12
 3
 4
 5
 6
 7
 
 | <?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
 https://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="accountService" class="top.meethigher.demo04.factory.StaticFactory" factory-method="getAccountService"></bean>
 </beans>
 
 | 
第二种和第三种方式,适合那些我们引入的jar包,我们无法通过修改源码的方式来提供默认构造函数
设置bean作用范围
在第二节的时候,我们通过修改工厂代码,来实现了生产单例对象,那么spring是不是单例对象呢,验证一下。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | public class SpringClient04 {
 
 
 
 public static void main(String[] args) {
 ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
 for(int i=0;i<10;i++){
 AccountService accountService = (AccountService) ac.getBean("accountService");
 System.out.println(accountService);
 }
 }
 }
 
 | 
通过输出结果,可知,默认构造函数只走了一次,后面可知,获取10次对象都是同一个。

bean标签的scope属性,用于指定bean的作用范围(常用就是单例和多例)
- singleton:单例(默认值)
- prototype:多例。每调用一次getBean,就重新获取一个新的对象
- request:作用于web应用的请求范围
- session:作用于web应用的会话范围
- global-session:作用于集群环境的会话范围(全局会话范围),当不是集群时,它就是session
在xml配置bean的时候,将scope配置为prototype,再运行代码的时候,就会发现,没getBean一次,就会创建一次对象,这就是多例。

集群就是,很多相互独立、高速互联的计算机组成的组。
就比如我们访问一个大型网站,登录时,会有验证码,我们集群里面挑选一台空闲的机器来存储生成验证码的session,但是,当我们去提交验证码时,若调用了另外一台机器,则该session就不会存在了。
那么我们可以通过配置global-session,让集群中的所有服务器共用一个session。

bean生命周期
单例对象
- 创建:当容器创建时(解析配置文件时),创建对象
- 服务:只要容器存在,对象就一直存在着
- 销毁:容器销毁,对象消亡
AccountServiceImpl
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | public class AccountServiceImpl implements AccountService {
 public AccountServiceImpl() {
 System.out.println("AccountService对象已经创建了!");
 }
 
 public void saveAccount() {
 System.out.println("service中的saveAccount方法执行");
 }
 
 public void init() {
 System.out.println("对象初始化");
 }
 
 public void destroy() {
 System.out.println("对象销毁");
 }
 }
 
 | 
测试类
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | public class SpringClient04 {public static void main(String[] args) {
 ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
 AccountService accountService = (AccountService) ac.getBean("accountService");
 System.out.println(accountService);
 
 
 ac.close();
 }
 }
 
 | 
spring.xml
| 12
 3
 4
 5
 6
 7
 
 | <?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
 https://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean init-method="init" destroy-method="destroy" scope="singleton" id="accountService" class="top.meethigher.demo04.service.impl.AccountServiceImpl"></bean>
 </beans>
 
 | 
输出结果如下

总结就是,单例对象的生命周期和容器相同。
多例对象
- 创建:当我们使用对象时(可以通过断点的方式,进行测试),创建对象
- 服务:对象使用过程中
- 销毁:当对象长时间不使用时,且没有别的对象引用时,由java的垃圾回收器回收。
注意
spring通过配置文件中的scope,判断要创建单例还是多例对象。
如果是单例,则立即创建。
如果是多例,则延迟创建。
四、DI
4.1 依赖注入DI
控制反转(Inversion of Control)的作用是降低程序间的耦合(依赖关系)。依赖关系的管理,都交给了spring来维护。这种依赖关系的管理,就叫做依赖注入(Dependency Injection)
在当前类中需要用到的其他类的对象,由spring来提供,我们只需要在配置文件或注解中说明。
依赖注入的类型
- 基本类型和String
- bean类型(在配置文件中、注解中配置的bean)
- 复杂类型/集合类型
注入的方式
构造函数注入
若是经常变化的数据,并不适用于构造函数注入的方式。
标签constructor-arg,放置在bean标签的内部。
五个属性
- 位置- index:指定构造函数中该索引位置的参数。参数索引的位置从0开始
- name:指定构造函数中该名称的参数。常用√
- type:指定要注入的数据的数据类型,该数据类型也是构造函数中某个或者某些参数的类型
 
- 传值- value:传入的参数的值,只适用于基本类型和String类型的数据
- ref: 指定其他的bean类型,它指的就是ioc核心容器中出现过得bean对象
 
AccountServiceImpl
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | public class AccountServiceImpl implements AccountService {
 private String name;
 private Integer age;
 private Date birthday;
 
 public AccountServiceImpl(String name, Integer age, Date birthday) {
 this.name = name;
 this.age = age;
 this.birthday = birthday;
 }
 
 public void saveAccount() {
 System.out.println("service中的saveAccount方法执行--"+name+"--"+age+"--"+birthday);
 }
 }
 
 | 
spring.xml
| 12
 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
 https://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="accountService" class="top.meethigher.demo05.service.impl.AccountServiceImpl">
 <constructor-arg index="0" value="白沉香"/>
 <constructor-arg index="1" value="18"/>
 <constructor-arg index="2" ref="time"/>
 </bean>
 <bean id="time" class="java.util.Date"></bean>
 </beans>
 
 | 
测试类
| 12
 3
 4
 5
 6
 7
 
 | public class SpringClient05 {public static void main(String[] args) {
 ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
 AccountService accountService = (AccountService) ac.getBean("accountService");
 accountService.saveAccount();
 }
 }
 
 | 
优点:获取bean对象时,注入数据是必需的操作,否则对象无法创建成功
弊端:改变了bean对象的实例化方式,在创建对象时,如果用不到这些数据,也必须提供
☆Set方法注入
标签property,bean标签的内部。
三个属性
- name:指定注入所调用的set方法名称
- value:传入的参数的值,只适用于基本类型和String类型的数据
- ref:指定其他的bean类型,它指的就是ioc核心容器中出现过得bean对象
AccountServiceImpl
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | public class AccountServiceImpl implements AccountService {
 private String name;
 private Integer age;
 private Date birthday;
 
 public void setName(String name) {
 this.name = name;
 }
 
 public void setAge(Integer age) {
 this.age = age;
 }
 
 public void setBirthday(Date birthday) {
 this.birthday = birthday;
 }
 
 public void saveAccount() {
 System.out.println("service中的saveAccount方法执行--"+name+"--"+age+"--"+birthday);
 }
 
 }
 
 | 
spring.xml
| 12
 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
 https://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="accountService" class="top.meethigher.demo05.service.impl.AccountServiceImpl">
 <property name="name" value="白沉香"></property>
 <property name="age" value="18"></property>
 <property name="birthday" ref="time"></property>
 </bean>
 <bean id="time" class="java.util.Date"></bean>
 </beans>
 
 | 
测试类跟构造函数注入的一样。
弊端:如果有某个成员必须有值,则set方法可能没有执行。
优势:改变了bean对象的实例化方式,在创建对象时,没有明确的限制。
复杂类型注入
用于给List结构(单列)注入的标签
用于Map结构(双列)注入的标签
总结:结构相同,标签可以互换
AccountServiceImpl2
| 12
 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
 
 | public class AccountServiceImpl2 implements AccountService {private String[] myStrs;
 private List<String> myList;
 private Set<String> mySet;
 private Map<String, String> myMap;
 private Properties props;
 
 public AccountServiceImpl2(Set<String> mySet, Map<String, String> myMap) {
 this.mySet = mySet;
 this.myMap = myMap;
 }
 
 public void setMyStrs(String[] myStrs) {
 this.myStrs = myStrs;
 }
 
 public void setMyList(List<String> myList) {
 this.myList = myList;
 }
 
 
 public void setProps(Properties props) {
 this.props = props;
 }
 
 @Override
 public void saveAccount() {
 System.out.println("myStrs=" + Arrays.toString(myStrs) +
 "\nmyList=" + myList +
 "\nmySet=" + mySet +
 "\nmyMap=" + myMap +
 "\nprops=" + props);
 }
 }
 
 | 
spring.xml
| 12
 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
 
 | <?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
 https://www.springframework.org/schema/beans/spring-beans.xsd">
 <bean id="accountService" class="top.meethigher.demo05.service.impl.AccountServiceImpl2">
 
 <constructor-arg name="mySet">
 <set>
 <value>白沉香</value>
 <value>小舞</value>
 <value>胡列娜</value>
 </set>
 </constructor-arg>
 <constructor-arg name="myMap">
 <map>
 <entry key="小舞" value="18000"></entry>
 </map>
 </constructor-arg>
 
 <property name="myStrs">
 <array>
 <value>白沉香</value>
 <value>小舞</value>
 <value>胡列娜</value>
 </array>
 </property>
 <property name="myList">
 <list>
 <value>白沉香</value>
 <value>小舞</value>
 <value>胡列娜</value>
 </list>
 </property>
 <property name="props">
 <props>
 <prop key="小舞">180000</prop>
 <prop key="白沉香">18</prop>
 <prop key="胡列娜">22</prop>
 </props>
 </property>
 </bean>
 </beans>
 
 | 
4.2 注解
spring既可以通过xml配置,也可以通过注解来配置。本章的1到3的例子,都是基于xml。这节讲注解来实现以上功能。
如果使用注解,那么需要在xml配置扫描当前包下文件所有的注解。
xml的约束可以去官方文档找,进入core,关键字搜索xmlns:context即可。
| 12
 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
 https://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 https://www.springframework.org/schema/context/spring-context.xsd">
 
 
 <context:component-scan base-package="top.meethigher.demo06"></context:component-scan>
 </beans>
 
 | 
注意
注解中,如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略
比如@Component(value=”accountService”),可以省略写成@Component(“accountService”)
常用注解
常用的11个注解,分为四类
以上三个注解,Resource、PreDestroy、PostConstruct不是spring提供的,是java提供的,所以我们需要在pom中引入javax.annotation。
如果要注入复杂/集合类型,只能使用xml
| 12
 3
 4
 5
 
 | <dependency><groupId>javax.annotation</groupId>
 <artifactId>javax.annotation-api</artifactId>
 <version>1.3.2</version>
 </dependency>
 
 | 
使用注解
首先要扫描指定包下的所有注解,配置xml。跳到3.4
AccountServiceImpl
| 12
 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
 
 | @Component(value = "accountService")public class AccountServiceImpl implements AccountService {
 
 @Autowired
 @Qualifier("accountDao1")
 
 private AccountDao accountDao;
 @Value("20")
 private Integer num;
 
 public AccountServiceImpl() {
 System.out.println("对象已经创建了!"+"num="+num+",accountDao="+accountDao);
 }
 
 @Override
 public void saveAccount() {
 accountDao.saveAccount();
 System.out.println(num);
 }
 
 @PostConstruct
 public void init() {
 System.out.println("初始化方法");
 }
 
 @PreDestroy
 public void destroy() {
 System.out.println("销毁方法");
 }
 }
 
 | 
AccountDaoImpl1
| 12
 3
 4
 5
 6
 7
 8
 
 | @Component("accountDao1")
 public class AccountDaoImpl1 implements AccountDao {
 @Override
 public void saveAccount() {
 System.out.println("保存数据1");
 }
 }
 
 | 
AccountDaoImpl
| 12
 3
 4
 5
 6
 7
 
 | @Component("accountDao")public class AccountDaoImpl implements AccountDao {
 @Override
 public void saveAccount() {
 System.out.println("保存数据");
 }
 }
 
 | 
测试类
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | public class SpringClient06 {public static void main(String[] args) {
 ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
 AccountServiceImpl accountService = (AccountServiceImpl) ac.getBean("accountService");
 System.out.println(accountService);
 accountService.saveAccount();
 ac.close();
 }
 }
 
 | 
以上述代码为例,来了解一下@Autowired的内部原理。
首先,通过xml的配置,扫描包下的所有bean的注解,然后存入spring容器。

当我们给类中的变量加上@Autowired时,他就会根据当前的类型,去springioc容器中找该类型。
如果唯一,那就进行注入。
如果不唯一,那就需要根据当前的变量名称,来寻找springioc容器中的key,寻找到了,就进行注入,否则报错。
| 12
 
 | private AccountDao accountDao=null;权限修饰符 数据类型    变量名     数据类型
 
 | 
由此为了方便使用,才有了@Qualifier,以及更方便地@Resource
五、CRUD案例
环境
- spring
- c3p0:以前没用过
- dbutils:以前没用过
在创建一个三层架构的项目时,其实有更方便的创建步骤
- domain实体类
- service业务层:创建接口、创建一系列方法、创建实现类
- dao持久层:创建接口,将service接口中的方法复制到dao接口,创建实现类
- 表现层(此处可以通过单元测试来模拟表现层)
5.1 基于XML
Account
| 12
 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
 
 | public class Account implements Serializable {private Integer id;
 private String name;
 private Float money;
 
 public Integer getId() {
 return id;
 }
 
 public void setId(Integer id) {
 this.id = id;
 }
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 public Float getMoney() {
 return money;
 }
 
 public void setMoney(Float money) {
 this.money = money;
 }
 
 @Override
 public String toString() {
 return "Account{" +
 "id=" + id +
 ", name='" + name + '\'' +
 ", money=" + money +
 '}';
 }
 }
 
 | 
AccountDao、AccountService、AccountServiceImpl省略,内容自己能想出来。
AccountDaoImpl
| 12
 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
 
 | public class AccountDaoImpl implements AccountDao {private QueryRunner runner;
 
 public void setRunner(QueryRunner runner) {
 this.runner = runner;
 }
 
 @Override
 public List<Account> findAll() {
 try {
 return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
 } catch (SQLException e) {
 
 throw new RuntimeException();
 }
 }
 
 @Override
 public Account findById(Integer id) {
 try {
 return runner.query("select * from account where id=?", new BeanHandler<Account>(Account.class), id);
 } catch (SQLException e) {
 throw new RuntimeException();
 }
 }
 
 
 @Override
 public void updateAccount(Account account) {
 try {
 runner.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 
 @Override
 public void deleteAccount(Integer id) {
 try {
 runner.update("delete from account where id=?", id);
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 
 
 
 
 
 
 @Override
 public void saveAccount(Account account) {
 try {
 runner.update("insert into account(name,money) values(?,?)", account.getName(), account.getMoney());
 
 account.setId(Integer.parseInt(runner.query("select last_insert_id() id",new MapHandler()).get("id").toString()));
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 
 
 
 
 
 @Override
 public int findCount() {
 try {
 
 
 
 return Integer.parseInt(runner.query("select count(*) from account",new ScalarHandler<>(1)).toString());
 } catch (SQLException e) {
 e.printStackTrace();
 throw new RuntimeException();
 
 }
 }
 }
 
 | 
spring.xml
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | <?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
 https://www.springframework.org/schema/beans/spring-beans.xsd">
 
 <bean id="accountService" class="top.meethigher.demo07.service.impl.AccountServiceImpl">
 <property name="accountDao" ref="accountDao"></property>
 </bean>
 
 <bean id="accountDao" class="top.meethigher.demo07.dao.impl.AccountDaoImpl">
 <property name="runner" ref="runner"></property>
 </bean>
 
 <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
 <constructor-arg name="ds" ref="ds"></constructor-arg>
 </bean>
 
 <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
 <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
 <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false"></property>
 <property name="user" value="root"></property>
 <property name="password" value="root"></property>
 </bean>
 </beans>
 
 | 
junit单元测试
| 12
 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
 
 | public class ClientTest {private ClassPathXmlApplicationContext cac;
 private AccountService accountService;
 @Before
 public void setUp() throws Exception {
 cac = new ClassPathXmlApplicationContext("spring.xml");
 accountService = cac.getBean("accountService", AccountService.class);
 }
 
 @After
 public void tearDown() throws Exception {
 cac.close();
 }
 
 @Test
 public void testFindAll() {
 List<Account> all = accountService.findAll();
 System.out.println(all);
 }
 
 @Test
 public void testFindById() {
 Account byId = accountService.findById(1);
 System.out.println(byId);
 }
 
 @Test
 public void testUpdateAccount() {
 Account account = new Account();
 account.setId(4);
 account.setName("水冰儿");
 account.setMoney(7000f);
 accountService.updateAccount(account);
 }
 
 @Test
 public void testDeleteAccount() {
 accountService.deleteAccount(4);
 }
 
 
 
 
 @Test
 public void testSaveAccount() {
 Account account = new Account();
 account.setName("水冰儿");
 account.setMoney(8000f);
 accountService.saveAccount(account);
 System.out.println(account);
 }
 
 
 
 
 @Test
 public void testFindCount() {
 System.out.println(accountService.findCount());
 }
 
 
 
 
 @Test
 public void test() {
 Account account = new Account();
 m(account);
 System.out.println(account);
 }
 public void m(Account account){
 account.setId(1);
 }
 }
 
 | 
在该过程中,有几点注意事项
- dbutils的使用。通过dbutils实现查询后保存id返回、实现聚合查询
- 形参是bean对象时(相当于传地址),改变形参,实参也会改变
- 编译期异常必须处理,运行时异常可以不用处理
- sql语句, select last_insert_id()
- 连接池应该使用多例,避免多个用户使用同一个对象引发问题。
5.2 基于XML与注解
引用的外部的类,需要xml来配置,但是自己写的类,是可以通过注解来配置了。
AccountDaoImpl
| 12
 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
 
 | @Repository("accountDao")public class AccountDaoImpl implements AccountDao {
 
 @Autowired
 private QueryRunner runner;
 
 
 @Override
 public List<Account> findAll() {
 try {
 return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
 } catch (SQLException e) {
 
 throw new RuntimeException();
 }
 }
 
 @Override
 public Account findById(Integer id) {
 try {
 return runner.query("select * from account where id=?", new BeanHandler<Account>(Account.class), id);
 } catch (SQLException e) {
 throw new RuntimeException();
 }
 }
 
 
 @Override
 public void updateAccount(Account account) {
 try {
 runner.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 
 @Override
 public void deleteAccount(Integer id) {
 try {
 runner.update("delete from account where id=?", id);
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 
 
 
 
 
 
 @Override
 public void saveAccount(Account account) {
 try {
 runner.update("insert into account(name,money) values(?,?)", account.getName(), account.getMoney());
 
 account.setId(Integer.parseInt(runner.query("select last_insert_id() id",new MapHandler()).get("id").toString()));
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 
 
 
 
 
 @Override
 public int findCount() {
 try {
 
 
 
 return Integer.parseInt(runner.query("select count(*) from account",new ScalarHandler<>(1)).toString());
 } catch (SQLException e) {
 e.printStackTrace();
 throw new RuntimeException();
 
 }
 }
 }
 
 | 
AccountServiceImpl
| 12
 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
 
 | @Service("accountService")public class AccountServiceImpl implements AccountService {
 
 @Resource(name="accountDao")
 private AccountDao accountDao;
 
 @Override
 public List<Account> findAll() {
 return accountDao.findAll();
 }
 
 @Override
 public Account findById(Integer id) {
 return accountDao.findById(id);
 }
 
 @Override
 public void saveAccount(Account account) {
 accountDao.saveAccount(account);
 }
 
 @Override
 public void updateAccount(Account account) {
 accountDao.updateAccount(account);
 }
 
 @Override
 public void deleteAccount(Integer id) {
 accountDao.deleteAccount(id);
 }
 
 @Override
 public int findCount() {
 return accountDao.findCount();
 }
 }
 
 | 
spring.xml
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | <?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
 https://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 https://www.springframework.org/schema/context/spring-context.xsd">
 <context:component-scan base-package="top.meethigher.demo08"></context:component-scan>
 
 
 <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
 <constructor-arg name="ds" ref="ds"></constructor-arg>
 </bean>
 
 <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
 <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
 <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false"></property>
 <property name="user" value="root"></property>
 <property name="password" value="root"></property>
 </bean>
 </beans>
 
 | 
注意
- 注解中,引入的包的对象,需要在xml中设置(当然,也可以用更高级的注解来实现)
- 注解中,用到的内容,必须出现在spring容器中。不管你是通过注解,还是xml来让他保存到spring容器
5.3 新注解
基于完全注解
我们目前所用的注解,都是使用在我们自己写的类上。引入的第三方的代码,需要用xml配置。
然而,我们可以通过spring的高级注解来摆脱xml
- @Configuration:放到类名之上- 作用:指定当前类是一个配置类
- 注意:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,可以不写。
 
- @ComponentScan:放到类名之上- 作用:用于通过注解指定spring在创建容器时,要扫描的包。相当于在xml中,配置扫描包。
- value属性:跟basePackages属性作用一样,用于指定创建容器时,要扫描的包。查看源码可知,跟basePackages属性作用一样。
 
- @Bean:放到方法之上- 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
- value属性:指定bean的id。不写时,默认值是当前方法的名称。查看源码可知,跟name属性作用一样
- 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象,查找的方式和Autowired一样的。如果有唯一的类型匹配时,就找到了,如果有多个类型匹配时,就找bean的id跟参数名称一样的
 
- @Scope:放到方法之上- 作用:设置当前方法返回对象的作用范围,单例or多例
- value属性:singleton或者prototype
 
- @Import:放到类名之上- 作用:用于导入其他的配置类。就相当于在JdbcConfig上添加@Configuration
- value属性:要引入的配置的字节码,比如@Import(JdbcConfig.class)
 
- @PropertySource:类名之上- 作用:用于指定properties文件的位置
- value属性:文件的名称和路径。- classpath:表示类路径下。也可以写上具体路径,比如classpath:config/jdbc.properties
- file:表示的是绝对路径
 
 
在上面的注解中,比如@ComponentScans或者@PropertySources这种,本质就是包含了多个@ComponentScan或者@PropertySource
通过上面的注解,我们就可以彻底摆脱xml。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 
 | <?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
 https://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/context
 https://www.springframework.org/schema/context/spring-context.xsd">
 <context:component-scan base-package="top.meethigher.demo08"></context:component-scan>
 
 
 <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
 <constructor-arg name="ds" ref="ds"></constructor-arg>
 </bean>
 
 <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
 <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
 <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false"></property>
 <property name="user" value="root"></property>
 <property name="password" value="root"></property>
 </bean>
 </beans>
 
 | 
我们创建一个SpringConfiguration的配置类,对比着上面的xml,来配置我们的注解类。
| 12
 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
 
 | @Configuration@ComponentScan("top.meethigher.demo09")
 public class SpringConfiguration {
 
 
 
 
 
 
 @Bean("runner")
 @Scope("prototype")
 public QueryRunner createQueryRunner(DataSource ds) {
 return new QueryRunner(ds);
 }
 
 @Bean("ds")
 public DataSource createDataSource() {
 ComboPooledDataSource ds = null;
 try {
 ds = new ComboPooledDataSource();
 ds.setDriverClass("com.mysql.cj.jdbc.Driver");
 ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false");
 ds.setUser("root");
 ds.setPassword("root");
 } catch (PropertyVetoException e) {
 throw new RuntimeException(e);
 }
 return ds;
 }
 }
 
 | 
测试类
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | public class AnnotationClientTest09 {private AnnotationConfigApplicationContext  aac;
 private AccountService accountService;
 
 @Before
 public void setUp() throws Exception {
 aac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
 accountService = aac.getBean("accountService", AccountService.class);
 }
 
 @After
 public void tearDown() throws Exception {
 aac.close();
 }
 
 @Test
 public void testFindAll() {
 List<Account> all = accountService.findAll();
 System.out.println(all);
 }
 }
 
 | 
配置细节
当配置类作为AnnotationConfigApplicationContext对象创建的参数时,可以不写@Configuration。
如果我们将SpringConfiguration中的配置,单独提取出来,作为JdbcConfig时(下面用sc和jc来代替),有三种方式
- 父子关系:如果jc作为sc的下一级时,sc需要扫描jc的所在包,并且,jc必须要加@Configuration 
- 兄弟关系:如果jc跟sc同级时,都可以不用加@Configuration,甚至不用扫描这两个类所在的包 
- 父子关系:通过在sc加入@Import,也可以实现,跟第一种本质上是一样的 
如果这块不理解的话,可以参考下面分别对应的三种方式。
第一种方式:父子关系
JdbcConfig
| 12
 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
 
 | @Configurationpublic class JdbcConfig {
 
 
 
 
 
 
 @Bean("runner")
 @Scope("prototype")
 public QueryRunner createQueryRunner(DataSource ds) {
 return new QueryRunner(ds);
 }
 
 @Bean("ds")
 public DataSource createDataSource() {
 ComboPooledDataSource ds = null;
 try {
 ds = new ComboPooledDataSource();
 ds.setDriverClass("com.mysql.cj.jdbc.Driver");
 ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false");
 ds.setUser("root");
 ds.setPassword("root");
 } catch (PropertyVetoException e) {
 throw new RuntimeException(e);
 }
 return ds;
 }
 }
 
 | 
SpringConfiguration
| 12
 3
 4
 5
 
 | @ComponentScan("top.meethigher.demo09")
 public class SpringConfiguration {
 
 }
 
 | 
测试类与上同。
第二种方式:JdbcConfig与SpringConfiguration作为兄弟关系。
如果在获取配置对象时,将所有配置类的字节码传入,那么,都可以省略@Configuration注解,甚至可以不用扫描配置类所在包了。
| 1
 | AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class, JdbcConfig.class);
 | 
第三种方式:父子关系
SpringConfiguration
| 12
 3
 4
 5
 6
 
 | @Configuration@ComponentScan("top.meethigher.demo09")
 @Import(JdbcConfig.class)
 public class SpringConfiguration {
 
 }
 
 | 
JdbcConfig
提取数据到properties
首先创建properties
| 12
 3
 4
 
 | driver=com.mysql.cj.jdbc.Driverurl=jdbc:mysql://localhost:3306/spring?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
 u=root
 pw=root
 
 | 
注意,再spring配置中使用username,会默认用电脑的名称去登录,原因以及解决
在主配置类上,添加properties的类路径
| 12
 3
 4
 5
 
 | @Configuration@ComponentScan("top.meethigher.demo09")
 @Import(JdbcConfig.class)
 @PropertySource("classpath:jdbc.properties")
 public class SpringConfiguration {}
 
 | 
在子配置类上,添加@Value,用的是el表达式,读取properties中的key
| 12
 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
 
 | public class JdbcConfig {@Value("${driver}")
 private String driver;
 @Value("${url}")
 private String url;
 @Value("${u}")
 private String username;
 @Value("${pw}")
 private String password;
 
 
 
 
 
 
 @Bean("runner")
 @Scope("prototype")
 public QueryRunner createQueryRunner(DataSource ds) {
 return new QueryRunner(ds);
 }
 
 @Bean("ds")
 public DataSource createDataSource() {
 ComboPooledDataSource ds = null;
 try {
 ds = new ComboPooledDataSource();
 ds.setDriverClass(driver);
 ds.setJdbcUrl(url);
 ds.setUser(username);
 ds.setPassword(password);
 } catch (PropertyVetoException e) {
 throw new RuntimeException(e);
 }
 return ds;
 }
 }
 
 | 
Qualifier解决方法重复
直接看代码吧
| 12
 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
 
 | public class JdbcConfig {@Value("${driver}")
 private String driver;
 @Value("${url}")
 private String url;
 @Value("${username}")
 private String username;
 @Value("${password}")
 private String password;
 
 
 
 
 
 
 
 @Bean("runner")
 @Scope("prototype")
 public QueryRunner createQueryRunner(@Qualifier("ds1") DataSource ds) {
 return new QueryRunner(ds);
 }
 
 @Bean("ds1")
 public DataSource createDataSource() {
 ComboPooledDataSource ds = null;
 System.out.println("1执行");
 try {
 ds = new ComboPooledDataSource();
 ds.setDriverClass(driver);
 ds.setJdbcUrl(url);
 ds.setUser(username);
 ds.setPassword(password);
 } catch (PropertyVetoException e) {
 throw new RuntimeException(e);
 }
 return ds;
 }
 
 @Bean("ds2")
 public DataSource createDataSource1() {
 ComboPooledDataSource ds = null;
 System.out.println("2执行");
 try {
 ds = new ComboPooledDataSource();
 ds.setDriverClass(driver);
 ds.setJdbcUrl(url);
 ds.setUser(username);
 ds.setPassword(password);
 } catch (PropertyVetoException e) {
 throw new RuntimeException(e);
 }
 return ds;
 }
 }
 
 | 
六、提取Spring静态工具类
阅读druid源码时,发现的一种写法
| 12
 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
 
 | 
 
 
 @Component
 public class SpringContextUtils implements ApplicationContextAware {
 private static ApplicationContext applicationContext;
 
 @Override
 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
 SpringContextUtils.applicationContext = applicationContext;
 }
 
 public static <T> T getBean(Class<T> clz) throws BeansException {
 return (T) applicationContext.getBean(clz);
 }
 
 public static boolean containsBean(String name) {
 return applicationContext.containsBean(name);
 }
 
 public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
 return applicationContext.isSingleton(name);
 }
 
 public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
 return applicationContext.getType(name);
 }
 
 public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
 return applicationContext.getAliases(name);
 }
 
 public static ApplicationContext getApplicationContext() {
 return applicationContext;
 }
 }
 
 |