网站怎么收录,seo基础知识培训视频,建设网站广告语,编程的网站都有哪些Spring——尚硅谷学习笔记 1 Spring简介#x1f47e;1.1 Spring概述1.2 Spring Framework1.2.1 Spring Framework特性1.2.2 Spring Framework五大功能模块 2 IOC-IOC容器思想#x1f47e;IOC容器思想IOC在Spring中的实现 3 IOC-基于XML文件管理Bean#x1f47e;3.1 准备工作… Spring——尚硅谷学习笔记 1 Spring简介1.1 Spring概述1.2 Spring Framework1.2.1 Spring Framework特性1.2.2 Spring Framework五大功能模块 2 IOC-IOC容器思想IOC容器思想IOC在Spring中的实现 3 IOC-基于XML文件管理Bean3.1 准备工作3.2 获取bean3.2.1 方式一通过bean的id3.2.2 方式二通过类型类的Class对象3.2.3 方式三通过类型和id3.2.4 获取bean的方式总结3.2.5 对于接口3.2.6 根据类型获取bean的实质 3.3 依赖注入setter注入和构造器注入⭐️3.3.1 特殊值处理3.3.2 类类型的属性赋值3.3.3 数组3.3.4 list集合3.3.5 map集合3.3.6 p命名空间 3.4 管理数据源和外部文件加入依赖创建jdbc的配置文件配置DataSource 3.5 bean的作用域3.6 bean的生命周期3.7 工厂bean概念实现FactoryBean接口 3.8 自动装配⭐️概念实现 4 IOC-基于注解管理Bean4.1 使用注解注册bean组件4.2 扫描组件4.3 Autowired自动装配4.3.1 能够标识的位置4.3.2 注入的方式 4.4 例子 5 AOP-概念5.1 代理模式5.1.1 概念5.1.2 静态代理5.1.3 动态代理 5.2 AOP概念及相关术语AOP横切关注点通知切面目标代理连接点切入点总结 6 AOP-基于注解的AOP6.1 技术说明6.2 配置maven依赖配置文件 6.3 切面类重点 7 声明式事务4.1 JdbcTemplate4.1.1 配置4.1.2 使用(在Spring中进行测试) 4.2 声明式事务的概念4.2.1 编程式事务4.2.2 声明式事务 4.3 基于注解的声明式事务场景模拟加入事务 4.4 事务的属性1、只读2、超时3、回滚策略4、隔离级别5、事务的传播 仅自用如有侵权立刻删——感谢【尚硅谷】官方文档
1 Spring简介
1.1 Spring概述 Spring 是最受欢迎的企业级 Java 应用程序开发框架数以百万的来自世界各地的开发人员使用 Spring 框架来创建性能好、易于测试、可重用的代码。 Spring 框架是一个开源的 Java 平台它最初是由 Rod Johnson 编写的并且于 2003 年 6 月首次在 Apache 2.0 许可下发布。 Spring 是轻量级的框架其基础版本只有 2 MB 左右的大小。 Spring 框架的核心特性是可以用于开发任何 Java 应用程序但是在 Java EE 平台上构建 web 应用程序是需要扩展的。 Spring 框架的目标是使 J2EE 开发变得更容易使用通过启用基于 POJO编程模型来促进良好的编程实践。 1.2 Spring Framework
Spring 基础框架可以视为 Spring 基础设施基本上任何其他 Spring 项目都是以 Spring Framework为基础的。
1.2.1 Spring Framework特性
非侵入式使用 Spring Framework 开发应用程序时Spring 对应用程序本身的结构影响非常小。对领域模型可以做到零污染对功能性组件也只需要使用几个简单的注解进行标记完全不会破坏原有结构反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序时结构清晰、简洁优雅。控制反转IOC——Inversion of Control翻转资源获取方向。把自己创建资源、向环境索取资源变成环境将资源准备好我们享受资源注入。面向切面编程AOP——Aspect Oriented Programming在不修改源代码的基础上增强代码功能。容器Spring IOC 是一个容器因为它包含并且管理组件对象的生命周期。组件享受到了容器化的管理替程序员屏蔽了组件创建过程中的大量细节极大的降低了使用门槛大幅度提高了开发效率。组件化Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭建超大型复杂应用系统。声明式很多以前需要编写代码才能实现的功能现在只需要声明需求即可由框架代为实现。一站式在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且Spring 旗下的项目已经覆盖了广泛领域很多方面的功能性需求可以在 Spring Framework 的基础上全部使用 Spring 来实现。
1.2.2 Spring Framework五大功能模块
功能模块功能介绍Core Container 核心容器在 Spring 环境下使用任何功能都必须基于 IOC 容器。AOPAspects 面向切面编程Testing 提供了对 junit 或 TestNG 测试框架的整合。Data Access/Integration 提供了对数据访问/集成的功能。Spring MVC 提供了面向Web应用程序的集成功能。
2 IOC-IOC容器思想
先简单说一下IOC容器就是放置已经创建好的实例的一个地方。这样实例不需要程序员来手动创建而是交给容器管理更加高效。 下面这一部分更加详细阐述了如果要深入了解IOC容器需要阅读源码这个以后安排上。这个笔记主要用于学习如何使用框架 IOC容器思想
IOCInversion of Control翻译过来是反转控制。 ①获取资源的传统方式 自己做饭买菜、洗菜、择菜、改刀、炒菜全过程参与费时费力必须清楚了解资源创建整个过程中的全部细节且熟练掌握。 在应用程序中的组件需要获取资源时传统的方式是组件主动的从容器中获取所需要的资源在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式增加了学习成本同时降低了开发效率。
②反转控制方式获取资源 点外卖下单、等、吃省时省力不必关心资源创建过程的所有细节。 反转控制的思想完全颠覆了应用程序组件获取资源的传统方式反转了资源的获取方向——改由容器主动的将资源推送给需要的组件开发人员不需要知道容器是如何创建资源对象的只需要提供接收资源的方式即可极大的降低了学习成本提高了开发的效率。这种行为也称为查找的被动形式。 ③DI DIDependency Injection翻译过来是依赖注入。 DI 是 IOC 的另一种表述方式即组件以一些预先定义好的方式例如setter 方法接受来自于容器的资源注入。相对于IOC而言这种表述更直接。 所以结论是IOC 就是一种反转控制的思想 而 DI 是对 IOC 的一种具体实现。
IOC在Spring中的实现
Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建bean 之前首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式 ①BeanFactory 这是 IOC 容器的基本实现是 Spring 内部使用的接口。面向 Spring 本身不提供给开发人员使用。 ②ApplicationContext BeanFactory 的子接口提供了更多高级特性。面向 Spring 的使用者几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。 3 IOC-基于XML文件管理Bean
对于xml文件的方式也要学会掌握因为对于工程里面的一些jar包你是无法在上面给他加注释的只是使用xml文件方式。
3.1 准备工作
maven依赖
dependencies!-- 基于Maven依赖传递性导入spring-context依赖即可导入当前所需所有jar包 --dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion5.3.1/version/dependency!-- junit测试 --dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.12/versionscopetest/scope/dependency
/dependencies创建Spring的配置文件 3.2 获取bean
3.2.1 方式一通过bean的id
配置文件中 bean idstudentOne classcom.zylai.spring.pojo.Student/beanbean idstudentTwo classcom.zylai.spring.pojo.Student/bean测试
Test
public void testIOC(){ApplicationContext ioc new ClassPathXmlApplicationContext(spring-ioc.xml);//获取bean
// 1.通过bean的id获取Student studentOne (Student) ioc.getBean(studentOne);
}3.2.2 方式二通过类型类的Class对象
注意要求ioc容器中有且仅有一个与之匹配的bean 若没有任何一个类型匹配的bean抛出NoSuchBeanDefinitionException 若有多个类型匹配的bean抛出NoUniqueBeanDefinitionException
Test
public void testIOC(){ApplicationContext ioc new ClassPathXmlApplicationContext(spring-ioc.xml);//获取bean
// 2.通过类的Class对象获取Student studentOne ioc.getBean(Student.class);
}3.2.3 方式三通过类型和id
Test
public void testIOC(){ApplicationContext ioc new ClassPathXmlApplicationContext(spring-ioc.xml);//3.通过类型和id获取Student studentOne ioc.getBean(studentOne,Student.class);System.out.println(studentOne);}3.2.4 获取bean的方式总结
以后用的最多的就是第二种方式一个类型的bean只需要配置一次就可以了如果真的需要多个实例那么配置时加上scope属性选择多例就可以了
注意在IOC容器中通过工厂模式和反射技术创建对象所以需要对象的无参构造器
3.2.5 对于接口
xml文件中不能写接口很简单接口没有实例对象也没有无参构造器 如果组件类实现了接口根据接口类型可以获取 bean 吗
可以前提是bean唯一
如果一个接口有多个实现类这些实现类都配置了 bean根据接口类型可以获取 bean 吗
不行因为bean不唯一
下面这个会得到接口的实现类对象 Testpublic void testIOC(){ApplicationContext ioc new ClassPathXmlApplicationContext(spring-ioc.xml);//通过接口获取实例Person person ioc.getBean(Person.class);System.out.println(person);}3.2.6 根据类型获取bean的实质
根据类型来获取bean时在满足bean唯一性的前提下其实只是看
对象 instanceof 指定的类型 的返回结果只要返回的是true就可以认定为和类型匹配能够获取到。
即通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean
3.3 依赖注入
setter注入和构造器注入⭐️
分别是调用类的set方法和有参构造
!--主要属性和成员变量的区别--
bean idstudentTwo classcom.zylai.spring.pojo.Student!--property通过成员变量的set方法进行赋值name设置需要赋值的属性名和set方法有关set方法名去掉set之后首字母大写value设置属性的值--property namesid value1001/property namesname value张三/property nameage value23/property namegender value男/
/beanbean idstudentThree classcom.zylai.spring.pojo.Studentconstructor-arg value1002/constructor-arg value李四/constructor-arg value女/constructor-arg value23 nameage/
/bean3.3.1 特殊值处理
特殊字符和CDATA节的处理如下
!--特殊值处理--
bean idstudentFour classcom.zylai.spring.pojo.Studentproperty namesid value1003/!--对于特殊字符应该使用该特殊字符对应的实体 lt; gt;另外也可以使用CDATA节其中的内容会原样解析,CDATA节是xml中一个特殊的标签不能写在属性中只能通过一个标签写入--!--property namesname valuelt;王五gt;/--property namesnamevalue![CDATA[王五]]/value/propertyproperty namegender!--使性别这个属性为null--null//property
/bean3.3.2 类类型的属性赋值
三种方式
refref引用IOC容器中的某个bean的id
bean idstudentFive classcom.zylai.spring.pojo.Studentproperty namesid value1006/property namesname value张六/property nameage value23/property namegender value男/!--ref引用IOC容器中的某个bean的id--property nameclazz refclazzOne//bean级联级联的方式要求clazz属性赋值或者实例化。所以一般不用
bean idstudentFive classcom.zylai.spring.pojo.Studentproperty namesid value1006/property namesname value张六/property nameage value23/property namegender value男/!--级联的方式要求clazz属性赋值或者实例化。所以一般不用--property nameclazz.cid value1122/property nameclazz.cname value哈哈班/
/bean内部bean在属性中设置一个bean。 注意不能通过ioc容器获取相当于内部类只能在当前student内使用 bean idstudentFive classcom.zylai.spring.pojo.Studentproperty namesid value1006/property namesname value张六/property nameage value23/property namegender value男/!--内部bean在属性中设置一个bean。注意不能通过ioc容器获取相当于内部类只能在当前student内使用--property nameclazzbean idclazzInner classcom.zylai.spring.pojo.Clazzproperty namecid value1122/property namecname value王班//bean/property
/bean3.3.3 数组
在属性中使用array标签标签中数组元素的值写在value中
!--数组--
bean idstudentSix classcom.zylai.spring.pojo.Studentproperty namesid value1006/property namesname value张六/property nameage value23/property namegender value男/property nameclazzbean idclazzInner classcom.zylai.spring.pojo.Clazzproperty namecid value1122/property namecname value王班//bean/property!--数组属性--property namehobbiesarrayvalue唱/valuevalue跳/valuevaluerap/value/array/property/bean3.3.4 list集合
方式一在属性中使用list标签
!--list集合1.在属性中使用list标签2.再写一个集合类型的bean
--
bean idclazzTwo classcom.zylai.spring.pojo.Clazzproperty namecid value1111/property namecname value王者班/property namestudentListlistref beanstudentOne/ref beanstudentTwo/ref beanstudentThree//list/property
/bean方式二再写一个集合类型的bean
!--list集合1.在属性中使用list标签2.再写一个集合类型的bean
--
bean idclazzTwo classcom.zylai.spring.pojo.Clazzproperty namecid value1111/property namecname value王者班/property namestudentList refstudentList/
/bean!--配置一个集合类型的bean需要使用util的约束--
util:list idstudentListref beanstudentFour/ref beanstudentFive/ref beanstudentSix/
/util:list3.3.5 map集合
方式和list差不多
方式一设置map标签
bean idstudentSix classcom.zylai.spring.pojo.Studentproperty namesid value1006/property namesname value张六/property nameage value23/property namegender value男/property nameclazzbean idclazzInner classcom.zylai.spring.pojo.Clazzproperty namecid value1122/property namecname value王班//bean/propertyproperty namehobbiesarrayvalue唱/valuevalue跳/valuevaluerap/value/array/property!--map集合和list差不多就是需要设置键值对。第二种方式就是util了--property nameteacherMapmapentry key语文老师 value-refteacherOne/entry key数学老师 value-refteacherTwo//map/property
/bean方式二写一个集合类型的bean
bean idstudentSix classcom.zylai.spring.pojo.Student···略property nameteacherMap refteacherMap/
/bean
util:map idteacherMapentry key语文老师 value-refteacherOne/entry key数学老师 value-refteacherTwo/
/util:map3.3.6 p命名空间
!--p命名空间需要在xml文件最开始设置引入--
bean idstudentSeven classcom.zylai.spring.pojo.Studentp:sid1008 p:sname小红 p:clazz-refclazzOnep:teacherMap-refteacherMap/bean3.4 管理数据源和外部文件
加入依赖
!-- MySQL驱动 --
dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.16/version
/dependency
!-- 数据源 --
dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.0.31/version
/dependency创建jdbc的配置文件
jdbc.propertiesjdbc.properties
jdbc.drivercom.mysql.cj.jdbc.Driver
jdbc.urljdbc:mysql://localhost:3306/ssm?serverTimezoneUTC
jdbc.usernameroot
jdbc.passwordroot
initialSize5
maxActive9配置DataSource 注意需要通过context标签引入外部文件注意context标签在xml文件头对应的链接 ?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-4.2.xsd!--引入jdbc.properties之后可以通过${key}的方式访问value--!--这里的context标签注意上面引入的--context:property-placeholder locationjdbc.properties/context:property-placeholderbean iddatasource classcom.alibaba.druid.pool.DruidDataSourceproperty namedriverClassName value${jdbc.driver}/property nameurl value${jdbc.url}/property nameusername value${jdbc.username}/property namepassword value${jdbc.password}/!--property nameinitialSize value${initialSize}/--!--property namemaxActive value${maxActive}/--/bean
/beans3.5 bean的作用域
在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围各取值含义参加下表:
取值含义创建对象的时机singleton (默认)在IOC容器中这个bean的对象始终为单实例IOC容器初始化时prototype这个bean在IOC容器中有多个实例获取bean时
如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用) :
取值含义request在一个请求范围内有效session在一个会话范围内有效
!--scope默认是单例可以选择prototype多例scopesingleton|prototypesingleton:单例表示获取该bean所对应的对象都是同一个prototype:多例表示获取该bean所对应的对象都不是同一个
--
bean idstudent classcom.zylai.spring.pojo.Student scopeprototypeproperty namesid value1001/property namesname value张三/
/bean3.6 bean的生命周期
实例化调用无参构造实例注入调用set方法初始化之前的操作由后置处理器负责初始化需要通过bean的init-method属性指定初始化方法初始化之后的操作由后置处理器负责IOC容器关闭时销毁
测试验证
实例
public class User {private Integer id;private String username;private String password;private Integer age;public User() {System.out.println(生命周期1、创建对象);}public User(Integer id, String username, String password, Integer age) {this.id id;this.username username;this.password password;this.age age;}public Integer getId() {return id;}public void setId(Integer id) {System.out.println(生命周期2、依赖注入);this.id id;}public String getUsername() {return username;}public void setUsername(String username) {this.username username;}public String getPassword() {return password;}public void setPassword(String password) {this.password password;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age age;}public void initMethod() {System.out.println(生命周期3、初始化);}public void destroyMethod() {System.out.println(生命周期4、销毁);}Overridepublic String toString() {return User{ id id , username username \ , password password \ , age age };}
}后置处理
public class MyBeanPostProcessor implements BeanPostProcessor {Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {//此方法在bean的生命周期初始化之前执行System.out.println(MyBeanPostProcessor--后置处理postProcessBeforeInitialization);return bean;}Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {//此方法在bean的生命周期初始化之后执行System.out.println(MyBeanPostProcessor--后置处理postProcessAfterInitialization);return bean;}
}测试方法
/*** 1、实例化调用无参构造* 2、实例注入调用set* 3、后置处理的postProcessBeforeInitialization方法* 4、初始化需要通过bean的init-method属性指定初始化方法* 使用bean* 5、后置处理的postProcessAfterInitialization方法* 6、IOC容器关闭时销毁需要使用bean的destroy-method属性指定销毁方法** bean的作用域是单例时在创建IOC容器时bean实例就会被创建* 作用域是多例时只有在获取bean实例时才会被创建** bean的后置处理器会在生命周期的初始化前后添加额外的操作* 需要实现BeanPostProcessor接口* 且配置到IOC容器中需要注意的是bean后置处理器不是单独针对某一个bean生效* 而是针对IOC容器中所有bean都会执行*/
Test
public void test(){//ClassPathXmlApplicationContext扩展了刷新和销毁的子方法ClassPathXmlApplicationContext ioc new ClassPathXmlApplicationContext(spring-lifecycle.xml);User user ioc.getBean(User.class);System.out.println(获取bean并使用user);ioc.close();
}结果 3.7 工厂bean
概念
FactoryBean是Spring提供的一种整合第三方框架的常用机制。
和普通的bean不同配置一个FactoryBean类型的bean在获取bean的时候得到的并不是class属性中配置的这个类的对象而是getObject()方法的返回值。
通过这种机制Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都屏蔽起来只把最简洁的使用界面展示给我们。
将来我们整合Mybatis时Spring就是通过FactoryBean机制来帮我们创建SqlSessionFactory对象的。 一句话IOC容器会创建工厂bean getObject方法返回的实例类型不会去创建工厂bean的实例。这样我们直接从ioc容器中获取工厂创建的实例对象 实现FactoryBean接口
接口中的三个方法
getObject():返回一个对象给IOC容器getObjectType():设置所提供对象的类型isSingleton():所提供的对象是否为单例
当把FactoryBean的实现类配置为bean时会将当前类中的getObject方法返回的对象交给IOC容器管理
public class UserFactoryBean implements FactoryBeanUser {Overridepublic User getObject() throws Exception {return new User();}Overridepublic Class? getObjectType() {return User.class;}
}public class FactoryTest {Testpublic void test(){
// 在配置文件中只需要配置FactoryBean即可
// 当把FactoryBean的实现类配置为bean时
// 真正交给IOC容器管理的对象是FactoryBean工厂中getObject方法返回的对象
// 也就是说省略了传统的工厂模式从工厂实例中获取产品的步骤
// 而是直接把工厂的产品交给了ioc容器管理
// 另外还可以设置是否为单例ApplicationContext ioc new ClassPathXmlApplicationContext(spring-factory.xml);User user ioc.getBean(User.class);System.out.println(user);}
}3.8 自动装配⭐️
概念
根据指定的策略在IOC容器中匹配某个bean自动为为bean中的类类型的属性或者接口类型的属性赋值
实现
可以通过bean标签的autowire属性设置自动装配的策略
自动装配的策略 no,default表示不装配即bean中的属性不会自动匹配某个bean为某个属性赋值 byType根据赋值的属性的类型在IOC容器中匹配某个bean为属性赋值 异常情况 IOC中一个类型都匹配不上属性就不会装配使用默认值有多个类型的bean此时会抛出异常总结当使用ByType实现自动装配时IOC容器中有且仅有一个类型匹配的bean能够为属性赋值 byName将要赋值的属性的属性名作为bean的id在IOC容器中匹配某个bean为属性赋值 总结一般使用byType。特殊情况下当类型匹配的bean有多个时此时可以使用byName实现自动装配
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdbean classcom.zylai.spring.controller.UserControllerautowirebyType!--property nameuserService refuserService/--/beanbean iduserServiceclasscom.zylai.spring.service.impl.UserServiceImplautowirebyType!--property nameuserDao refuserDao/--/beanbean iduserDao classcom.zylai.spring.dao.impl.UserDaoImpl/bean
/beans4 IOC-基于注解管理Bean
4.1 使用注解注册bean组件
Component将类标识为普通组件Controller将类标识为控制层组件Service将类标识为业务层组件Repository将类标识为持久层组件
这四个注解本质和功能上完全一样后面三个相当于Component改了个名字但是对于开发人员便于理解
注意 在service层和dao层注解应该标识在接口的实现类上 加了注解的类在IOC容器中的默认id为类名的小驼峰
4.2 扫描组件 !--开启组件扫描--
!-- contxt:component-scan base-packagecom.zylai.spring.controller,com.zylai.spring.service.impl,--
!--com.zylai.spring.dao.impl/--
!-- 这个包下面下的所有类都会扫描--contxt:component-scan base-packagecom.zylai.spring!--contxt:exclude-filter 排除扫描设置不扫描哪些annotation根据注解类型进行排除expression中设置排除的注解的全类名assignable根据类名进行排除expression中设置排除的类的全类名--!--contxt:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/--!--contxt:exclude-filter typeassignable expressioncom.zylai.spring.controller.UserController/--!--contxt:include-filter 包含扫描设置只扫描谁注意需要在contxt:component-scan标签中设置属性use-default-filtersfalse,为false时设置的包下面所有的类都不需要扫描此时可以使用包含扫描为true时默认的设置的包下面所有的类都进行扫描此时可以使用排除扫描--!--contxt:include-filter typeassignable expressionorg.springframework.stereotype.Service/--/contxt:component-scan通过注解加扫描所配置的bean的id默认值为类名的小驼峰即类名的首字母为小写的结果注意接口应该是其实现类。可以通过标识组件注解的value属性设置bean的自定义的id
4.3 Autowired自动装配
4.3.1 能够标识的位置
成员变量上此时不需要设置成员变量的set方法set方法上为当前成员变量赋值的有参构造器上
4.3.2 注入的方式
Autowired默认通过byType方式自动注入在IOC容器中通过类型匹配某个bean为属性赋值
若有多个类型匹配的bean此时会自动转化为byName的方式来实现自动装配的效果
即将要赋值的属性的属性名作为bean的id匹配某个bean为属性赋值
若byType和byName的方式都无法实现自动装配即IOC容器中有多个类型匹配的bean且这些bean的id和要复制的属性的属性名都不一致此时抛异常。
此时可以在要赋值的属性上添加一个注解Qualifier(value)通过该注解的value属性值指定某个bean的id然后将这个bean为属性赋值 注意若IOC容器中没有任何一个类型匹配bean此时抛出异常NoSuchBeanDefinitionException 在Autowired注解中有个required属性默认值为true要求必须完成自动装配可以将required设置为false此时能装配则装配无法装配则使用属性的默认值 4.4 例子
controller
Controller
public class UserController {Autowiredprivate UserService userService;public void saveUser(){userService.saveUser();}
}service
Service
public class UserServiceImpl implements UserService {Autowiredprivate UserDao userDao;Overridepublic void saveUser() {System.out.println(保存信息--service);userDao.saveUser();}
}dao
Repository
public class UserDaoImpl implements UserDao {Overridepublic void saveUser() {System.out.println(保存成功--dao);}
}5 AOP-概念
5.1 代理模式
5.1.1 概念
二十三种设计模式中的一种属于结构型模式。它的作用就是通过提供一个代理类让我们在调用目标方法的时候不再是直接对目标方法进行调用而是通过代理类间接调用。
让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法减少对目标方法的调用和打扰同时让附加功能能够集中在一起也有利于统一维护。 5.1.2 静态代理
静态代理的思想就是代理对象和目标对象都实现同一个接口然后在代理对象中调用目标对象的方法。
接口
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);
}目标对象
public class CalculatorImpl implements Calculator{Overridepublic int add(int i, int j) {int result i j;System.out.println(方法内部resultresult);return result;}Overridepublic int sub(int i, int j) {int result i - j;System.out.println(方法内部resultresult);return result;}Overridepublic int mul(int i, int j) {int result i * j;System.out.println(方法内部resultresult);return result;}Overridepublic int div(int i, int j) {int result i / j;System.out.println(方法内部resultresult);return result;}
}代理对象
public class CalculatorStaticProxy implements Calculator {
// 将被代理的目标对象声明为成员变量private Calculator target;public CalculatorStaticProxy(Calculator target) {this.target target;}Overridepublic int add(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println([日志] add 方法开始了参数是 i , j);// 通过目标对象来实现核心业务逻辑int addResult target.add(i, j);System.out.println([日志] add 方法结束了结果是 addResult);return addResult;}
}可以看到通过静态代理达到了我们想要的目标。
缺点代理都写死了不具备灵活性。比如将来要给其他的类加上这个日志功能那么还需要创建很多的代理类产生大量重复的代码。
5.1.3 动态代理
比如说将日志功能都集中到一个代理类中将来有任何的日志需求都通过这一个代理类实现。这里就用到了动态代理的技术。
使用JDK动态代理
public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target target;}public Object getProxy(){//使用JDK动态代理/*** 必须给出类加载器根据类加载器来创建类* ClassLoader loader指定目标对象使用的类加载器* Class?[] interfaces获取目标对象实现的所有接口的class对象的数组* InvocationHandler h 设置代理类中的抽象方法如何重写*/ClassLoader classLoader target.getClass().getClassLoader();Class?[] interfaces target.getClass().getInterfaces();InvocationHandler h new InvocationHandler() {//这里是设置代理类中的方法如何重写//proxy:表示代理对象method表示要执行的方法args表示要执行的方法的参数列表Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object res null;try {System.out.println(日志方法method.getName(),参数 Arrays.toString(args));//调用目标对象的方法res method.invoke(target, args);System.out.println(日志方法method.getName(),结果res);return res;} catch (Exception e) {e.printStackTrace();System.out.println(日志方法method.getName(),异常e);} finally {System.out.println(日志方法method.getName(),方法执行完毕);}return res;}};return Proxy.newProxyInstance(classLoader,interfaces,h);}
}ProxyFactory proxyFactory new ProxyFactory(new CalculatorImpl());
Calculator proxy (Calculator)proxyFactory.getProxy();
proxy.add(1,2);5.2 AOP概念及相关术语
AOP
AOPAspect Oriented Programming面向切面编程是一种设计思想是面向对象编程的一种补充和完善。它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术
简单来说就是把非核心业务抽取出来给切面类管理。再把抽取出来的放到相应的位置。
横切关注点
从目标对象中抽取出来的非核心业务比如之前代理模式中的日志功能针对于计算器功能来说日志就是非核心业务。
这个概念不是语法层面天然存在的而是根据附加功能的逻辑上的需要有十个附加功能就有十个横切关注点。 通知
非核心的业务再目标对象中叫做横切关注点将横切关注点抽取出来封装到切面类中他就是这个类中的一个方法叫做通知。
每一个横切关注点要做的事情都封装成一个方法这样的方法就叫做通知方法。
前置通知在被代理的目标方法前执行返回通知在被代理的目标方法成功结束后执行寿终正寝异常通知在被代理的目标方法异常结束后执行死于非命后置通知在被代理的目标方法最终结束后执行盖棺定论环绕通知使用try…catch…finally结构围绕整个被代理的目标方法包括上面四种通知对应的所有位置 切面
封装横切关注点的类通知的方法都写在切面类中 目标
被代理的目标对象比如计算器的实现类
代理
代理对象
连接点
一个纯逻辑的概念抽取横切关注点的位置比如方法执行之前方法捕获异常的时候等等。
连接点的作用我们不但要抽取出来还要套回去。 切入点
定位连接点的方式。
总结
抽和套抽取出来横切关注点封装到切面中就是一个通知。然后通过切入点找到连接点把通知套到连接点的位置。
6 AOP-基于注解的AOP 6.1 技术说明
动态代理InvocationHandlerJDK原生的实现方式需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口兄弟两个拜把子模式。cglib通过继承被代理的目标类认干爹模式实现代理所以不需要目标类实现接口。AspectJ本质上是静态代理将代理逻辑“织入”被代理的目标类编译得到的字节码文件所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
6.2 配置
maven依赖
!-- spring-aspects会帮我们传递过来aspectjweaver --
dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion5.3.1/version
/dependency配置文件
切面类和目标类都需要交给IOC容器管理切面类必须通过Aspect注解标识为一个切面切面类必须通过Aspect注解标识为一个切面
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd!--AOP的注意事项切面类和目标类都需要交给IOC容器管理切面类必须通过Aspect注解标识为一个切面在spring的配置文件中设置aop:aspectj-autoproxy/标签开启基于注解的AOP功能
--context:component-scan base-packagecom.zylai.spring.aop.annotation/!--开启基于注解的AOP功能--aop:aspectj-autoproxy/
/beans6.3 切面类重点 在切面中需要通过指定的注解将方法标识为通知方法 Before前置通知在目标方法执行之前执行After后置通知在目标对象方法的finally子句中执行AfterReturning返回通知在目标对象方法返回值之后执行AfterThrowing异常通知在目标对象方法的catch子句中执行 切入点表达式设置在表示通知的注解的value属性中 * execution(public int com.zylai.spring.aop.annotation.CalculatorImpl.add(int,int))
* execution(* com.zylai.spring.aop.annotation.*.*(..))
* 第一个*表示任意的访问修饰符和返回值类型
* 第二个*表示包下所有的类
* 第三个*表示类中任意的方法
* ..表示任意的参数列表重用连接点表达式 * //声明一个公共的切入点表达式
* Pointcut(execution(* com.zylai.spring.aop.annotation.CalculatorImpl.*(..)))
* public void pointCut(){}
* 使用方式 After(pointCut())重用连接点表达式 * 在通知方法的参数位置设置JoinPoint类型的参数就可以获取连接点所对应的方法信息
* //获取连接点对应方法的签名信息
* Signature signature joinPoint.getSignature();
* //获取连接点所对应的参数
* Object[] args joinPoint.getArgs();切面的优先级 * 可以通过Order注解的value属性设置优先级默认值为Integer.MAX
* value值越小优先级越高总的
* 1. 在切面中需要通过指定的注解将方法标识为通知方法*
* 2. 切入点表达式设置在表示通知的注解的value属性中
* execution(public int com.zylai.spring.aop.annotation.CalculatorImpl.add(int,int))
* execution(* com.zylai.spring.aop.annotation.*.*(..))
* 第一个*表示任意的访问修饰符和返回值类型
* 第二个*表示包下所有的类
* 第三个*表示类中任意的方法
* ..表示任意的参数列表
*
* 3.重用连接点表达式
* //声明一个公共的切入点表达式
* Pointcut(execution(* com.zylai.spring.aop.annotation.CalculatorImpl.*(..)))
* public void pointCut(){}
* 使用方式 After(pointCut())
*
*
* 4. 获取连接点信息
* 在通知方法的参数位置设置JoinPoint类型的参数就可以获取连接点所对应的方法信息
* //获取连接点对应方法的签名信息
* Signature signature joinPoint.getSignature();
* //获取连接点所对应的参数
* Object[] args joinPoint.getArgs();
*
* 5.切面的优先级
* 可以通过Order注解的value属性设置优先级默认值为Integer.MAX
* value值越小优先级越高Component
Aspect//将当前组件表示为切面
public class LoggerAspect {Pointcut(execution(* com.zylai.spring.aop.annotation.CalculatorImpl.*(..)))public void pointCut(){}// Before(execution(public int com.zylai.spring.aop.annotation.CalculatorImpl.add(int,int)))//表示这个类下所有的方法用*表示所有参数列表用..表示所有的参数列表
// Before(execution(* com.zylai.spring.aop.annotation.CalculatorImpl.*(..)))Before(pointCut())public void beforeAdviceMethod(JoinPoint joinPoint){//获取连接点对应方法的签名信息签名信息就是方法的声明信息Signature signature joinPoint.getSignature();//获取连接点所对应的参数Object[] args joinPoint.getArgs();System.out.println(LoggerAspect前置通知方法signature.getName(),参数 Arrays.toString(args));}After(pointCut())public void afterAdviceMethod(JoinPoint joinPoint){//获取连接点对应方法的签名信息签名信息就是方法的声明信息Signature signature joinPoint.getSignature();System.out.println(LoggerAspect后置通知方法signature.getName()执行完毕);}//在返回通知中若要获取目标对象方法的返回值只需要通过注解的returning属性值//就可以将通知方法的某个参数指定为接收目标对象方法的返回值AfterReturning(value pointCut(),returning result)public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result){Signature signature joinPoint.getSignature();System.out.println(LoggerAspect返回通知方法signature.getName()结果result);}//在返回通知中若要获取目标对象方法的异常只需要通过注解的throwing属性值//就可以将通知方法的某个参数指定为接收目标对象方法出现的异常AfterThrowing(value pointCut(),throwing ex)public void afterThrowingAdviceMethod(JoinPoint joinPoint,Throwable ex){Signature signature joinPoint.getSignature();System.out.println(LoggerAspect异常通知方法signature.getName()异常ex);}Around(pointCut())//环绕通知的方法返回值一定要和目标方法的返回值一致public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){Object result null;try {System.out.println(环绕通知--前置通知);//表示目标对象方法的执行result joinPoint.proceed();System.out.println(环绕通知--返回通知);} catch (Throwable throwable) {throwable.printStackTrace();System.out.println(环绕通知--异常通知);}finally {System.out.println(环绕通知--后置通知);}return result;}
}7 声明式事务 在Spring中提供了封装了jdbc的JdbcTemplate在之后的事务模块中我们使用JdbcTemplate执行SQL 4.1 JdbcTemplate
4.1.1 配置
maven
!-- Spring 持久化层支持jar包 --
!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中需要使用orm、jdbc、tx三个
jar包 --
!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 --
dependencygroupIdorg.springframework/groupIdartifactIdspring-orm/artifactIdversion5.3.1/version
/dependency配置文件
!--引入jdbc.properties之后可以通过${key}的方式访问value--
!--这里的context标签注意上面引入的--
!--在web项目中有两种路径一个是类路径一个是web资源路径--
context:property-placeholder locationclasspath:jdbc.properties/context:property-placeholderbean iddataSource classcom.alibaba.druid.pool.DruidDataSourceproperty namedriverClassName value${jdbc.driver}/property nameurl value${jdbc.url}/property nameusername value${jdbc.username}/property namepassword value${jdbc.password}/!--property nameinitialSize value${initialSize}/--!--property namemaxActive value${maxActive}/--
/beanbean idjdbcTemplate classorg.springframework.jdbc.core.JdbcTemplateproperty namedataSource refdataSource/
/bean4.1.2 使用(在Spring中进行测试)
//指定当前测试类在spring的测试环境中执行此时就可以通过注入的方法直接获取IOC容器的bean
RunWith(SpringJUnit4ClassRunner.class)
//设置Spring测试环境的配置文件
ContextConfiguration(classpath:spring-jdbc.xml)
public class JdbcTemplateTest {Autowiredprivate JdbcTemplate jdbcTemplate;Testpublic void testInsert(){String sql insert into t_user values(null,?,?,?,?,?);int update jdbcTemplate.update(sql, root, 123, 23, 女, 123163.com);System.out.println(update);}Testpublic void testGetUserById(){String sql select * from t_user where id ?;User user jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(User.class), 1);System.out.println(user);}Testpublic void testGetAllUser(){String sql select * from t_user;ListUser list jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class));System.out.println(list);}Testpublic void testGetCount(){String sql select count(*) from t_user;Integer count jdbcTemplate.queryForObject(sql, Integer.class);System.out.println(count);}
}4.2 声明式事务的概念
4.2.1 编程式事务 4.2.2 声明式事务
既然事务控制的代码有规律可循代码的结构基本是确定的所以框架就可以将固定模式的代码抽取出来进行相关的封装。
封装起来的好处
好处1提高开发效率好处2消除了冗余的代码好处3框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题进行了健壮性、性能等各个方面的优化
编程式自己写代码实现功能 声明式通过配置让框架实现功能
4.3 基于注解的声明式事务
场景模拟 用户购买图书先查询图书的价格再更新图书的库存和用户的余额 假设用户id为1的用户购买id为1的图书 用户余额为50而图书价格为80 购买图书之后用户的余额为-30数据库中余额字段设置了无符号因此无法将-30插入到余额字段 此时执行sql语句会抛出SQLException 加入事务
配置事务管理器并且加上数据源属性开启事务的注解驱动
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/context xmlns:txhttp://www.springframework.org/schema/txxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd!--扫描组件--context:component-scan base-packagecom.zylai.spring/context:component-scan!-- 导入外部属性文件 --context:property-placeholder locationclasspath:jdbc.properties /!-- 配置数据源 --bean iddruidDataSource classcom.alibaba.druid.pool.DruidDataSourceproperty nameurl value${jdbc.url}/property namedriverClassName value${jdbc.driver}/property nameusername value${jdbc.username}/property namepassword value${jdbc.password}//bean!-- 配置 JdbcTemplate --bean idjdbcTemplate classorg.springframework.jdbc.core.JdbcTemplate!-- 装配数据源 --property namedataSource refdruidDataSource//bean!-- 配置事务管理器--bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdruidDataSource//bean!--开启事务的注解驱动将使用Transactional注解所标识的方法或类中所有的方法使用事务进行管理Transactional注解在哪连接点就在哪里transaction-manager属性设置事务管理器的id若事务管理器的id默认为transactionManager则改属性可以不写--tx:annotation-driven transaction-managertransactionManager//beans在需要进行事务操作的类或方法上加上注解即可
Service
public class BookServiceImpl implements BookService {Autowiredprivate BookDao bookDao;OverrideTransactionalpublic void buyBook(Integer userId, Integer bookId) {//查询图书的价格Integer price bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId,price);}
}结果如果更新图书的库存顺利执行而更新用户余额执行失败那么将会回滚事务图书库存将会恢复。
4.4 事务的属性
1、只读 介绍 对一个查询操作来说如果我们把它设置成只读就能够明确告诉数据库这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化 使用方式 Transactional(readOnly true)public void buyBook(Integer userId, Integer bookId) {//查询图书的价格Integer price bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId,price);}注意 对增删改操作设置只读会抛出下面异常 Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
2、超时 介绍 事务在执行过程中有可能因为遇到某些问题导致程序卡住从而长时间占用数据库资源。而长时间占用资源大概率是因为程序运行出现了问题可能是Java程序或MySQL数据库或网络连接等等。 此时这个很可能出问题的程序应该被回滚撤销它已做的操作事务结束把资源让出来让其他正常程序可以执行。 概括来说就是一句话超时回滚释放资源。 使用方式 Transactional(timeout 3)
public void buyBook(Integer bookId, Integer userId) {try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}//查询图书的价格Integer price bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);//System.out.println(1/0);
}观察结果 执行过程中抛出异常 org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Fri Jun 04 16:25:39 CST 2022
3、回滚策略
声明式事务默认对于运行时异常都进行回滚一般使用的是在此基础上加上不因为哪个异常而回滚
可以通过Transactional中相关属性设置回滚策略
rollbackFor属性需要设置一个Class类型的对象rollbackForClassName属性需要设置一个字符串类型的全类名noRollbackFor属性需要设置一个Class类型的对象rollbackFor属性需要设置一个字符串类型的全类名
Transactional(noRollbackFor ArithmeticException.class)
//Transactional(noRollbackForClassName java.lang.ArithmeticException)
public void buyBook(Integer bookId, Integer userId) {//查询图书的价格Integer price bookDao.getPriceByBookId(bookId);//更新图书的库存bookDao.updateStock(bookId);//更新用户的余额bookDao.updateBalance(userId, price);System.out.println(1/0);
}4、隔离级别
数据库系统必须具有隔离并发运行各个事务的能力使它们不会相互影响避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别不同隔离级别对应不同的干扰程度隔离级别越高数据一致性就越好但并发性越弱。
隔离级别一共有四种 读未提交READ UNCOMMITTED 允许Transaction01读取Transaction02未提交的修改。 读已提交READ COMMITTED、 要求Transaction01只能读取Transaction02已提交的修改。 可重复读REPEATABLE READ 确保Transaction01可以多次从一个字段中读取到相同的值即Transaction01执行期间禁止其它事务对这个字段进行更新。 串行化SERIALIZABLE 确保Transaction01可以多次从一个表中读取到相同的行在Transaction01执行期间禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题但性能十分低下。
各个隔离级别解决并发问题的能力见下表
隔离级别脏读不可重复读幻读READ UNCOMMITTED有有有READ COMMITTED无有有REPEATABLE READ无无有SERIALIZABLE无无无
各种数据库产品对事务隔离级别的支持程度:
隔离级别OracleMySQLREAD UNCOMMITTED×√READ COMMITTED√默认√REPEATABLE READ×√默认SERIALIZABLE√√
使用
Transactional(isolation Isolation.DEFAULT)//使用数据库默认的隔离级别
Transactional(isolation Isolation.READ_UNCOMMITTED)//读未提交
Transactional(isolation Isolation.READ_COMMITTED)//读已提交
Transactional(isolation Isolation.REPEATABLE_READ)//可重复读
Transactional(isolation Isolation.SERIALIZABLE)//串行化5、事务的传播
简单来说
结账买两本书。
如果以结账时间为事务第二本买失败第一本一会回滚。
如果用买书本身的操作就是能买几本就是几本。
详细介绍
当事务方法被另一个事务方法调用时必须指定事务应该如何传播。例如方法可能继续在现有事务中运行也可能开启一个新事务并在自己的事务中运行。