Spring框架
- Spring框架的概述以及spring中基于XML的IOC配置
- Spring中基于注解的IOC和ioc的案例
- Spring中的aop和基于XML以及注解的AOP配置
- Spring中的JdbcTemplate以及Spring事务控制
文章目录
Spring的概述
-
Spring是什么
- Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了表现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架
- Spring是一个轻量级,一站式,用于企业级应用开发的框架,spring框架是一个模块化的框架,你想用哪块就用哪块,不想用就不引用; Spring是一个非侵入式框架,指的就是不会因为引入spring框架就是改变或影响我们自己的程序代码
- 轻量级: 针对重量级而言,指的是代码体量小,使用简单方便
- 一站式: 企业级应用开发(Web项目开发)需要的所有技术框架,spring都会提供,类似于小米生态圈
- 官方文档: https://spring.io/projects/spring-framework
-
Spring的发展历程
- 1997 年,IBM 提出了 EJB 的思想
- 1998 年,SUN 制定开发标准规范 EJB1.0
- 1999 年,EJB1.1 发布
- 2001 年,EJB2.0 发布
- 2003 年,EJB2.1 发布
- 2006 年,EJB3.0 发布
- Rod Johnson(spring 之父)
- Expert One-to-One J2EE Design and Development(2002)
- 阐述了 J2EE 使用 EJB 开发设计的优点及解决方案
- Expert One-to-One J2EE Development without EJB(2004)
- 阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形)
-
spring在三层架构的地位:
- 表示层:SpringMVC/struts2
- 业务逻辑层:Spring framework
- 数据持久层:Mybatis/Hibernate/JdbcTemplate
-
spring的优点:
- 方便解耦,简化开发(高内聚,低耦合)
- 通过 S pring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用
- aop编程的支持(面向切面编程)
- 通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
- 声明式事务的支持
- 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理, 提高开发效率和质量。
- 方便程序的测试
- 可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可 做的事情。
- 方便集成各种优秀框架
- Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。
- 降低JavaEE API的使用难度
- Spring 对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的 使用难度大为降低。
- Java源码是经典学习范例
- Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以 及对 Java 技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。
- spring是工厂,负责创建对象以及对象关系的维护。
- spring支持对javaee api的简化
- spring支持对junit的整合
- 方便解耦,简化开发(高内聚,低耦合)
-
spring的体系结构
- Core Container: 核心容器(IOC)模块
- spring-jdbc: 持久层模块,用于操作数据库
- spring-test: spring提供的单元测试
- spring-aop: spring提供的sop面向切面编程模块
- springmvc: spring提供的web开发模块
-
spring的两大核心
- ioc:控制反转
- aop:面向切
程序的耦合及解耦
-
什么是程序的耦合
- 耦合性(Coupling),也叫耦合度,是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调 用模块的方式以及通过界面传送数据的多少。模块间的耦合度是指模块之间的依赖关系,包括控制关系、调用关 系、数据传递关系。模块间联系越多,其耦合性越强,同时表明其独立性越差( 降低耦合性,可以提高其独立 性)。耦合性存在于各个领域,而非软件设计中独有的,但是我们只讨论软件工程中的耦合。
- 在软件工程中,耦合指的就是就是对象之间的依赖性。对象之间的耦合越高,维护成本越高。因此对象的设计 应使类和构件之间的耦合最小。软件设计中通常用耦合度和内聚度作为衡量模块独立程度的标准。划分模块的一个准则就是高内聚低耦合。
- 它有如下分类:
- 内容耦合。当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另 一个模块时,这样的耦合被称为内容耦合。内容耦合是最高程度的耦合,应该避免使用之。
- 公共耦合。两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大 量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
- 外部耦合 。一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传 递该全局变量的信息,则称之为外部耦合。
- 控制耦合 。一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进 行适当的动作,这种耦合被称为控制耦合。
- 标记耦合 。若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间 存在一个标记耦合。
- 数据耦合。模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形 式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另 一些模块的输入数据。
- 非直接耦合 。两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。
- 总结:
- 耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须 存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。
-
内聚与耦合
- 内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。内聚是从 功能角度来度量模块内的联系,一个好的内聚模块应当恰好做一件事。它描述的是模块内的功能联系。耦合是软件 结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通 过接口的数据。
- 程序讲究的是低耦合,高内聚。就是同一个模块内的各个元素之间要高度紧密,但是各个模块之 间的相互依存度却要不那么紧密。
- 内聚和耦合是密切相关的,同其他模块存在高耦合的模块意味着低内聚,而高内聚的模块意味着该模块同其他 模块之间是低耦合。在进行软件设计时,应力争做到高内聚,低耦合。
-
程序的耦合
-
耦合: 程序间的依赖关系 * 包括: * 类之间的依赖 * 方法间的依赖 * 解耦: * 降低程序间的依赖关系 * 实际开发中: * 应该做到: 编译时不依赖, 运行时才依赖 * 解决的思路: * 第一步: 使用反射来创建对象, 而避免使用new关键字 * 第二步: 读取配置文件来获取要创建的对象全限定类名
-
-
解决程序耦合的思路
-
当是我们讲解 jdbc 时,是通过反射来注册驱动的,代码如下:
Class.forName("com.mysql.jdbc.Driver");//此处只是一个字符串
-
此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(运 行就不要想了,没有驱动不可能运行成功的)。
-
同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改 源码。
-
解决这个问题也很简单,使用配置文件配置
-
-
工厂模式解耦
-
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的 方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
-
那么,这个读取配置文件,创建和获取三层对象的类就是工厂。
-
示例
-
配置文件
accountService=com.danny.service.impl.AccountServiceImpl accountDao=com.danny.dao.impl.AccountDaoImpl
-
工厂类
/** * 一个创建Bean对象的工厂 * * Bean: 在计算机英语中, 有可重用组件的含义 * JavaBean: 用java语言编写的可重用组件 * JavaBean > 实体类 * 它就是创建我们的service和dao的对象 * * 第一个: 需要一个配置文件来配置我们的service和dao * 配置的内容: 唯一标识=全限定类名 (key=value) * 第二个: 通过读取配置文件中配置的内容,反射创建对象 * * 配置文件可以是xml也可以是properties */ public class BeanFactory { //定义一个Properties对象 private static Properties props; //定义一个Map,用于存放我们要创建的对象,我们把它称之为容器 private static Map<String,Object> beans; //使用静态代码块为Properties对象赋值 static{ try { //实例化对象 props = new Properties(); //获取properties文件的流对象 InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties"); props.load(in); //实例化容器 beans = new HashMap<String, Object>(); //取出配置文件中的所有key Enumeration keys = props.keys(); //遍历枚举 while (keys.hasMoreElements()){ //取出每个key String key = keys.nextElement().toString(); //根据key获取value String beanPath = props.getProperty(key); //反射创建对象 Object value = Class.forName(beanPath).newInstance(); //把key和value存入容器 beans.put(key,value); } } catch (Exception e) { throw new ExceptionInInitializerError("初始化properties失败!"); } } /** * 根据bean的名称获取bean对象,单例 * @param beanName * @return */ public static Object getBean(String beanName){ return beans.get(beanName); } /** * 根据bean的名称获取bean对象,多例 * @param beanName * @return */ public static Object getBeans(String beanName){ Object bean = null; try { String beanPath = props.getProperty(beanName); bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象 }catch (Exception e){ e.printStackTrace(); } return bean; } }
-
测试类
@Test public void test(){ IAccountService as = (IAccountService) BeanFactory.getBean("accountService"); System.out.println(as); as.saveAccount(); }
-
-
IOC概念和Spring中的IOC
- 控制反转-IoC
- 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
- 指的是把我们自己曾经创建对象的权力反转给spring容器帮我们创建
- 明确 ioc 的作用:削减计算机程序的耦合(解除我们代码中的依赖关系)。
Spring 基于 XML 的 IOC 细节
SpringIoC模块的API
-
在springIoC中提供了一个核心工厂接口BeanFactory,用于项目所有全局类型实例的创建,spring专门提供了子接口APPlicationContext用于客户创建实例使用
-
ApplicationContext的三个常用实现类:
- ClassPathXmlApplicationContext
- 它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话,加载不了
- FileSystemXmlApplicationContext
- 它可以加载磁盘任意路径下的配置文件(必须有访问权限)
- AnnotationConfigApplicationContext
- 它是用于读取注解创建容器的
- ClassPathXmlApplicationContext
-
示例
-
@Test public void testApplicationContext(){ //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService) ac.getBean("accountService"); as.saveAccount(); }
-
Spring容器创建实例的过程
- 启动Tomcat时,加载spring配置文件,创建配置文件中所有类型的实例
- 初始化实例
- 哪里需要就从容器中获取对应的实例
- Tomcat正常关闭时,spring容器销毁,实例销毁
BeanFactory 和 ApplicationContext 的区别
- BeanFactory 才是 Spring 容器中的顶层接口。
- ApplicationContext是它的子接口
- ApplicationContext: (更多采用此接口)
- 单例对象使用
- 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式;也就是说,只要一读取完配置文件马上就创建配置文件中的配置对象
- BeanFactory:
- 多例对象使用
- 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式;也就是说,什么时候根据id获取对象,什么时候才真正的创建对象
IOC配置文件中bean标签和管理细节
bean标签
- 作用:
- 用于配置对象让 spring 来创建的。
- 默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
- 属性:
- id:给对象在容器中提供一个唯一标识,用于获取对象。同一个配置文件中,id值不能重名。
- name:该属性和id属性一样,用于对spring容器中的对象取别名,做唯一标识,同一个配置文件中不能重名。
- class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
- factory-bean:用于指定实例工厂 bean 的 id。
- factory-method:用于指定实例工厂中创建对象的方法。
- scope:指定对象的作用范围。
- singleton :默认值,单例的。
- prototype :多例的。
- request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中。
- session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中。
- global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么 globalSession 相当于 session。spring容器将创建的实例绑定到全局session中。
- init-method:指定类中的初始化方法名称。 (spring创建实例时就会调用)
- destroy-method:指定类中销毁方法名称。(spring实例销毁时就会调用)
bean的作用范围和生命周期
- 单例对象:
scope="singleton"
- 一个应用只有一个对象的实例。
- 作用范围:在整个应用(项目)中。
- 生命周期:
- 对象出生:当应用加载,创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象一直活着。 对象死亡:当
- 应用卸载,销毁容器时,对象就被销毁了。
- 多例对象:
scope="prototype"
- 每次访问对象时,都会重新创建对象实例。
- 作用范围:在自定义逻辑代码块中
- 生命周期:
- 对象出生:当使用对象时,创建新的对象实例。
- 对象活着:只要对象在使用中,就一直活着。
- 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
实例化Bean的三种方式
-
第一种方式: 使用默认构造方法创建
-
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时;
-
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建
-
<bean id="accountService" class="com.danny.service.impl.AccountServiceImpl"/>
-
-
第二种方式: 使用工厂普通方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
-
模拟一个工厂,创建业务层实现类; 此工厂创建对象,必须现有工厂实例对象,再调用方法
-
/** * 模拟一个工厂类(该类可能是存在jar包中的,我们无法通过修改源码的方式提供默认构造函数) */ public class InstanceFactory { public IAccountService getAccountService(){ return new AccountServiceImpl(); } }
-
xml文件中
-
<!-- 此种方式是: 先把工厂的创建交给 spring 来管理。 然后在使用工厂的 bean 来调用里面的方法 factory-bean 属性:用于指定实例工厂 bean 的 id。 factory-method 属性:用于指定实例工厂中创建对象的方法。 --> <bean id="instanceFactory" class="com.danny.factory.InstanceFactory"/> <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"/>
-
-
第三种方式: 使用工厂静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
-
模拟一个静态工厂,创建业务层实现类
-
/** * 模拟一个工厂类(该类可能是存在jar包中的,我们无法通过修改源码的方式提供默认构造函数) */ public class StaticInstanceFactory { public static IAccountService getAccountService(){ return new AccountServiceImpl(); } }
-
xml文件中
-
<!-- 此种方式是: 使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器 id 属性:指定 bean 的 id,用于从容器中获取 class 属性:指定静态工厂的全限定类名 factory-method 属性:指定生产对象的静态方法 --> <bean id="accountService" class="com.danny.factory.StaticInstanceFactory" factory-method="getAccountService"/>
-
-
测试示例
-
@Test public void testApplicationContext(){ //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService) ac.getBean("accountService"); as.saveAccount(); }
-
SpringDI依赖注入
依赖注入的概念
- 依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。
- 我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。 ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。
- 那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。
- 简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。
依赖注入的数据与方式
-
IoC的作用:
- 降低程序间的耦合(依赖关系)
-
依赖关系的管理:
- 以后交给spring来维护
-
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明
-
依赖关系的维护:
- 就称之为依赖注入
-
依赖注入:
- 能注入的数据: 有三类
- 基本类型和String
- 其他bean类型(在配置文件中或者注解配置过的bean)
- 复杂类型/集合类型
- 注入的方式: 有三种
- 使用构造方法注入
- 使用set方法注入
- 使用注解注入
- 能注入的数据: 有三类
构造标签注入
-
使用的标签:
<constructor-arg>
-
标签出现的位置:
<bean>
标签的内部 -
标签中的属性
- type: 用于指定要注入的数据类型, 该数据类型也是构造函数中某个或某些参数的类型(不能独立实现注入)
- index: 用于指定要注入的数据给构造函数中指定索引位置的参数赋值,索引的位置从0开始
- name: 用于给构造函数中指定名称的参数赋值 *常用的
以上三个用于指定给构造函数中哪个参数赋值
-
value: 用于提供基本类型和String类型的数据
-
ref: 用于指定其他的bean类型数据, 它指的就是spring的IoC核心容器中出现的bean对象
-
示例
-
<bean id="accountService" class="com.danny.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="test"/> <constructor-arg name="age" value="18"/> <constructor-arg name="birthday" ref="now"/> </bean>
-
-
优势:
- 在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
-
弊端:
- 改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供
set方法注入
-
#更常用
-
涉及的标签:
<property>
-
出现的位置:
<bean>
标签的内部 -
标签中的属性
-
name: 用于指定注入时所调用的set方法名称
-
value: 用于提供基本类型和String类型的数据
-
ref: 用于指定其他的bean类型数据, 它指的就是spring的IoC核心容器中出现的bean对象
-
示例
-
<bean id="accountService2" class="com.danny.service.impl.AccountServiceImpl2"> <property name="name" value="test"/> <property name="age" value="21"/> <!-- 方法一 bean --> <property name="birthday"><bean class="java.util.Date"></bean></property> <!-- 方法二 ref --> <property name="birthday2" ref="now"/> <property name="birthday3"><ref bean="now"/></property> </bean> <!-- 配置一个日期对象 --> <bean id="now" class="java.util.Date"></bean>
-
-
-
优势:
- 创建对象时没有明确的限制, 可以直接使用默认构造函数
-
弊端:
- 如果有某个必须有值, 则获取对象有可能set方法没有执行
复杂/集合类型的set注入
-
用于给List结构集合注入的标签:
- list
- array
- set
-
用于给Map结构集合注入的标签:
- map
- props
-
结构相同,标签可以互换
-
示例
-
<bean id="accountService3" class="com.danny.service.impl.AccountServiceImpl3"> <property name="myStr"> <array> <value>AAA</value> <value>BBB</value> </array> </property> <property name="myList"> <list> <value>AAA</value> <value>BBB</value> </list> </property> <property name="mySet"> <set> <value>AAA</value> <value>BBB</value> </set> </property> <property name="myMap"> <map> <entry key="AAA" value="aaa"/> <entry key="BBB" value="bbb"/> </map> </property> <property name="myProps"> <props> <prop key="CCC">ccc</prop> <prop key="DDD">ddd</prop> </props> </property> </bean>
-
Spring的各种整合
Spring整合Jdbc
-
spring-jdbc是由spring提供的一个持久层框架,用于和数据库做交互,spring-jdbc也提供了一个核心对象,叫做jdbcTemplate,它提供了一系列操作数据库的方法;
-
spring-jdbc的使用步骤
-
引入jar包
-
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.7.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.4</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.7.RELEASE</version> </dependency> </dependencies>
-
-
定义dao层接口操作数据库
-
/** * 账户的持久层方法 */ public interface IAccountDao { /** * 查询所有 * @return */ List<Account> findAllAccount(); /** * 查询一个 * @return */ Account findAccountById(Integer aId); /** * 保存 */ void saveAccount(Account account); /** * 更新 */ void updateAccount(Account account); /** * 删除 */ void deleteAccount(Integer aId); }
-
-
使用spring-jdbc实现dao层接口
-
/** * 账户的持久层实现类 * spring-jdbcTemplate */ public class AccountDaoImpl2 implements IAccountDao { private JdbcTemplate jdbcTemplate; public JdbcTemplate getJdbcTemplate() { return jdbcTemplate; } public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public List<Account> findAllAccount() { return jdbcTemplate.query("SELECT id,NAME,money FROM account",new BeanPropertyRowMapper<>(Account.class)); } @Override public Account findAccountById(Integer aId) { //return jdbcTemplate.query("SELECT id,NAME,money FROM account where id=?",new BeanPropertyRowMapper<>(Account.class),aId).get(0); return jdbcTemplate.queryForObject("SELECT id,NAME,money FROM account where id=?",(rs,rowNum)->{ Account account = new Account(); account.setId(rs.getInt("id")); account.setMoney(rs.getDouble("money")); account.setName(rs.getString("name")); return account; },aId); } @Override public void saveAccount(Account account) { jdbcTemplate.update("insert into account (NAME,money) values(?,?)",account.getName(),account.getMoney()); } @Override public void updateAccount(Account account) { jdbcTemplate.update("update account set name=?, money=? where id=?",account.getName(),account.getMoney(),account.getId()); } @Override public void deleteAccount(Integer aId) { jdbcTemplate.update("delete from account where id=?",aId); }
-
-
配置spring配置文件创建对象
-
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置Service对象 --> <bean id="accountService" class="com.danny.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"/> </bean> <!-- 配置dao对象 --> <bean id="accountDao" class="com.danny.dao.impl.AccountDaoImpl2"> <!-- 注入jdbcTemplate --> <property name="jdbcTemplate" ref="jdbcTemplate"/> </bean> <!-- 配置jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" scope="prototype"> <!-- 注入数据源 --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 连接数据库的必备信息 --> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db_spring?useUnicode=true&characterEncoding=UTF-8"/> <property name="user" value="root"/> <property name="password" value="root"/> </bean> </beans>
-
-
测试示例
-
public class AccountServiceTest_jdbcTemp { private ApplicationContext applicationContext; private IAccountService accountService; @Before public void before() { //1.获取容器 applicationContext = new ClassPathXmlApplicationContext("beanWithJdbcTemp.xml"); //2.得到业务层对象 accountService = applicationContext.getBean("accountService",IAccountService.class); } @Test public void testFindAll() { //3.执行方法 List<Account> accounts = accountService.findAllAccount(); for (Account account: accounts) { System.out.println(account); } } @Test public void testFindOne() { Account account = accountService.findAccountById(1); System.out.println(account); } @Test public void testSave() { Account account = new Account(); account.setName("danny"); account.setMoney(2000D); accountService.saveAccount(account); } @Test public void testUpdate() { Account account = new Account(); account.setName("danny"); account.setId(4); account.setMoney(2000D); accountService.updateAccount(account); } @Test public void testDelete() { accountService.deleteAccount(4); } }
-
-
Spring整合DbUtils
-
步骤
-
导入jar包
-
<dependency> <groupId>commons-dbutils</groupId> <artifactId>commons-dbutils</artifactId> <version>1.6</version> </dependency>
-
-
定义dao层接口
-
public interface IAccountDao { List<Account> findAllAccount(); Account findAccountById(Integer aId); void saveAccount(Account account); void updateAccount(Account account); void deleteAccount(Integer aId); }
-
-
使用dbUtils实现dao层接口
-
public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; public void setRunner(QueryRunner runner) { this.runner = runner; } @Override public List<Account> findAllAccount() { try { return runner.query("SELECT id,NAME,money FROM account",new BeanListHandler<>(Account.class)); } catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountById(Integer aId) { try { return runner.query("SELECT id,NAME,money FROM account where id=?",new BeanHandler<>(Account.class),aId); } 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 aId) { try { runner.update("delete from account where id=?",aId); } catch (Exception e) { throw new RuntimeException(e); } } }
-
-
配置spring配置文件创建对象
-
<!-- 配置Service对象 --> <bean id="accountService" class="com.danny.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"/> </bean> <!-- 配置dao对象 --> <bean id="accountDao" class="com.danny.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"/> </bean> <!-- 配置QueryRunner --> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!-- 注入数据源 --> <constructor-arg name="ds" ref="dataSource"/> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 连接数据库的必备信息 --> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db_spring?useUnicode=true&characterEncoding=UTF-8"/> <property name="user" value="root"/> <property name="password" value="root"/> </bean>
-
-
在Spring中引入配置文件
属性文件引入
-
<!--引入属性配置文件--> <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
xml配置文件引入
-
<!--引入其他spring配置文件--> <import resource="classpath:beans.xml"></import>
Spring整合各种连接池
c3p0连接池
-
<bean id="c3p0DataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!-- 连接数据库的必备信息 --> <property name="driverClass" value="${db.driver}"/> <property name="jdbcUrl" value="${db.url}"/> <property name="user" value="${db.username}"/> <property name="password" value="${db.password}"/> </bean>
整合druid连接池
-
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${db.driver}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.username}"/> <property name="password" value="${db.password}"/> </bean>
整合dbcp连接池
-
<bean id="dbcpDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${db.driver}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.username}"/> <property name="password" value="${db.password}"/> </bean>
整合spring-jdbc自带连接池
-
<bean id="driverManagerDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${db.driver}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.username}"/> <property name="password" value="${db.password}"/> </bean>
Spring整合junit
spring-test
-
spring-test单元测试依赖于junit单元测试,相对于junit单元测试而言,spring单元测试可以自动加载spring配置文件,并初始化springIOC容器;
-
spring-test的具体使用:
-
引入单元测试jar:
-
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.7.RELEASE</version> </dependency>
-
-
使用单元测试:
-
//该注解用于指定采用spring-test单元测试运行 @RunWith(SpringJUnit4ClassRunner.class) //该注解用于指定要加载的spring的配置文件 @ContextConfiguration(locations = {"classpath:beanWithJdbcTemp.xml"}) public class SpringAnnotation { @Autowired @Qualifier("accountService") private IAccountService accountService; @Test public void test1(){ Account account = accountService.findAccountById(1); System.out.println(account); } }
-
-
为什么不把测试类配到 xml 中
- 在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢?
- 答案是肯定的,没问题,可以使用。
- 那么为什么不采用配置到 xml 中的方式呢?
- 这个原因是这样的:
- 第一:当我们在 xml 中配置了一个 bean,spring 加载配置文件创建容器时,就会创建对象。
- 第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问 题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。
- 所以,基于以上两点,我们不应该把测试配置到 xml 文件中。
Spring整合mybatis
-
步骤
-
引入jar包
-
<!-- spring整合mybatis相关jar包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.7.RELEASE</version> </dependency> <!-- spring整合mybatis的jar包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.2</version> </dependency>
-
-
spring整合mybatis配置
-
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!--配置数据源--> <context:property-placeholder location="classpath:db.properties"/> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${db.driver}"/> <property name="url" value="${db.url}"/> <property name="username" value="${db.username}"/> <property name="password" value="${db.password}"/> </bean> <!--创建sqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 注入数据源 --> <property name="dataSource" ref="druidDataSource"/> <!-- 加载映射配置 --> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <!-- 加载主配置文件 --> <property name="configLocation" value="classpath:mybatisConfig.xml"/> <!-- 取别名 --> <property name="typeAliasesPackage" value="com.danny"/> </bean> <!-- 创建dao层代理类实例 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <property name="basePackage" value="com.danny"/> </bean> </beans>
-
-
使用mybatis基于接口代理方式操作数据库
-
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.danny.service.IAccountService"> <select id="findAllAccount" resultType="com.danny.domain.Account"> SELECT id,NAME,money FROM account </select> <select id="findAccountById" resultType="com.danny.domain.Account"> SELECT id,NAME,money FROM account where id=#{id} </select> </mapper>
-
-
测试类
-
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring-mybatis.xml"}) public class myBatisTest { @Autowired private IAccountService accountService; @Test public void test1() { Account account = accountService.findAccountById(1); System.out.println(account); } }
-
-
基于注解的IoC配置
-
xml配置文件
-
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 告知 spring 创建容器时要扫描的包 --> <context:component-scan base-package="com.danny"></context:component-scan> </beans>
-
用于创建对象
-
作用就和在xml配置文件中编写一个
<bean>
标签实现的功能一样<bean id="" class="">
-
@Component
- 作用:
- 把资源让 spring 来管理。相当于在 xml 中配置一个 bean。
- 属性:
- value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。
- 作用:
-
@Controller
&@Service
&@Repository
- 他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。 他们只不过是提供了更加明确的语义化。
@Controller
:一般用于表现层的注解。@Service
:一般用于业务层的注解。@Repository
:一般用于持久层的注解。
- 细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。
- 他们三个注解都是针对一个的衍生注解,他们的作用及属性都是一模一样的。 他们只不过是提供了更加明确的语义化。
用于注入数据
- 作用就和在xml配置文件中的
<bean>
标签中写一个<property>
标签作用是一样的<property name="" ref="">
<property name="" value="">
@Autowired
- 作用:
- 自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。
- 当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。
- 出现位置:
- 可以是成员变量上,也可以是方法上
- 作用:
@Qualifier
- 作用:
- 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。
- 它在给字段注入时不能独立使用,必须和
@Autowired
一起使用; - 但是给方法参数注入时,可以独立使用。
- 属性:
- value:指定 bean 的 id。
@Resource
- 作用:
- 直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。
- 属性:
- name:指定 bean 的 id。
- 作用:
- 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过xml来实现。
@Value
- 作用:
- 注入基本数据类型和 String 类型数据的
- 属性:
- value:用于指定数据的值。
- 它可以使用spring中SpEl(也就是spring的el表达式)
- SpEL的写法:
${表达式}
- SpEL的写法:
用于改变作用范围
- 作用就和在xml配置文件中的
<bean>
标签中使用scope
属性实现的功能是一样的<bean id="" class="" scope="">
@Scope
- 作用:
- 指定 bean 的作用范围。
- 属性:
- value:指定范围的值。
- 取值:
- singleton
- prototype
- request
- session
- globalsession
- 作用:
和生命周期相关
- 作用就和在xml配置文件中的
<bean>
标签中使用init-method
和destory-method
属性实现的功能是一样的<bean id="" class="" init-method="" destroy-method="" />
@PostConstruct
- 作用:
- 用于指定初始化方法。
- 作用:
@PreDestroy
- 作用:
- 用于指定销毁方法。
- 作用:
关于 Spring 注解和 XML 的选择问题
-
注解的优势:
- 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
-
XML 的优势:
- 修改时,不用改源码。不涉及重新编译和部署。
-
Spring 管理 Bean 方式的比较:
-
基于XML配置 基于注解配置 Bean定义 <bean id="..." class="..."/>
@Component
衍生类@Repository
@Service
@Controller
Bean名称 通过id或name @Component("")
Bean注入 <property>
或者通过p命名空间@Autowired
按类型注入@Qualifer
按名称注入生命周期/Bean作用范围 init-method
destory-method
范围scope
属性@PostConstruct
初始化@PreDestory
销毁@Scope
设置作用范围适合场景 Bean来自第三方,使用其他 Bean的实现类由用户自己开发 -
最常用方式:
- 建议别人写好的类采用xml方式配置,自己定义的类采用注解方式配置
新注解说明
@Configuration
- 作用:
- 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)。
- 属性:
- value:用于指定配置类的字节码
- 作用:
@ComponentScan
- 作用:
- 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:
<context:component-scan base-package="com.danny"/>
是一样的。 - 属性:
- basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。
- 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:
- 作用:
@Bean
- 作用:
- 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。
- 属性:
- name:给当前
@Bean
注解方法创建的对象指定一个名称(即 bean 的 id)。
- name:给当前
- 作用:
@PropertySource
- 作用:
- 用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。
- 属性:
- value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上
classpath:
- value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上
- 作用:
@Import
- 作用:
- 用于导入其他配置类,在引入其他配置类时,可以不用再写
@Configuration
注解。当然,写上也没问 题。
- 用于导入其他配置类,在引入其他配置类时,可以不用再写
- 属性:
- value[]:用于指定其他配置类的字节码。
- 作用:
纯注解使用
-
新建注解类
-
@Configuration @ComponentScan("com.danny") @PropertySource("classpath:db.properties") public class MyConfiguration { @Value("${db.driver}") private String driver; @Value("${db.url}") private String url; @Value("${db.username}") private String username; @Value("${db.password}") private String password; @Bean public DruidDataSource getDataSource(){ DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(driver); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); return druidDataSource; } @Bean public JdbcTemplate getJdbcTemplate(){ JdbcTemplate jdbcTemplate = new JdbcTemplate(); jdbcTemplate.setDataSource(getDataSource()); return jdbcTemplate; } }
-
-
测试类前加载注释类
-
@ContextConfiguration(classes = MyConfiguration.class)
-
动态代理
proxy代理
-
特点: 字节码文件随用随创建,随用随加载
-
作用: 不修改源码的基础上对方法增强
-
分类:
- 基于接口的动态代理
- 基于子类的动态代理
-
基于接口的动态代理:
- 涉及的类: Proxy
- 提供者: JDK官方
-
如何创建代理对象:
- 使用Proxy类中的newProxyInstance方法
- 该方法返回指定接口的代理类实例
- 使用Proxy类中的newProxyInstance方法
-
创建代理对象的要求:
- 被代理类最少实现一个接口,如果没有则不能使用
-
newProxyInstance方法的参数
- ClassLoader: 类加载器
- 它是用于加载代理对象字节码的,和被代理对象使用相同的类加载器,固定写法
- Class[]: 字节码数组
- 它是用于让代理对象和被代理对象有相同的方法,固定写法
- InvocationHandler: 用于提供增强的代码
- 它是让我们写如何代理,我们一般都实现一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
- 此接口的实现类都是谁用谁写
- invoke方法
- 作用:
- 执行被代理对象的任何接口方法都会经过该方法
- 参数:
- Proxy
- 代理对象的引用
- Method
- 当前执行的方法
- Object[]
- 当前执行方法所需的参数
- Proxy
- 作用:
- ClassLoader: 类加载器
-
示例:
-
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), Producer.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Double money = (Double)args[0]; //2.判断当前方法是不是销售 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer,money*0.8); } return returnValue; } });
-
cglib代理
-
特点: 字节码文件随用随创建,随用随加载
-
作用: 不修改源码的基础上对方法增强
-
分类:
- 基于接口的动态代理
- 基于子类的动态代理
-
基于接口的动态代理:
- 涉及的类: Enhancer
- 提供者: 第三方cglib库
-
如何创建代理对象:
- 使用Enhancer类中的create方法
- 该方法返回指定接口的代理类实例
- 使用Enhancer类中的create方法
-
创建代理对象的要求:
- 被代理类不能是最终类
-
create方法的参数
-
Class: 字节码
- 它是用于指定被代理对象的字节码
-
Callback: 用于提供增强的代码
- 它是让我们写如何代理,我们一般都实现一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
- 此接口的实现类都是谁用谁写
- 我们一般写的都是该接口的子接口实现类: MethodInterceptor
- intercept方法
- Object
- Method
- Object[]
- MethodProxy
-
-
示例:
-
Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { //提供增强的代码 Object returnValue = null; //1.获取方法执行的参数 Double money = (Double)args[0]; //2.判断当前方法是不是销售 if ("saleProduct".equals(method.getName())){ returnValue = method.invoke(producer,money*0.8); } return returnValue; } });
-
Spring的AOP简介
1.什么是AOP
- AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
- AOP 是 OOP面向对象编程思想的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- 简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。
2.AOP 的作用及其优势
-
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
-
优势:减少重复代码,提高开发效率,并且便于维护
3.AOP 的底层实现
- 实际上,AOP 的底层是通过 Spring 提供的的动态代理技术实现的。在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强
4.AOP 的动态代理技术
-
常用的动态代理技术
-
JDK 代理 : 基于接口的动态代理技术
-
cglib 代理:基于父类的动态代理技术
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-26wEbWks-1611919682506)(https://pcsdata.baidu.com/thumbnail/31daaebf5o1ffdb07aec396fc6302485?fid=2789047568-16051585-537728588177287&rt=pr&sign=FDTAER-yUdy3dSFZ0SVxtzShv1zcMqd-7n4WLxGWX5zX%2FhuLgH9be8a7XhM%3D&expires=2h&chkv=0&chkbd=0&chkpc=&dp-logid=1573217883&dp-callid=0&time=1611658800&size=c1600_u1600&quality=100&vuk=-&ft=video)]
5.AOP 相关概念
-
Spring 的 AOP 实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强。
-
在正式讲解 AOP 的操作之前,我们必须理解 AOP 的相关术语,常用的术语如下:
- Target(目标对象):
- 代理的目标对象
- Proxy (代理):
- 一个类被 AOP 织入增强后,就产生一个结果代理类
- Joinpoint(连接点):
- 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切入点):
- 所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义
- Advice(通知/ 增强):
- 所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知
- 通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
- Aspect(切面):
- 是切入点和通知(引介)的结合
- Introduction(引介):
- 引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
- Weaving(织入):
- 是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
- Target(目标对象):
6.AOP 开发明确的事项
-
需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
-
AOP 技术实现的内容
- Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
-
AOP 底层使用哪种代理方式
- 在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。
基于XML的AOP开发
XML相关标签
<aop:config>
:- 作用:
- 用于声明开始 aop 的配置
- 作用:
<aop:aspect>
:- 作用:
- 用于配置切面。
- 属性:
- id:给切面提供一个唯一标识。
- ref:引用配置好的通知类 bean 的 id。
- 作用:
<aop:pointcut>
:- 作用:
- 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
- 属性:
- expression:用于定义切入点表达式。
- id:用于给切入点表达式提供一个唯一标识
- 作用:
<aop:before>
- 作用:
- 用于配置前置通知。指定增强的方法在切入点方法之前执行
- 属性:
- method:用于指定通知类中的增强方法名称
- ponitcut-ref:用于指定切入点的表达式的引用
- poinitcut:用于指定切入点表达式
- 执行时间点:
- 切入点方法执行之前执行
- 作用:
<aop:after-returning>
- 作用:
- 用于配置后置通知
- 属性:
- method:指定通知中方法的名称。
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
- 执行时间点:
- 切入点方法正常执行之后。它和异常通知只能有一个执行
- 作用:
<aop:after-throwing>
- 作用:
- 用于配置异常通知
- 属性:
- method:指定通知中方法的名称。
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
- 执行时间点:
- 切入点方法执行产生异常后执行。它和后置通知只能执行一个
- 作用:
<aop:after>
- 作用:
- 用于配置最终通知
- 属性:
- method:指定通知中方法的名称。
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
- 执行时间点:
- 无论切入点方法执行时是否有异常,它都会在其后面执行。
- 作用:
execution
: 匹配方法的执行(常用)- 作用:
- 切入点表达式说明
- execution(表达式)
- 表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 注:
- 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
- 作用:
<aop:around>
:- 作用:
- 用于配置环绕通知
- 属性:
- method:指定通知中方法的名称。
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
- 说明:
- 它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
- 注意:
- 通常情况下,环绕通知都是独立使用的
- 作用:
快速入门
-
引入aop的相关jar包
-
<!--导入spring的context坐标,context依赖aop--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.7.RELEASE</version> </dependency> <!-- aspectj的织入 --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency>
-
-
定义通知类:
-
package com.danny.advice; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; public class MyAdviser { public void pointCut(){ } public void beforeAdvice(){ System.out.println("before"); } public void afterAdvice(){ System.out.println("after"); } public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("around-before"); //调用目标方法 joinPoint.proceed(); System.out.println("around-after"); } public void exceptionAdvice(){ System.out.println("after-throwing"); } public void returnAdvice(){ System.out.println("after-returning"); } }
-
-
配置xml文件做面向切面编程:
-
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" 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 https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 告知 spring 创建容器时要扫描的包 --> <context:component-scan base-package="com.danny"/> <!-- aop面向切面标签 --> <!-- 定义通知 --> <bean id="myAdviser" class="com.danny.advice.MyAdviser"/> <aop:config> <!-- 构建切面 --> <aop:aspect ref="myAdviser"> <!-- 定义切点 --> <!-- 定义表达式时,可使用`*`表示任意返回值类型,任意包,任意类; 可使用`..`表示任意方法入参 --> <aop:pointcut id="pt" expression="execution(void com.danny.service.impl.OrderServiceImpl.*(..))"/> <aop:before method="beforeAdvice" pointcut-ref="pt"/> <aop:after method="afterAdvice" pointcut-ref="pt"/> <aop:around method="aroundAdvice" pointcut-ref="pt"/> <aop:after-returning method="returnAdvice" pointcut-ref="pt"/> <aop:after-throwing method="exceptionAdvice" pointcut-ref="pt"/> </aop:aspect> </aop:config> </beans>
-
-
测试代码
-
//该注解用于指定采用spring-test单元测试运行 @RunWith(SpringJUnit4ClassRunner.class) //该注解用于指定要加载的spring的配置文件 @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class MyAdviserTest { @Autowired private IOrderService orderService; @Test public void testAOP() { orderService.confirm(); } }
-
基于注解的AOP开发
快速入门
基于注解的aop开发步骤:
-
定义通知类并基于注解做配置:
-
package com.danny.advice; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect //相当于<bean id="myAdviser" class="com.danny.advice.MyAdviser"/> @Component public class MyAdviser { @Pointcut("execution(void com.danny.service.impl.OrderServiceImpl.*(..))") //相当于<aop:pointcut id="pt" expression="execution(void com.danny.service.impl.OrderServiceImpl.*(..))"/> public void pointCut(){ } @Before("pointCut()") //相当于<aop:before method="beforeAdvice" pointcut-ref="pt"/> public void beforeAdvice(){ System.out.println("before"); } @After("pointCut()") //相当于<aop:after method="afterAdvice" pointcut-ref="pt"/> public void afterAdvice(){ System.out.println("after"); } @Around("pointCut()") //相当于<aop:around method="aroundAdvice" pointcut-ref="pt"/> public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("around-before"); //调用目标方法 joinPoint.proceed(); System.out.println("around-after"); } @AfterThrowing("pointCut()") //<aop:after-returning method="returnAdvice" pointcut-ref="pt"/> public void exceptionAdvice(){ System.out.println("after-throwing"); } @AfterReturning("pointCut()") //<aop:after-throwing method="exceptionAdvice" pointcut-ref="pt"/> public void returnAdvice(){ System.out.println("after-returning"); } }
-
-
在配置文件中添加注解驱动:
-
<!-- 告知 spring 创建容器时要扫描的包 --> <context:component-scan base-package="com.danny"/> <aop:aspectj-autoproxy/> <!-- 默认jdk自带的proxy动态代理 -->
-
-
测试代码
-
//该注解用于指定采用spring-test单元测试运行 @RunWith(SpringJUnit4ClassRunner.class) //该注解用于指定要加载的spring的配置文件 @ContextConfiguration(locations = {"classpath:applicationContext2.xml"}) public class MyAdviserTest2 { @Autowired private IOrderService orderService; @Test public void testAOP() { orderService.confirm(); } }
-
事务管理
事务的概念
- 将多个sql语句放到一起执行,要么同时执行成功,要么同时执行失败;
事务的四大特性
- 原子性:
- 将事务中的多个sql语句看作一个整体,要么同时执行,要么同时不执行;
- 一致性:
- 从业务角度描述,事务从一种正确的状态进入另一种正确的状态,数据始终保持一致;
- 隔离性:
- 指的是事务与事务直接的隔离特性,事务与事务直接的隔离特性分为以下几个事务隔离级别;
- 持久性:
- 可以将修改的数据持久化到数据库中;
事务隔离级别
事务隔离级别 | 脏读 | 不可重复读 | 幻读 | 数据库的默认级别 |
---|---|---|---|---|
未提交读(read-uncommitted) | 是 | 是 | 是 | |
已提交读(read-committed) | 否 | 是 | 是 | Oracle/SQL Server |
可重复读(repeatable-read) | 否 | 否 | 是 | MySQL |
可串行化/序列化(serializable) | 否 | 否 | 否 |
- 以上四种隔离级别按照从低到高的顺序排列为:Read uncommitted < Read committed < Repeatable read < Serializable
- Read uncommitted:
- 未提交读,就是一个事务可以读取另一个未提交事务的数据。
- 导致脏读;
- Read committed:
- 已提交读,就是一个事务要等另一个事务提交后才能读取数据。
- 导致多次读取相同数据时,数据可能不一致
- Repeatable read:
- 可重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。(多次读取相同数据时,数据一致)
- Serializable: 序列化
- Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
事务的传播特性
-
指的是在一个方法中调用另一个方法,那么另一个方法上的事务如何传播到这个方法中;
-
事务的传播特性是Spring的事务应用策略。
-
事务的七大传播特性
-
PROPAGATION_REQUIRED
- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。(默认值)
- 当A方法调用B方法时,如果在B方法上设置了该传播特性,那么不管A有事务还是没有事务,A,B都会属于同一事务中;
-
PROPAGATION_SUPPORTS
- 支持当前事务,如果当前没有事务,就以非事务方式执行。
- 当A方法调用B方法时,如果在B方法上设置了该传播特性,那么如果A方法有事务,A,B使用A方法的事务;如果A方法没有事务,那么A,B方法都不支持事务;
-
PROPAGATION_MANDATORY
- 支持当前事务,如果当前没有事务,就抛出异常。
- 当A方法调用B方法时,如果在B方法上设置了该传播特性,那么A方法在调用B方法时,必须有事务,否则直接抛异常;
-
PROPAGATION_NOT_SUPPORTED
- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- 当A方法调用B方法时,如果在B方法上设置了该传播特性,那么如果A方法有事务,当执行A方法时,以A方法的事务运行,当执行到B方法时,A方法事务挂起(失效),当B方法执行完之后,A方法事务恢复;如果A方法没有事务,则A,B方法都不使用事务;
-
PROPAGATION_NEVER
- 以非事务方式执行,如果当前存在事务,则抛出异常。
- 当A方法调用B方法时,如果在B方法上设置了该传播特性,那么A方法在调用B方法时必须没有事务,否则抛异常;
-
PROPAGATION_REQUIRES_NEW
- 新建事务,如果当前存在事务,把当前事务挂起。
- 当A方法调用B方法时,如果在B方法上设置了该传播特性;那么如果A方法有事务,当执行到A方法时,使用A方法的事务,当执行到B方法时,B方法会新建一个事务;会导致事务嵌套;如果A方法没有事务,当执行A方法时,以非事务方式运行,当执行到B方法时,B方法会新建一个事务,以事务方式运行,当B执行完毕,回到A方法,A方法继续以非事务方式运行;
-
PROPAGATION_NESTED
- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则新建一个事务。
- 当A方法调用B方法时,如果在B方法上设置了该传播特性,那么如果A方法有事务,会导致事务嵌套;如果A方法没有事务,B方法会创建一个事务,让A,B方法属于同一个事务中;
事务嵌套:
- 外部事务出现异常,会同时回滚外部事务和内部事务;而内部事务出现异常,只会回滚内部事务本身;
-
Spring声明式事务的使用
-
步骤
-
引入jar包
-
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.7.RELEASE</version> </dependency>
-
-
配置spring事务管理
-
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:cop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 告知 spring 创建容器时要扫描的包 --> <context:component-scan base-package="com.danny"/> <bean id="jdbcTemple" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="driverManagerDataSource"/> </bean> <!-- spring整合spring-jdbc自带连接池 --> <bean id="driverManagerDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/db_spring?useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="root"/> </bean> <!--创建spring事务管理对象--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="driverManagerDataSource"/> </bean> </beans>
-
-
配置事务
-
xml加注解方式
-
<!--开启事务注解驱动--> <tx:annotation-driven/>
-
@Override @Transactional(isolation = Isolation.DEFAULT,propagation = Propagation.REQUIRED) public void transfer(String sourceName, String targetName, double money) { //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); //2.6.更新转入账户 //int i =1/0; accountDao.updateAccount(target); System.out.println("转账成功"); }
-
-
纯xml方式
-
<!--xml配置事务--> <tx:advice id="transactionInterceptor" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!--构建事务切面--> <cop:config> <cop:pointcut id="tx" expression="execution(void com.danny.service.impl.AccountServiceImpl.transfer(..))"/> <cop:advisor advice-ref="transactionInterceptor" pointcut-ref="tx"/> </cop:config>
-
-
-
测试类
-
import com.danny.service.IAccountService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:applicationContext.xml"}) public class MyTest { @Autowired private IAccountService accountService; @Test public void test01() { accountService.transfer("aaa","bbb",-100d); } }
-
-