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 { 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工厂
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; 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方法,都换成通过工厂来获取
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 { 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放到重写的方法中,才有了预期的结果。但我纳闷的是,为什么不能直接成员变量实例化呢?
下面是最后的解决方案,只需要修改此处即可。
1 2 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
即可。
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="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 {
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
- 它在创建核心容器时,创建对象采取的策略是,延迟加载。也就是说,什么时候要获取对象,什么时候才开始创建对象。
- 适用多例对象
对于这两个的测试,可以通过打断点来验证,具体步骤采用视频比较直观。
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 {
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
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> <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 {
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
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);
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"> <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> <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"> <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")
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
1 2 3 4 5 6 7 8
| @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(); } }
|
以上述代码为例,来了解一下@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) { 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
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"> <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单元测试
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); }
@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) { 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
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>
<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>
<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 {
@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 {
@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
| @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
提取数据到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;
@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;
@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
|
@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; } }
|