摘要
了解一下Spring框架,并且学习Spring的控制反转IOC(通过工厂来间接获取对象)、依赖注入DI(在IOC创建对象时,通过配置间接传入数据)
正文
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的使用难度 二、耦合 耦合是指程序间的依赖关系,包括类之间的依赖 、方法间的依赖
解耦就是降低程序间的依赖关系
实际开发中做到:编译期不依赖,运行时才依赖
下面举个例子
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 工厂模式解耦 为了降低耦合,我们可以通过工厂模式,读取配置文件来创建对象,从而降低耦合度。
展开
对于类之间依赖,解耦的思路
通过工厂模式,使用反射来创建对象,避免使用new关键字。不能像上面那样添加字符串,那就写死了,如果mysql换成oracle,岂不是又得改一遍?所以需要配合文件 通过读取配置文件来获取要创建的对象全限定类名 创建生产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的管理细节
创建bean的三种方式 bean对象的作用范围 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) 复杂类型/集合类型 注入的方式
构造函数注入 若是经常变化的数据,并不适用于构造函数注入的方式。
标签constructor-arg,放置在bean标签的内部。
五个属性
位置index:指定构造函数中该索引位置的参数。参数索引的位置从0开始 name:指定构造函数中该名称的参数。常用√ type:指定要注入的数据的数据类型,该数据类型也是构造函数中某个或者某些参数的类型 传值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结构(单列)注入的标签
用于Map结构(双列)注入的标签
总结:结构相同,标签可以互换
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个注解,分为四类
以上三个注解,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:以前没用过 在创建一个三层架构的项目时,其实有更方便的创建步骤
domain实体类 service业务层:创建接口、创建一系列方法、创建实现类 dao持久层:创建接口,将service接口中的方法复制到dao接口,创建实现类 表现层(此处可以通过单元测试来模拟表现层) 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&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&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 );
}
}
在该过程中,有几点注意事项
dbutils的使用 。通过dbutils实现查询后保存id返回、实现聚合查询形参是bean对象时(相当于传地址),改变形参,实参也会改变 编译期异常必须处理,运行时异常可以不用处理 sql语句, select last_insert_id() 连接池应该使用多例,避免多个用户使用同一个对象引发问题。 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&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。
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&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,来配置我们的注解类。
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来代替),有三种方式
父子关系:如果jc作为sc的下一级时,sc需要扫描jc的所在包,并且,jc必须要加@Configuration
兄弟关系:如果jc跟sc同级时,都可以不用加@Configuration,甚至不用扫描这两个类所在的包
父子关系:通过在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 ;
}
}