言成言成啊 | Kit Chen's Blog

Spring的IOC与DI

发布于2021-02-18 01:21:18,更新于2021-12-30 21:34:40,标签:spring  文章会持续修订,转载请注明来源地址:https://meethigher.top/blog

Spring官网

Spring学习资料

Spring学习目录

  1. Spring的IOC和DI
  2. Spring的AOP
  3. Spring的事务控制
  4. 源码

一、概念

Spring Framework 是一个开源的Java SE/EE 全栈(full-stack)的应用程序框架,以 IoC(Inverse of Control,控制反转)和 AOP(Aspect Oriented Programming,面向切面编程)为内核,还能整合开源世界的众多著名的第三方框架和类库。

Spring的源码是经典学习范例,对java的设计模式灵活运用,有能力可以研究。

作用

  1. 方便解耦,简化开发
  2. AOP编程支持
  3. 声明式事务的支持
  4. 方便程序的测试
  5. 方便集成各种优秀框架
  6. 降低JavaEE Api的使用难度

二、耦合

耦合是指程序间的依赖关系,包括类之间的依赖方法间的依赖

解耦就是降低程序间的依赖关系

实际开发中做到:编译期不依赖,运行时才依赖

下面举个例子

1
2
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 {
//1.注册驱动
//这种写法算是一种优化,依赖的只是一个字符串,找不到就是没有
//去掉依赖,编译仍然可以通过
Class.forName("com.mysql.cj.jdbc.Driver");
//这种写法存在耦合问题,依赖的是一个类文件,如果去掉依赖,编译不通过
// DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver());
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false", "root", "root");
//3.获取操作数据库的预处理对象
PreparedStatement ps = conn.prepareStatement("select * from account");
//4.执行sql,得到结果集
ResultSet rs = ps.executeQuery();
//5.封装结果集
while(rs.next()){
System.out.println(rs.getString("name"));
}
//6.释放资源
rs.close();
ps.close();
conn.close();
}
}

对于传统的开发工程,我们可以参照下面这种结构,ui调用service,service调用dao,其中如果没有dao的实现类,那么就不能new对象,整个工程就会报错,无法编译,这就是存在耦合问题。

2.1 工厂模式解耦

为了降低耦合,我们可以通过工厂模式,读取配置文件来创建对象,从而降低耦合度。

对于类之间依赖,解耦的思路

  1. 通过工厂模式,使用反射来创建对象,避免使用new关键字。
    • 不能像上面那样添加字符串,那就写死了,如果mysql换成oracle,岂不是又得改一遍?所以需要配合文件
  2. 通过读取配置文件来获取要创建的对象全限定类名

创建生产bean工厂

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
public class BeanFactory  {
private static Properties props;
//使用静态代码块,为Properties对象赋值
static{
try {
props=new Properties();
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(is);
} catch (Exception e) {
throw new ExceptionInInitializerError("读取配置文件失败!");
}
}

/**
* 创建Bean
* @param beanName
* @param <T>
* @return 返回值可以是泛型,也可以是Object对象,我这里用泛型,<T>表示声明
*/
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方法,都换成通过工厂来获取

1
2
3
//如下
AccountService as = (AccountService)BeanFactory.getBean("accountService");
AccountDao accountDao= (AccountDao) BeanFactory.getBean("accountDao");

通过这么处理,即使删除掉了AccountDaoImpl,那么也是可以编译的,不报错,但是运行时,会有找不到文件的异常弹出。

工厂模式中也存在问题,进行如下修改。

AccountDaoImpl.java

1
2
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

1
2
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获取,这样就到做到单例了。

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
public class BeanFactory {
//定义一个Properties对象
private static Properties props;

//定义一个Map,用于存放我们要创建的对象。我们把它称之为容器
private static Map<String,Object> beans;

//使用静态代码块为Properties对象赋值
static {
System.out.println("静态代码块执行..");
try {
props = new Properties();
InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(is);
//实例化容器
beans = new HashMap<String,Object>();
//取出配置文件中所有的Key
Enumeration<Object> keys = props.keys();
//遍历枚举
while (keys.hasMoreElements()){
String key = keys.nextElement().toString();
String beanPath = props.getProperty(key);
// Object value = Class.forName(beanPath).newInstance();
//jdk9以后的实例化推荐写法
Object value = Class.forName(beanPath).getDeclaredConstructor().newInstance();
beans.put(key,value);
}
}catch(Exception e){
throw new ExceptionInInitializerError("初始化properties失败!");
}
System.out.println("静态代码块执行完毕!");
}

/**
* 根据bean的名称获取对象
* @param beanName
* @return
*/
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放到重写的方法中,才有了预期的结果。但我纳闷的是,为什么不能直接成员变量实例化呢?

下面是最后的解决方案,只需要修改此处即可。

1
2
3
4
5
6
7
8
9
10
11
12
public class AccountServiceImpl implements AccountService {
// private AccountDao accountDao=new AccountDaoImpl();
//直接在后面实例化,或者在默认构造方法中,进行实例化,会出现问题。具体原因为啥,咱不清楚。
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即可。

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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把对象的创建交给spring管理-->
<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:读取注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class SpringClient {
/**
* 获取Spring的ioc核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
// ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\Develop\\JavaDev\\Spring-notes\\src\\main\\resources\\spring.xml");
//2.根据id获取bean对象
AccountServiceImpl accountService = (AccountServiceImpl) ac.getBean("accountService");
AccountDaoImpl accountDao = (AccountDaoImpl) ac.getBean("accountDao");

System.out.println(accountService);
System.out.println(accountDao);
}
}

3.2 创建对象

创建对象时机

核心容器的两个接口引发的问题

  • ApplicationContext
    • 它在创建核心容器时,创建对象采取的策略是,立即加载。也就是说,只要一读取配置文件,马上创建配置文件中的对象。
    • 适用单例对象
  • BeanFactory
    • 它在创建核心容器时,创建对象采取的策略是,延迟加载。也就是说,什么时候要获取对象,什么时候才开始创建对象。
    • 适用多例对象

对于这两个的测试,可以通过打断点来验证,具体步骤采用视频比较直观。

1
2
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 {
/**
* 获取Spring的ioc核心容器,并根据id获取对象
* @param args
*/
public static void main(String[] args) {
//=====ApplicationContext
//1.获取核心容器对象
// ApplicationContext ac=new ClassPathXmlApplicationContext("spring.xml");
//// ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\Develop\\JavaDev\\Spring-notes\\src\\main\\resources\\spring.xml");
// //2.根据id获取bean对象
// AccountServiceImpl accountService = (AccountServiceImpl) ac.getBean("accountService");
//
//
// System.out.println(accountService);


//=====BeanFactory,延迟加载
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的管理细节

  1. 创建bean的三种方式
  2. bean对象的作用范围
  3. bean对象的生命周期

第一种方式:使用默认构造函数创建。

在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数,创建bean对象。此时,若没有默认构造函数(比如只有一个有参构造函数),则对象无法创建。

第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)

创建InstanceFactory

1
2
3
4
5
public class InstanceFactory {
public AccountService getAccountService(){
return new AccountServiceImpl();
}
}

我们想将InstanceFactory中创建的AccountService对象存入spring容器,可以使用第二种方式。

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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="instanceFactory" class="top.meethigher.demo04.factory.InstanceFactory"></bean>
<!--通过factory-bean找到工厂的bean,然后调用他的factory-method生成对象,存为accountService-->
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
</beans>

第三种方式:使用工厂中的静态方法创建对象(使用某个类的静态方法创建对象,并存入spring容器)

创建StaticFactory

1
2
3
4
5
public class StaticFactory {
public static AccountService getAccountService(){
return new AccountServiceImpl();
}
}

使用第三种方式获取accountService

1
2
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是不是单例对象呢,验证一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SpringClient04 {
/**
* 获取Spring的ioc核心容器,并根据id获取对象
* @param args
*/
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次全是AccountServiceImpl@14a2f921
}
}
}

通过输出结果,可知,默认构造函数只走了一次,后面可知,获取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

1
2
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("对象销毁");
}
}

测试类

1
2
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);

//手动关闭容器,如果此处使用ApplicationContext是没有close方法的,原因是父类无法调用子类特有的方法
ac.close();
}
}

spring.xml

1
2
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)
  • 复杂类型/集合类型

注入的方式

  • 构造函数
  • set方法
  • 注解

构造函数注入

若是经常变化的数据,并不适用于构造函数注入的方式。

标签constructor-arg,放置在bean标签的内部。

五个属性

  1. 位置
    • index:指定构造函数中该索引位置的参数。参数索引的位置从0开始
    • name:指定构造函数中该名称的参数。常用√
    • type:指定要注入的数据的数据类型,该数据类型也是构造函数中某个或者某些参数的类型
  2. 传值
    • value:传入的参数的值,只适用于基本类型和String类型的数据
    • ref: 指定其他的bean类型,它指的就是ioc核心容器中出现过得bean对象

AccountServiceImpl

1
2
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

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
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>

测试类

1
2
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

1
2
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

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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--set方法注入-->
<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结构(单列)注入的标签

  • list
  • array
  • set

用于Map结构(双列)注入的标签

  • map
  • props

总结:结构相同,标签可以互换

AccountServiceImpl2

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
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

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
<?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>
<!--set方法注入-->
<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即可。

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

<!--扫描top.meethigher.demo06下的所有注解-->
<context:component-scan base-package="top.meethigher.demo06"></context:component-scan>
</beans>

注意

注解中,如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略

比如@Component(value=”accountService”),可以省略写成@Component(“accountService”)

常用注解

常用的11个注解,分为四类

  • 创建对象

    • @Component,相当于xml的bean
      • 作用:用于把当前类,通过反射创建对象,存入spring容器中(容器是个key-value结构,对象作为存入容器,还需要一个key)
      • 位置:当前的类名上
      • value属性:相当于bean的id(容器key-value中的键),当我们不写时,他的默认值是当前首字母小写的类名
      • 下面的Controller、Service、Repository作用跟属性与Component一样,相当于Component的没有扩展的子类。但是,他们是spring为我们提供的明确三层使用的注解,使我们的三层对象更加清晰。
    • @Controller:一般用于表现层
    • @Service:一般用于业务层
    • @Repository:一般用于持久层
  • 注入数据:Autowired、Qualifier、Resource只能注入其他bean类型的数据,无法注入基本类型和String类型的数据。

    • @Autowired
      • 作用:自动按照类型注入,只要容器中有唯一的bean对象类型和要注入的变量类型匹配,就可以注入成功
      • 位置:变量上或者方法上
      • 注意:使用注解时,set方法就不是必需的了,可以理解成该注解本身已经通过set方法来赋值了。
      • 如果ioc容器中,没有任何bean的类型和要注入的变量类型匹配,则报错。
      • 如果ioc容器中,有多个类型匹配时,就选择id与变量名相同的bean,没有则报错。
    • @Qualifier
      • 作用:在按照类型注入的基础上,再按照名称注入。它在给类成员变量注入时,不能单独使用,但是在给方法参数注入时,可以单独使用。可以参照本章下面的代码,和CRUD案例中的代码
      • 位置:变量上或者方法上
      • value属性:指定注入bean的id
    • @Resource
      • 作用:直接按照bean的id注入,它可以独立使用。就相当于@Autowired和@Qualifier
      • 位置:变量上或者方法上
      • name属性:用于指定bean的id
    • @Value
      • 作用:用于注入基本类型和String类型的数据
      • 位置:变量上或者方法上
      • value属性:用于指定数据的值。他可以使用spring中的spel(也就是spring中的el表达式)
      • spel写法:${表达式}。其实jsp、mybatis、spring中el表达式格式都一样,就看它是写的位置。
  • 作用范围

    • @Scope

      • 作用:用于指定bean的作用范围

      • value属性:指定范围的取值,常用取值:singleton(单例,默认值)、prototype(多例)

  • 声明周期:这个只需要了解即可。

    • @PreDestroy
      • 作用:用于指定销毁方法
    • @PostConstruct
      • 作用:用于指定初始化方法

以上三个注解,Resource、PreDestroy、PostConstruct不是spring提供的,是java提供的,所以我们需要在pom中引入javax.annotation。

如果要注入复杂/集合类型,只能使用xml

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

使用注解

首先要扫描指定包下的所有注解,配置xml。跳到3.4

AccountServiceImpl

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
@Component(value = "accountService")
public class AccountServiceImpl implements AccountService {

@Autowired
@Qualifier("accountDao1")
// @Resource(name="accountDao1"),相当于以上两个,该注解由java提供,8以上需要自己引入
private AccountDao accountDao;
@Value("20")//spel表达式暂不演示
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

1
2
3
4
5
6
7
8
//该类用来测试@Autowired,类型匹配不唯一时,如何处理
@Component("accountDao1")
public class AccountDaoImpl1 implements AccountDao {
@Override
public void saveAccount() {
System.out.println("保存数据1");
}
}

AccountDaoImpl

1
2
3
4
5
6
7
@Component("accountDao")
public class AccountDaoImpl implements AccountDao {
@Override
public void saveAccount() {
System.out.println("保存数据");
}
}

测试类

1
2
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();//测试@PreDestroy注解
}
}

以上述代码为例,来了解一下@Autowired的内部原理。

首先,通过xml的配置,扫描包下的所有bean的注解,然后存入spring容器。

当我们给类中的变量加上@Autowired时,他就会根据当前的类型,去springioc容器中找该类型。

如果唯一,那就进行注入。

如果不唯一,那就需要根据当前的变量名称,来寻找springioc容器中的key,寻找到了,就进行注入,否则报错。

1
2
private AccountDao accountDao=null;
权限修饰符 数据类型 变量名 数据类型

由此为了方便使用,才有了@Qualifier,以及更方便地@Resource

五、CRUD案例

环境

  • spring
  • c3p0:以前没用过
  • dbutils:以前没用过

在创建一个三层架构的项目时,其实有更方便的创建步骤

  1. domain实体类
  2. service业务层:创建接口、创建一系列方法、创建实现类
  3. dao持久层:创建接口,将service接口中的方法复制到dao接口,创建实现类
  4. 表现层(此处可以通过单元测试来模拟表现层)

5.1 基于XML

Account

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
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

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
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) {
//因为sqlexception是编译时期异常,必须处理。把他作为运行时异常,就可以不用处理了。
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();
}
}


/**
* 重点:保存并返回id
* @param account
*/
@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();
}
}

/**
* 重点:聚合查询
* @return
*/
@Override
public int findCount() {
try {
// 法一
// return Integer.parseInt(runner.query("select count(*) count from account", new MapHandler()).get("count").toString());
// 法二
return Integer.parseInt(runner.query("select count(*) from account",new ScalarHandler<>(1)).toString());
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException();

}
}
}

spring.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
<?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">
<!--配置service-->
<bean id="accountService" class="top.meethigher.demo07.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置dao-->
<bean id="accountDao" class="top.meethigher.demo07.dao.impl.AccountDaoImpl">
<property name="runner" ref="runner"></property>
</bean>
<!--配置QueryRunner,避免多个用户使用同一个对象,我们应该将作用范围设置为多例-->
<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&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;useSSL=false"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>

junit单元测试

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
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);
}

/**
* 重点:保存,并且返回自增的id
*/
@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);
}
}

在该过程中,有几点注意事项

  1. dbutils的使用。通过dbutils实现查询后保存id返回、实现聚合查询
  2. 形参是bean对象时(相当于传地址),改变形参,实参也会改变
  3. 编译期异常必须处理,运行时异常可以不用处理
  4. sql语句, select last_insert_id()
  5. 连接池应该使用多例,避免多个用户使用同一个对象引发问题。

5.2 基于XML与注解

引用的外部的类,需要xml来配置,但是自己写的类,是可以通过注解来配置了。

AccountDaoImpl

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
@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) {
//因为sqlexception是编译时期异常,必须处理。把他作为运行时异常,就可以不用处理了。
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();
}
}


/**
* 重点:保存并返回id
* @param account
*/
@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();
}
}

/**
* 重点:聚合查询
* @return
*/
@Override
public int findCount() {
try {
// 法一
// return Integer.parseInt(runner.query("select count(*) count from account", new MapHandler()).get("count").toString());
// 法二
return Integer.parseInt(runner.query("select count(*) from account",new ScalarHandler<>(1)).toString());
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException();

}
}
}

AccountServiceImpl

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
@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

1
2
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>

<!--配置QueryRunner,避免多个用户使用同一个对象,我们应该将作用范围设置为多例-->
<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&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;useSSL=false"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>

注意

  1. 注解中,引入的包的对象,需要在xml中设置(当然,也可以用更高级的注解来实现)
  2. 注解中,用到的内容,必须出现在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。

1
2
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>

<!--配置QueryRunner,避免多个用户使用同一个对象,我们应该将作用范围设置为多例-->
<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&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;useSSL=false"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>

我们创建一个SpringConfiguration的配置类,对比着上面的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
29
30
@Configuration
@ComponentScan("top.meethigher.demo09")
public class SpringConfiguration {
/**
* 用于创建QueryRunner
*
* @param ds
* @return
*/
@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;
}
}

测试类

1
2
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来代替),有三种方式

  1. 父子关系:如果jc作为sc的下一级时,sc需要扫描jc的所在包,并且,jc必须要加@Configuration

  2. 兄弟关系:如果jc跟sc同级时,都可以不用加@Configuration,甚至不用扫描这两个类所在的包

  3. 父子关系:通过在sc加入@Import,也可以实现,跟第一种本质上是一样的

如果这块不理解的话,可以参考下面分别对应的三种方式。

第一种方式:父子关系

JdbcConfig

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
@Configuration
public class JdbcConfig {
/**
* 用于创建QueryRunner
*
* @param ds
* @return
*/
@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

1
2
3
4
5
//@Configuration 因为该类的字节码作为配置类的参数传入,所以可以省略,别的不能省略
@ComponentScan("top.meethigher.demo09")
public class SpringConfiguration {

}

测试类与上同。

第二种方式:JdbcConfig与SpringConfiguration作为兄弟关系。

如果在获取配置对象时,将所有配置类的字节码传入,那么,都可以省略@Configuration注解,甚至可以不用扫描配置类所在包了。

1
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class, JdbcConfig.class);

第三种方式:父子关系

SpringConfiguration

1
2
3
4
5
6
@Configuration
@ComponentScan("top.meethigher.demo09")
@Import(JdbcConfig.class)
public class SpringConfiguration {

}

JdbcConfig

1
// JdbcConfig去掉@Configuration之后的代码

提取数据到properties

首先创建properties

1
2
3
4
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/spring?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
u=root
pw=root

注意,再spring配置中使用username,会默认用电脑的名称去登录,原因以及解决

在主配置类上,添加properties的类路径

1
2
3
4
5
@Configuration
@ComponentScan("top.meethigher.demo09")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbc.properties")
public class SpringConfiguration {}

在子配置类上,添加@Value,用的是el表达式,读取properties中的key

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
public class JdbcConfig {
@Value("${driver}")
private String driver;
@Value("${url}")
private String url;
@Value("${u}")
private String username;
@Value("${pw}")
private String password;
/**
* 用于创建QueryRunner
*
* @param ds
* @return
*/
@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解决方法重复

直接看代码吧

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
public class JdbcConfig {
@Value("${driver}")
private String driver;
@Value("${url}")
private String url;
@Value("${username}")
private String username;
@Value("${password}")
private String password;

/**
* 用于创建QueryRunner
*
* @param ds
* @return
*/
@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源码时,发现的一种写法

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
/**
* @author linchtech
* @date 2020-09-22 11:18
**/
@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;
}
}
发布:2021-02-18 01:21:18
修改:2021-12-30 21:34:40
链接:https://meethigher.top/blog/2021/spring/
标签:spring 
付款码 打赏 分享
Shift+Ctrl+1 可控制工具栏