你对spring框架的理解
spring是一个轻量级的IOC和AOP框架,旨在提高开发人员的开发效率和对项目的维护,它的核心模块:
- spring CORE:核心类库,提供IOC服务
- spring AOP:提供AOP服务
- spring context:提供框架式的bean访问方式
- spring Dao:对jdbc进行了抽象封装,简化数据访问异常
- spring ORM:提供对持久层框架支持
- spring web:提供面向web的综合特性
- spring mvc:提供面向web的model-view-controller实现
spring的优点:
- DI机制将对象之间的依赖关系交给容器来管理,减少各个组件之间的耦合性;
- aop将通用的业务逻辑抽取出来提高代码的复用性;
- spring提供了对主流框架很好的支持
- spring属于低侵入式编程,对代码的污染极低;
谈谈你对IOC的理解
- ioc即inversion of control,将对象之间的依赖关系交给spring容器来管理,减少各个组件之间的耦合性,DI和IOC是同一个概念只是不同的视角;
- 以前创建对象的时机和动作都是自己把握(自己根据需要new一个对象),现在是交给spring来管理根据xml文件在运行时动态的去创建对象,使用反射机制。
- ioc注入数据有三种方式:构造方法注入、set方法注入、注解注入
谈谈你对AOP的理解
aop即面向切面编程,在一个方法前面或者后面做一些额外的处理,比如:权限控制,日志记录等功能,将通用的业务逻辑抽取出来,提高代码的复用性。
aop中的术语:
- 连接点:类中可以被增强的方法
- 切入点:哪些连接点可以被拦截
- 增强:拦截之后要做的事情
- 切面:切入点与增强的结合
- 目标对象:代理的连接点对象
- 织入:把增强应用到目标的过程
- 引介:在运行期间动态的添加一些方法和属性
aop的实现方式:
- JDK的动态代理:只能对接口的实现类生成代理对象,而不是针对类的,原理是通过在运行期间创建一个接口的实现类来完成代理对象
- cglib:主要对类生成代理对象,对是否实现接口无要求,原理是对指定的类
IOC的初始化过程
- Resource定位
- BeanDefinition加载
- BeanDefinition注册
ApplicationContext和BeanFactory的区别
applicationcontext和beanfactory是spring容器的两大核心接口,applicationcontext是beanfactory派生而来的,beanfactory包含了各种bean的定义,加载,实例化,依赖注入和生命周期管理。
ApplicationContext | BeanFactory |
---|---|
可以同时加载多个配置文件 | 加载一个配置文件 |
采用立即加载的方式,创建容器时,一口气创建完xml中的所有的bean,因此容器启动时我们就能发现spring配置是否存在问题 | 采用按需加载的方式,根据需要调用getBean()创建对象,因此不能早早发现spring容器配置是否有问题,直到第一次调用getBean()才发现依赖没有注入 |
适用于单例模式 | 适用于多例模式 |
因为提前预载入了很多bean实例,所以运行速度比较快,唯一的不足就是如果bean实例很多会很占用内存启动程序会很慢 | |
继承MessageResource,支持国际化 |
BeanFactory和FactoryBean的区别
BeanFactory | FactoryBean |
---|---|
是spring容器的核心接口,包含了各种bean的定义、加载、实例化、依赖注入、生命周期 | 用来产生其他bean实例 |
Spring中Bean的作用域有哪几种
- singleton:默认作用域,容器中只能有一个bean实例;
- prototype:每次创建都创建一个bean实例,容器中可以有多个bean实例
- request:每个request请求都会创建一个bean实例,该作用域仅在web的spring applicationcontext情形下有效
- session:每个session会话都会创建一个bean实例,该作用域仅在web的spring applicaitoncontext情形下有效
- global-session:所有会话共享一个bena实例,仅在web的spring applicaitoncontext情形下有效
Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?
要分情况讨论:
- 作用域如果是prototype,每次都创建一个bean实例,容器中可以有多个bean实例,多个线程使用自己的bean实例不存在线程安全问题。
- 作用域如果是singleton,容器中只能有一个bean实例,多个线程共享一个bean实例就会存在线程安全问题。但是如果这个bean实例是无状态(只执行查询操作)的比如:controller、service、dao这种就是线程安全的;如果bean实例有状态的那就不是线程安全的如:model和view。
- 最简单的办法就是将singleton变成prototype;或者使用ThreadLocal
Spring如何解决循环依赖问题
循环依赖问题在Spring中主要有三种情况:
- 通过构造方法进行依赖注入时产生的循环依赖问题。
- 通过set方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
- 通过set方法进行依赖注入且是在单例模式下产生的循环依赖问题。
只有通过set()方法且单例模式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常因为:
- 通过构造方法注入依赖,new对象的时候就被堵塞住了;
- set()且多例模式下的注入依赖,每一次getBean()都会都会产生一个bean实例,这样就会有越来越多的bean实例会导致内存出现问题。
set()且单例模式下的注入依赖,是通过spring的二级缓存和三级缓存解决的(详细原理)
基于注解的自动装配方式
使用@autowired、@resource注解来装配bena实例,在使用@autowired注解之前要在xml中开启注解功能<context:-annotation-config/>
,且装配的bean要被注入spring容器中,使用@controller、@service、@repository、@component将bean注入容器中。
- 如果查询结果刚好为一个,就按照@autowired类型注入到bea中
- 如果查询的结果不止一个,就按照@autowired根据名称来查找;
- 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。
@Autowired和@Resource之间的区别:
- @Resource一般用在成员变量上,默认是按照bean的id名称来装配注入的。
- @Autowired一般用在成员变量上,默认是按照类型装配注入的
- 如果注入类型在ioc中有一个类型,直接按照类型注入
- 如果注入类型在ioc中有多个类型,按照变量名称去查找bean.id,然后注入
- 如果注入类型在ioc中找不到,报异常可以使用requeied=false解决
Spring事务的实现方式和实现原理
spring事务的本质是数据库提供对事务的支持,spring自己并不提供事务功能,它只提供对事务管理的统一接口,由各数据库自己实现事务。
Spring事务的种类:
- 编程式事务:在业务逻辑代码中嵌入事务声明的逻辑代码,通过TransanctionTemplate实现。
- 声明式事务:在AOP的基础上对方法进行拦截,即在方法之前开启事务,在方法执行结束后提交事务或者回滚事务。
声明式事务的优点:不需要在业务逻辑代码中切入事务逻辑代码,通过在xml中声明事务规则或者通过@transanctional对事务进行声明,减少代码的污染符合spring的低侵入式编程,唯一的不足就是只能作用到方法级别,无法像编程式事务那样作用到代码块级别。
事务的传播行为
事务 | 说明 |
---|---|
PROPAGATION_REQUIRED | 默认传播行为,如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。 |
PROPAGATION_REQUIRES_NEW | 无论当前存不存在事务,都创建新事务进行执行。 |
PROPAGATION_SUPPORTS | 如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行 |
PROPAGATION_MANDATORY | 如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
Spring 框架中都用到了哪些设计模式?
Spring设计模式的详细使用案例可以阅读这篇文章:https://blog.csdn.net/a745233700/article/details/112598471
- 工厂模式:Spring使用工厂模式,通过BeanFactory和ApplicationContext来创建对象
- 单例模式:Bean默认为单例模式
- 策略模式:例如Resource的实现类,针对不同的资源文件,实现了不同方式的资源获取策略
- 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术
- 模板方法:可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。比如RestTemplate,
JmsTemplate, JpaTemplate - 适配器模式:Spring AOP的增强或通知(Advice)使用到了适配器模式,Spring
MVC中也是用到了适配器模式适配Controller - 观察者模式:Spring事件驱动模型就是观察者模式的一个经典应用。
- 桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库
ApplicationContext的三个常用实现类
- ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
如下图:
- FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
- AnnotationConfigApplicationContext:它是用于读取注解创建容器
spring中的配置文件命名空间和约束
bean名称空间和约束的配置文件bean.xml
基于xml的配置文件前缀:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
bean和context名称空间和约束的配置文件bean.xml
基于注解的配置文件前缀:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
bean和aop名称空间和约束的配置文件bean.xml
基于xml配置文件前缀:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
bean、context和aop名称空间和约束的配置文件bean.xml
基于注解的配置文件前缀:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
</beans>
把创建的对象注入spring容器中范例
项目结构:
配置文件:bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把对象的创建交给spring来管理 -->
<bean id="accountService" class="com.hunan.tabu.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.hunan.tabu.dao.impl.AccountDaoImpl"></bean>
</beans>
client.java:
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
System.out.println(as);
System.out.println(adao);
}
运行结果:
核心容器两个接口ApplicationContext和BeanFactory的区别(重点)
如下代码:bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--把对象的创建交给spring来管理-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>
</beans>
AccountServiceImpl .java:
public class AccountServiceImpl implements IAccountService {
public AccountServiceImpl(){
System.out.println("对象创建了");
}
}
- ApplicationContext:(更常用)
1.可以同时加载多个配置文件
2.一读取完配置文件,创建容器时,就会一口气创建配置文件中所有的对象,采用立即加载
方式,有利于快速检查spring中是否存在配置错误。
3.适用于单例对象
优点:一开始就可以快速发现spring配置是否存在问题
缺点:一口气创建所有对象,占用内存较大
client.java:
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountDao adao = ac.getBean("accountDao",IAccountDao.class)
在运行完第2行之后,即读取完配置文件后,马上就创建配置文件中AccountServiceImpl类的对象。
运行结果:
- BeanFactory:
1.创建对象时采用的是延迟加载
策略,什么时候需要对象的时候,才调用getBean()方法,才开始创建对象,不利于一开始发现spring是否存在配置错误
2.适用于多例对象
优点:应用启动时占用资源较少
缺点:无法在容器创建时就发现spring中是否存在配置问题,也有可能会出现空指针异常。
Client.java:
//--------BeanFactory----------
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService as = (IAccountService)factory.getBean("accountService");
System.out.println(as);
在运行完第4行后,即根据accountService获取对象后,开始创建配置文件中的对象
运行结果:
spring对bean的管理细节
创建bean的三种方式
- 方式一:使用默认构造函数创建对象
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象。此时如果类中没有默认构造函数,则对象无法创建。
配置文件bean.xml:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
AccountServiceImpl.java:
public class AccountServiceImpl implements IAccountService {
//默认构造函数
public AccountServiceImpl(){
System.out.println("对象创建了");
}
}
运行结果:
但是如下AccountServiceImpl.java无默认构造函数,无法创建bean对象:
public class AccountServiceImpl implements IAccountService {
//因为创建了带参构造函数,所以系统不会再创建默认构造函数,此时类中没有默认构造函数
public AccountServiceImpl(String name){
System.out.println("对象创建了");
}
}
- 方式二:使用工厂中的普通方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
InstanceFactory .java:
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
bean.xml:
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
运行结果:
3. 方式三:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
StaticFactory .java:
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
bean.xml:
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
运行结果:
bean.xml:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
client.java:
public static void main(String[] args) {
//1.获取核心容器对象
// ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
IAccountService as2 = (IAccountService)ac.getBean("accountService");
System.out.println(as);
System.out.println(as2);
运行结果:
bean对象的生命周期
- 单例对象:
出生:当容器创建时对象出生
活着:容器还在,对象一直活着
死亡:容器销毁,对象死亡
AccountServiceImpl .java:
public class AccountServiceImpl implements IAccountService {
public AccountServiceImpl(){
System.out.println("对象创建了");
}
public void saveAccount(){
System.out.println("service中的saveAccount方法执行了。。。");
}
public void init(){
System.out.println("对象初始化了。。。");
}
public void destroy(){
System.out.println("对象销毁了。。。");
}
}
bean.xml:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="singleton" init-method="init" destroy-method="destroy"></bean>
client.java:
public static void main(String[] args) {
//1.获取核心容器对象
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
as.saveAccount();
//手动关闭容器
ac.close();
}
运行结果:
- 多例对象:
出生:当需要使用对象时,spring框架为我们创建
活着:当只要在使用时,对象就一直活着
死亡:当对象长时间不用时,且没有别的对象引用,垃圾回收器回收
bean.xml:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="prototype" init-method="init" destroy-method="destroy"></bean>
运行结果:
标签注入数据的三种方式
- 方式一:构造函数注入:
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
*******type:指定要注入的数据的数据类型,该数据类型也是构造函数中某些参数的类型
*******index:指定要注入的数据对应构造函数中指定索引位置的参数赋值。索引的位置是从0开始
*******name:指定构造函数中指定名称的参数赋值 (常用的)
以上三个都是用于指定给构造函数中哪个参数赋值=================
value:为基本类型和String类型的数据赋值
ref:指定其他的bean类型数据。它指的就是在spring Ioc核心容器中出现过的bean对象
AccountServiceImpl .java:
public class AccountServiceImpl implements IAccountService {
//如果是经常变化的数据,并不适用于注入的方式
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);
}
}
bean.xml:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="泰斯特"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
client.java:
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
as.saveAccount();
}
运行结果:
- 方式二:set方法注入(常用方式)
使用的标签:property
出现的位置:bean标签的内部
标签的属性:
****name: 所调用的set方法后面的名称
******value:给基本类型和String类型的数据赋值
******ref:指定其他的bean类型数据。它指的就是在spring Ioc核心容器中出现过的bean对象
AccountServiceImpl2 .java:
public class AccountServiceImpl2 implements IAccountService {
//如果是经常变化的数据,并不适用于注入的方式
private String name;
private Integer age;
private Date birthday;
public void setUserName(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);
}
}
bean.xml:
<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2">
<property name="userName" value="TEST" ></property>
<property name="age" value="21"></property>
<property name="birthday" ref="now"></property>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
运行结果:
- 方式三:给集合类型的注入
给List集合注入的标签:
list array set
给Map集合注入的标签:
map props
AccountServiceImpl3 .java:
public class AccountServiceImpl3 implements IAccountService {
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
public void saveAccount(){
System.out.println(Arrays.toString(myStrs));
System.out.println(myList);
System.out.println(mySet);
System.out.println(myMap);
System.out.println(myProps);
}
}
bean.xml:
<bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3">
<property name="myStrs">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myList">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="mySet">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="myMap">
<props>
<prop key="testC">ccc</prop>
<prop key="testD">ddd</prop>
</props>
</property>
<property name="myProps">
<map>
<entry key="testA" value="aaa"></entry>
</map>
</property>
</bean>
运行结果:
注解(重点)
用于创建对象的注解
- @Component
***作用地方:类上
***作用:把当前类对象存入spring容器中
***属性:
**********value:指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
bean.xml:
<!--创建容器时,扫描com.itheima下的包及子包中类和接口的注解-->
<context:component-scan base-package="com.itheima"></context:component-scan>
AccountServiceImpl .java:
@Component(value = "accountService")
public class AccountServiceImpl implements IAccountService {
public AccountServiceImpl() {
System.out.println("对象创建了");
}
}
client.java:
public static void main(String[] args) {
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountService");
System.out.println(as);
}
运行结果:
-
@Controller:一般用在表现层
-
@Service:一般用在业务层
-
@Repositrory:一般用在持久层
以上三个注解他们的作用和属性与@Component是一模一样。他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
注入对象的注解
-
@Autowired:
******作用:自动按照类型注入。只要容器中有bean对象类型和要注入的对象类型匹配,就可以注入成功。
***** 出现位置: 可以是变量上,也可以是方法上
******细节: 在使用注解注入时,set方法就不是必须的了。 -
Resource
******作用:直接按照bean的id注入。它可以独立使用
******属性:
***********name:用于指定bean的id。
spring的IOC容器注入数据原理
单个注入类型匹配:
多个注入类型匹配:
运行结果:
NoUniqueBeanDefinitionException: No qualifying bean of type 'com.itheima.dao.IAccountDao' available: expected single matching bean but found 2: accountDao1,accountDao2
多个注入类型匹配解决方案:
匹配原则:
当注入的数据类型和容器中多个bean对象类型匹配时,使用变量名和bean对象中id进行匹配。
AccountServiceImpl .java:
AccountDaoImpl .java:
@Repository("accountDao1")
public class AccountDaoImpl implements IAccountDao {
public void saveAccount(){
System.out.println("保存了账户1111111111111");
}
}
AccountDaoImpl2 .java:
@Repository("accountDao2")
public class AccountDaoImpl2 implements IAccountDao {
public void saveAccount(){
System.out.println("保存了账户2222222222222");
}
}
运行结果:
使用@Qualifier注解解决多个类型注入:
- @Qualifier:
***作用:给类成员注入时不能单独使用,要和@Autowired联合使用,但是给方法参数注入时可以。
*** 属性:
*********value:指定注入bean的id。
AccountServiceImpl .java:
@Component(value = "accountSer")
public class AccountServiceImpl implements IAccountService {
@Autowired
@Qualifier("accountDao1")
private IAccountDao accountDao;
public AccountServiceImpl() {
System.out.println("对象创建了");
}
public void saveAccount(){
accountDao.saveAccount();
}
}
AccountDaoImpl .java:
@Repository("accountDao1")
public class AccountDaoImpl implements IAccountDao {
public void saveAccount(){
System.out.println("保存了账户1111111111111");
}
}
- Resource
******作用:直接按照bean的id注入。它可以独立使用
******属性:
***********name:用于指定bean的id。
@Component(value = "accountSer")
public class AccountServiceImpl implements IAccountService {
@Resource(name = "accountDao2")
private IAccountDao accountDao;
public AccountServiceImpl() {
System.out.println("对象创建了");
}
public void saveAccount(){
accountDao.saveAccount();
}
}
AccountDaoImpl2.java:
@Repository("accountDao2")
public class AccountDaoImpl2 implements IAccountDao {
public void saveAccount(){
System.out.println("保存了账户2222222222222");
}
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
另外,集合类型的注入只能通过XML来实现。
- @Value
******作用:注入基本类型和String类型的数据
******属性:
*********value:指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
用于改变作用范围的
- @ Scope
*******作用:指定bean的作用范围
*******使用地方:类上/方法上
*******属性:
************value:范围的取值。常用取值:singleton—单例,prototype多例(默认是单例)
AccountServiceImpl .java:
@Scope("prototype")
@Component(value = "accountSer")
public class AccountServiceImpl implements IAccountService {
@Autowired
@Qualifier("accountDao1")
private IAccountDao accountDao;
public AccountServiceImpl() {
System.out.println("对象创建了");
}
public void saveAccount(){
accountDao.saveAccount();
}
}
AccountDaoImpl .java:
@Repository("accountDao1")
public class AccountDaoImpl implements IAccountDao{
public void saveAccount(){
System.out.println("保存了账户1111111111111");
}
}
client.java:
//1.获取核心容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取Bean对象
IAccountService as = (IAccountService)ac.getBean("accountSer");
IAccountService as2 = (IAccountService)ac.getBean("accountSer");
System.out.println(as == as2);
运行结果:
案例分析
基于xml的spring的IOC案例
AccountServiceImpl .java:
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
}
AccountDaoImpl .java:
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
}
bean.xml:
<!-- 配置Service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb3?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8"></property>
<property name="user" value="root"></property>
<property name="password" value="636895"></property>
</bean>
分析:创建业务层service对象,往其中注入持久层对象dao,创建持久层对象,往其中注入QueryRunner对象,创建QueryRunner对象,因为它是单例,防止多个dao进行访问,产生线程安全问题,设置成多例模式,往其中注入数据源对象dataSource,创建数据源对象,往其中注入数据库连接信息。
如下图:
基于注解的Spring的IOC案例
使用的注解(重点)
- @Configuration
********作用:指定当前类是一个配置类,相当于bean.xml
********细节:当该类的字节码文件,作为AnnotationConfigApplicationContext创建容器的参数时,该注解可以不写。因为它==使用了@Configuration
********属性:proxyBeanMethods,默认为true,保证依赖的组件始终是单实例的
有如下2种方式:
方式一:使用字节码文件的方式,SpringConfiguration作为主配置类,JdbcConfig作为次配置类,全部使用字节码文件作为参数,传入到AnnotationConfigApplicationContext创建对象中去,spring会自动识别他们是一个配置类。
SpringConfiguration.java:
//@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {}
JdbcConfig.java:
public class JdbcConfig {
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
@Bean(name="ds2")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
测试类:
ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class,JdbcConfig.class);
方式二:使用扫描包的方式,这时JdbcConfig使用@Configuration注解,标志位它是一个配置类,才会对该类下的注解进行扫描,否则,无法扫描该类下的注解。
SpringConfiguration.java:
//@Configuration
@ComponentScan({"com.itheima","config"})
public class SpringConfiguration {}
config包下
JdbcConfig.java:
@Configuration
public class JdbcConfig {}
测试类:
ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
- ComponentScan
********作用:指定spring在创建容器时要扫描的包
********属性:
*************value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。 我们使用此注解就等同于在xml中配置了:
<context:component-scan base-package=“com.itheima”></context:component-scan> - Bean
******作用:把当前方法的返回值作为bean对象,并存入spring的ioc容器中
******属性:
********** name:等于bean的id。当不写时,默认值是当前方法的名称
******细节:
***********当使用@Bean注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。查找的方式和Autowired注解的作用是一样的
没有注入DataSource对象到Spring的Ioc的容器中,所以报错:
多个类型注入时:
匹配原则:当注入的数据类型和容器中多个bean对象类型匹配时,使用变量名和bean对象中id进行匹配。
使用@Qualifier注解解决:
@Qulifier注解作为方法参数使用:
public class JdbcConfig {
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="ds2")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
@Bean(name="ds1")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
- @Import
******作用:导入其他配置类的字节码文件,将该类的实例注入springIOC核心容器中,且bean的id就是该类的全限定类名。当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
****** 属性:
************value:指定其他配置类的字节码文件。
既不想加入扫描包,也不想加入@Configuration注解,可以使用@Import注解:
SpringConfiguration.java:
//@Configuration
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
public class SpringConfiguration {}
config包下
JdbcConfig.java:
public class JdbcConfig {}
测试类:
ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);
-
@PropertySource
********属性:
**************value:指定properties配置文件的位置
************* 关键字:classpath,表示类路径下 -
@ImportResource
作用地方:作用在配置类上
作用:导入spring的配置文件(如:bean.xml)让springboot加载spring的xml配置文件。
属性:locations表示xml配置文件路径,关键字classpth表示在当前类路径下
@ImportResource(locations={"classpath:bean.xml"})
public class MyConfig{
}
完整案例:
AccountDaoImpl.java:
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
@Override
public List<Account> findAllAccount() {
try{
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountById(Integer accountId) {
try{
return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void saveAccount(Account account) {
try{
runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void updateAccount(Account account) {
try{
runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteAccount(Integer accountId) {
try{
runner.update("delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
AccountServiceImpl.java:
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
@Override
public void deleteAccount(Integer acccountId) {
accountDao.deleteAccount(acccountId);
}
}
jdbcConfig.properties:
#key可以随便设置,value有规定写法
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
SpringConfiguration.java:
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {}
JdbcConfig.java:
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="ds2")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
@Bean(name="ds1")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
测试类AccountServiceTest.java:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
@Test
public void testFindAll() {
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
@Test
public void testFindOne() {
//3.执行方法
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
Account account = new Account();
account.setName("test anno");
account.setMoney(12345f);
//3.执行方法
as.saveAccount(account);
}
@Test
public void testUpdate() {
//3.执行方法
Account account = as.findAccountById(4);
account.setMoney(23456f);
as.updateAccount(account);
}
@Test
public void testDelete() {
//3.执行方法
as.deleteAccount(4);
}
}
spring整合junit单元测试(重点)
1.导入spring整合junit的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2.使用@RunWith(SpringJUnit4ClassRunner.class)
***********作用:创建spring的IOC核心容器
3.使用@ContextConfiguration
***********作用:告知spring,spring的ioc核心容器创建的是基于xml配置文件还是注解类,并且说明位置
***********属性:
******************locations:指定xml配置文件的位置,加上classpath关键字,表示在类路径下
*******************classes:指定注解类所在的位置
4.细节:当我们使用spring 5.x版本的时候,要求junit的依赖必须是4.12及以上
AccountServiceTest.java:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
}
Spring中的AOP面向切面编程有了解吗?(重点)
aop指的是在一个方法之前或者之后做一些处理,包括:日志记录、权限控制等,可以在业务逻辑中将功能代码抽离出来。
aop中常用的术语
- 连接点(joinpoint):类中可以被增强的方法
- 切入点(pointcut):类中哪些方法可以被增强,然后就对它进行拦截
- 通知/增强:拦截所做的具体事情
- 切面:切入点和通知的结合
spring基于XML的AOP配置
步骤:
1.把通知Bean也交给spring来管理
2.使用aop:config标签开始AOP的配置
3.使用aop:aspect标签开始切面配置
*********id属性:给切面提供一个唯一标识
*********ref属性:指定通知类bean的Id。
4.在aop:aspect标签的内部使用对应标签来配置通知的类型
**我们现在示例是让printLog方法在切入点方法执行之前执行:所以是前置通知
*******aop:before:表示配置前置通知
*********************method属性:指定Logger类中哪个方法是前置通知
*********************pointcut属性:指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
*********关键字:execution(表达式)
*********表达式:访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
*********标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
完整示例:
AccountServiceImpl.java:
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
@Override
public void updateAccount(int i) {
System.out.println("执行了更新"+i);
}
@Override
public int deleteAccount() {
System.out.println("执行了删除");
return 0;
}
}
Logger.java:
/**
* 用于记录日志的工具类,它里面提供了公共的代码
*/
public class Logger {
/**
* 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
*/
public void printLog(){
System.out.println("Logger类中的pringLog方法开始记录日志了。。。");
}
}
bean.xml:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(public void com.itheima.service.impl.AccountServiceImpl.saveAccount())"></aop:before>
</aop:aspect>
</aop:config>
测试类:
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
}
运行结果:(在业务层之前执行了)
切入点表达式的多种写法:
- 访问修饰符可以省略
- 返回值可以使用通配符,表示任意返回值
- 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*。另外包名也可以使用
..
表示当前包及其子包 - 类名和方法名都可以使用*来代替
- 参数列表:
基本类型直接写名称 如: int
引用类型写包名.类名的方式 如:java.lang.String
可以使用..
表示有无参数均可
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法:
*
com.itheima.service.impl.*
.*
(..
)
对业务成所有方法进行了增强:
bean.xml:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
测试类:
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
}
运行结果:
四种通知类型
基于xml
AccountServiceImpl.java:
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("执行了保存");
// int i=1/0;
}
}
Logger.java:
public class Logger {
/**
* 前置通知
*/
public void beforePrintLog(){
System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
}
/**
* 后置通知
*/
public void afterReturningPrintLog(){
System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
}
/**
* 异常通知
*/
public void afterThrowingPrintLog(){
System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
}
/**
* 最终通知
*/
public void afterPrintLog(){
System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
}
}
bean.xml:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行
<aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))" ></aop:before>-->
<!-- 配置后置通知:在切入点方法正常执行之后值。后置通知和异常通知永远只能执行一个
<aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-returning>-->
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。后置通知和异常通知永远只能执行一个
<aop:after-throwing method="afterThrowingPrintLog" ppointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-throwing>-->
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
<aop:after method="afterPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after>-->
</aop:aspect>
</aop:config>
测试类:
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
}
运行结果:
xml的aop事务控制(案例)
AccountServiceImpl.java:
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
// int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
AccountDaoImpl.java:
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
private ConnectionUtils connectionUtils;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public List<Account> findAllAccount() {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from acount",new BeanListHandler<Account>(Account.class));
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountById(Integer accountId) {
try{
return runner.query(connectionUtils.getThreadConnection(),"select * from acount where id = ? ",new BeanHandler<Account>(Account.class),accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void saveAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"insert into acount(name,balance)values(?,?)",account.getName(),account.getMoney());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void updateAccount(Account account) {
try{
runner.update(connectionUtils.getThreadConnection(),"update acount set name=?,balance=? where id=?",account.getName(),account.getMoney(),account.getId());
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteAccount(Integer accountId) {
try{
runner.update(connectionUtils.getThreadConnection(),"delete from acount where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Account findAccountByName(String accountName) {
try{
List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from acount where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
if(accounts == null || accounts.size() == 0){
return null;
}
if(accounts.size() > 1){
throw new RuntimeException("结果集不唯一,数据有问题");
}
return accounts.get(0);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
TransactionManager.java:
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
ConnectionUtils.java:
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
bean.xml:
<!-- 配置Service -->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb3?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false"></property>
<property name="user" value="root"></property>
<property name="password" value="636895"></property>
</bean>
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置aop-->
<aop:config>
<!--配置通用切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<aop:aspect id="txAdvice" ref="txManager">
<!--配置前置通知:开启事务-->
<aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
<!--配置后置通知:提交事务-->
<aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
<!--配置异常通知:回滚事务-->
<aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
<!--配置最终通知:释放连接-->
<aop:after method="release" pointcut-ref="pt1"></aop:after>
</aop:aspect>
</aop:config>
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("张无忌","谢逊",100f);
}
}
运行结果:
注释 int i=1/0; 转账失败,事务进行回滚
放开注释,成功转账
基于xml配置环绕通知
AccountServiceImpl.java:
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("执行了保存");
// int i=1/0;
}
}
bean.xml:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!---配置通用切入点表达式->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置环绕通知 详细的注释请看Logger类中-->
<aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around>
</aop:aspect>
</aop:config>
Logger.java:
Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法,因此切入点方法和通知方法都会执行。
public class Logger {
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
测试类:
public class AOPTest {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
IAccountService as = (IAccountService)ac.getBean("accountService");
//3.执行方法
as.saveAccount();
}
运行结果:
基于注解配置环绕通知
注解(重点)
- @Aspect
作用:表示当前类是一个切面类
使用地方:当前类上 - @EnableAspectJAutoProxy
作用:开启spring对注解aop的支持 - @Pointcut
作用:联结切入点方法和通知方法
使用地方:方法上
关键字:execution(表达式) - @Before
作用:前置通知
使用地方:方法上 - @AfterReturning
作用:后置通知
使用地方:方法上 - @AfterThrowing
作用:异常通知
使用地方:方法上 - @After
作用:最终通知
使用地方:方法上 - @Around
作用:环绕通知
使用地方:方法上
AccountServiceImpl.java:
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
@Override
public void saveAccount() {
System.out.println("执行了保存");
// int i=1/0;
}
}
通知类Logger.java:
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}
@Around("pt1()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
}
配置类SpringConfig.java:
//@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}
测试类:
public static void main(String[] args) {
//1.获取容器
// ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
//2.获取对象
IAccountService as =ac.getBean("accountService",IAccountService.class);
System.out.println(as);
//3.执行方法
as.saveAccount();
}
运行结果:
Spring中的JdbcTemplate
JdbcTemplate的作用
spring提供和数据库进行交互,实现对表的CRUD操作
如何创建对象
常用方法
Spring的事务有了解吗?(重点)
spring的事务包括编程式事务控制和声明式事务控制两种。
- 编程式事务控制:通过TransactionTemplate实现。
- 声明式事务控制:建立在aop基础上,通过aop的功能,对目标方法进行拦截,将进行事务控制的代码插入到拦截方法中,也就是在目标方法前开启事务,在目标方法执行结束后提交事务或者回滚事务。
- 声明式事务控制的优点:不需要在业务逻辑代码中掺杂进行事务控制的代码,只需要通过配置文件或者
@Transactionl
注解的方式将事务控制加入到业务逻辑代码中。
两种事务如何选择?(重点)
声明式事务控制优于编程式事务控制,它符合spring倡导的非侵入式开发方式
,使业务逻辑代码不受污染,唯一不足就是声明式事务控制只能进行到方法级别
,编程式事务控制可以作用到代码块级别
。
基于xml的声明式事务控制
步骤:
- 配置事务管理器
- 配置事务通知
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用 - 配置AOP中的通用切入点表达式
- 建立事务通知和切入点表达式的对应关系
- 配置事务的属性
是在事务通知tx:advice标签的内部
使用tx:attributes标签,在其内部使用tx:method标签
属性:
name:对哪个方法进行
isolation:指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有给值,表示任何异常都回滚。
no-rollback-for:指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有给值,表示任何异常都回滚。
案例:
AccountServiceImpl.java:
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
bean.xml:
<!-- 1.配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2.配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 5.配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:attributes>
<!--*表示所有方法-->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<!--find*表示所有查询方法 看自己怎么写-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!--3. 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--4.建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
AccountDaoImpl.java:
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from acount where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = super.getJdbcTemplate().query("select * from acount where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update acount set name=?,balance=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("张无忌","谢逊",100f);
}
}
运行结果:
基于纯注解的声明式事务控制
- 配置事务管理器
- 开启spring对注解事务的支持
@EnableTransactionManagement - 在需要事务支持的地方使用@Transactional注解
AccountServiceImpl.java:
/**
*
* 事务控制应该都是在业务层
*/
@Service("accountService")
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)//只读型事务的配置,只能查询
public class AccountServiceImpl implements IAccountService{
@Autowired
private IAccountDao accountDao;
@Override
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
//上面进行只读型配置,但是这里需要的是读写型事务配置,所以继续进行配置
@Transactional(propagation= Propagation.REQUIRED,readOnly=false)
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
AccountDaoImpl.java:
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
@Override
public Account findAccountByName(String accountName) {
List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
if(accounts.isEmpty()){
return null;
}
if(accounts.size()>1){
throw new RuntimeException("结果集不唯一");
}
return accounts.get(0);
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
}
}
SpringConfiguration.java:
/**
* spring的配置类,相当于bean.xml
*/
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement //开启spring对注解事务的支持
public class SpringConfiguration {
}
JdbcConfig.java:
/**
* 和连接数据库相关的配置类
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate
* @param dataSource
* @return
*/
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
TransactionConfig.java:
/**
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
jdbcConfig.properties:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
运行结果: