Spring框架笔记
开闭原则(Open Cloce Protocal, OCP):软件应对扩展开放,对修改关闭 依赖倒转原则(Dependency Inversion Principle, DIP): * 高层模块不应该依赖低层模块,二者都应该依赖其抽象
* 中心思想:面向接口编程,面向抽象编程
* 目的:降低程序耦合,提高扩展性能
控制反转 编程思想(Inversion of Control, IoC) * 不在程序中采用硬编码的方式来新建对象
* 不在程序中采取硬编码的方式来维护对象关系
* 由于出现较晚,没有被纳入GoF23种设计模式范围内
* 实现方式较多,其中比较重要的有:依赖注入(Dependency Injection, DI)
* IoC是思想,DI是实现
* 注入方法:Set方法注入/构造方法注入
* set注入
1 2 3 4 <property name ="set方法去掉set后首字母变成小写" ref ="被注入的BeanID" />
* 构造方法注入
1 2 3 4 5 6 7 8 9 <bean id ="" class ="" > <constructor-arg index ="0" ref ="BeanID" /> <constructor-arg index ="1" ref ="BeanID" /> ...... <constructor-arg name ="参数名" ref ="" /> </bean >
Spring框架 * 实现控制反转IoC,协助新建对象/维护对象关系
* 实现IoC思想的容器
* 八大模块:
* Core: 基于IoC的核心,实现对Bean的管理,主要组件是BeanFactorty
* AOP: 次核心,面向切面编程
* Web MVC: Spring自己提供的一套MVC框架(暨SpringMVC)
* WebFlux: 响应式Web框架
* Web: 支持集成常见的Web框架(如struts,webwork)
* DAO: 提供了单独的支持JDBC操作的API
* ORM: 支持集成常见的ORM框架(如MyBatis,Hibernate)
* Context: 提供扩展服务
* 特点
* 轻量
* 非侵入式:Spring应用中对象不依赖Spring的特定类
* 轻量:体积小
* 控制反转:底层使用工厂模式(XML解析+工厂模式+反射机制)
* 面向切面
* 容器
* 框架
* pom.xml
* packaging 打包方式
* repositories 仓库地址
* dependencies 依赖
Spring的配置文件 * 放在resources目录下的xml文件
* 配置标签,让spring创造对象
* bean的两个重要属性:
* id bean的唯一标识符
* 类的全限定类名(带包名)
Spring容器的使用 * 获取Spring容器对象:ApplicationContext ac=new ClassPathXmlApplicationContext("配置文件路径")
* 注意:运行上一行句内代码即视为启动Spring容器,解析xml文件,并实例化其所有的Bean对象
* 根据BeanID获取对象:ac.getBean("Bean的ID",bean类型.class); 参数2若留空,返回类型默认为Object
* spring框架通过反射机制来创建对象→一定要保证无参构造方法存在
* Class cz=Class.forName("完整类名")
* Object obj=cz.newInstance()
* 底层使用Map<String,Object存储>
* 同一个xml文件内不可以有同id的bean,若同时加载多个xml后,存在同id的bean,后加载的bean的会覆盖先加载的bean,被覆盖的bean不会被实例化(猜测:也许是先加载完所有的bean然后再统一实例化?)
* getBean的时候不可以虚空索敌,会报错
1 2 3 4 5 6 7 8 9 10 11 <repository > <id > repository.spring.milestone</id > <name > Spring Milestone Repository</name > <url > https://repo.spring.io/milestone</url > </repository > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.0.0-M2</version > </dependency >
启动Log4j2日志框架 * 引入依赖
1 2 3 4 5 6 7 8 9 10 11 <properties > <log4j.version > 2.14.1</log4j.version > </properties > <dependencies > <dependency > <groupId > org.apache.logging.log4j</groupId > <artifactId > log4j-slf4j-impl</artifactId > <version > ${log4j.version}</version > </dependency > </dependencies >
* 创建xml配置文件
* 在项目内记录日志信息
* 创建日志记录器对象
1 Logger logger=LoggerFactory.getLogger(被记录类名.class)
* 根据不同级别输出日志
set注入详解 * 注入外部Bean:外部定义好bean,property中使用ref引用
1 2 3 4 <bean id ="exm1" class ="bean" > </bean > <bean id ="Service" class ="service" > <property name ="ServiceBean1" ref ="exm1" /> </bean >
* 注入内部Bean:在内部property中嵌套定义bean,不用写ref(少用)
1 2 3 4 5 <bean id ="Service" class ="service" > <property name ="ServiceBean1" > <bean class ="bean" > </bean > </property > </bean >
* 注入简单类型(如int,char,enum等):property中不指定ref,property指定value
~~Date虽然可以使用value赋值,但对于格式有着严格的要求,如Mon Feb 6 14:50:17 CST 2023,所以尽量还是用ref~~
* 级联属性赋值:`\<property name=id.属性 value=""/\>`
注意:如果想要使用级联属性赋值,需要编写对应id-bean的get方法
* 注入数组:property内嵌array,使用value/ref填充
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <bean id ="array1" class ="String" > <property name ="val" > <array > <value > value1</value > <value > value2</value > </array > </property > </bean > <bean id ="r1" class ="exam" > <property name ="e1" value ="e11" /> </bean > <bean id ="r2" class ="exam" > <property name ="e2" value ="e22" /> </bean > <bean id ="r3" class ="exam" > <property name ="e3" value ="e33" /> </bean > <bean id ="array2" class ="notSimple" > <property name ="array" > <array > <ref bean ="r1" /> <ref bean ="r2" /> <ref bean ="r3" /> </array > </property > </bean >
* 注入List/Set/Map/属性类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <bean id ="array3" class ="notSimple" > <property name ="List" > <list > <value > val1</value > <value > val2</value > <value > val3</value > </list > </property > <property name ="Set" > <set > <value > val1</value > <value > val2</value > <value > val3</value > </set > </property > <property name ="Map" > <map > <entry key ="" value ="" /> <entry key-ref ="" value-ref ="" /> </map > </property > <property name ="properties" > <props > <prop key ="key" > value</prop > <prop key ="pwd" > pwd</prop > </props > </property > </bean >
* 注入null和空字符串
1 2 3 4 5 6 7 8 <property name ="qwq" > <null /> </property > <property name ="qwq" value ="null" > <property name ="qwq" > <value /> </property > <property name ="qwq" value ="" >
使用实体符号代替特殊符号
|符号|转义字符|
|:-:|:-:|:-:|
|>|& gt;|
|<|& lt;|
|'|& apos;|
|"|& quot;|
|&|& amp;|
|¿|& iquest;|
将特殊符号放到<![CDATA[…]]>(只能用value标签,即双标记value)
命名空间注入 1. p命名空间注入
* 目的:简化配置
* 基于set注入
* 配置文件头:xmlns:p="http://www.springframework.org/schema/p
1 <bean id ="" class ="" p:简单属性 ="" p:复杂属性-ref ="" />
2. c命名空间注入
* 目的:简化配置
* 基于构造方法注入
* 配置文件头:xmlns:p="http://www.springframework.org/schema/c
1 <bean id ="" class ="" c:下标 ="" c:下标-ref ="" c:参数名 ="" />
util命名空间实现配置复用 * 配置文件头:xmlns:p="http://www.springframework.org/schema/util
* 更改schemaLocation:www.springframework.org/schema/util http://ww.springframework.org/schema/util/spring-util.xsd"
1 2 3 4 5 6 7 8 <util:properties id ="" > <prop key ="" > ...</prop > </util:properties > <bean id ="..." class ="..." > <property name ="properties" ref ="上面的id" /> </bean >
基于XML的自动装配 1 <bean id ="" class ="" autowire ="byName" > </bean >
* 根据名字进行自动装配,底层基于set方法
* 被自动注入的bean的id需要和set方法中的name一致(去掉set首字母小写)
1 2 <bean class ="" > </bean > <bean id ="" class ="" autowire ="byType" > </bean >
* 根据类型进行自动装配,底层基于set方法
* 一个类型的对象只可以有一个
引入外部properties文件 * 配置文件头,引入context命名空间:xmlns:p="http://www.springframework.org/schema/context
* 更改schemaLocation:www.springframework.org/schema/context http://ww.springframework.org/schema/util/spring-context.xsd"
* 使用context:property-placeholder的location属性指定路径,从根目录开始寻址
* resources相当于根目录
* 使用${key}取值,保留双引号
* 警惕系统变量乱入.
bean的作用域 * scope项:
* Spring默认是单例的,即singleton,启动容器时即创建对象
* 将bean的scope属性设置为prototype则为多例,此时每一次getBean则实例化一次
* 若引用SpringMVC框架,可有request(一次请求一个bean)和session(一次会话一个值)
* 还有其他少用scope项 如global session(portlet应用专用)\application(一个应用)\websocket(一个生命周期)\自定义
bean的实例化 * 构造方法实例化-自动调用无参构造方法
* 简单工厂模式实例化
1 <bean id ="" class ="工厂类完整名" factory-method ="创建的静态方法" />
* factory-bean实例化
1 2 3 4 <bean id ="" factory-bean ="工厂类id" factory-method ="创建的静态方法" /> <bean id ="factory" class ="org.example.factory" /> <bean id ="qwq" factory-bean ="factory" factory-method ="get" />
* FactoryBean接口实例化
1 2 3 4 5 6 7 8 public class PersonFactoryBean implements FactoryBean <Person>{ @override public Person getObject () throws Excrption () { return new Spring ; } @override ...... }
1 2 <bean id ="qwq" class ="PersonFactoryBean" />
Bean的生命周期 1. 实例化
2. Bean属性赋值
2.5 检查Bean是否实现了Aware相关接口,并设置相关依赖
3. 执行“bean后处理器”的before方法
3.5 检查Bean是否实现了InitializingBean接口,调用方法
4. 初始化bean(xml中配置init-method="")
5. 执行“bean后处理器”的after方法
6. 使用bean
6.5 检查Bean是否实现了DisposableBean接口并调用接口方法
7. 销毁bean(xml中配置destroy-method="")
* bean后处理器: implements BeanPostProcessor并重写其before和after方法,写完在xml中创建该bean即可
* 一旦配置则对当前xml所有的bean都生效
* 作用:检查Bean是否实现了某些特定的接口
* Spring容器仅对singleton的bean进行完整的生命周期管理,其他scope只负责初始化完毕
1 2 3 DefaultListableBeanFactory fac=new DefaultListableBeanFactory (); fac.registerSingleton(beanname(String),object)
Bean的循环依赖
spring仅可以自动处理set注入/singleton情况下的循环依赖问题
构造注入/(singleton/prototype/Others)情况下__无法解决__ 原理:调用无参构造方法实例化对象(此时即曝光该Bean对象),然后使用set注入
Bean的三级缓存 private final Map<String,Object> singletonObjects 一级缓存 private final Map<String,Object> earlysingletonObjects 二级缓存 private final Map<String,ObjectFactory<?>> singletonFactories 三级缓存(该map集合即被称为缓存) key为bean的id value: 一级缓存:经过注入后完整的bean对象 二级缓存:已经被实例化,但是还没有赋值属性的bean对象 三级缓存:单例工厂对象
Spring IoC 注解式开发 目的:简化XML配置 使用方式:在配置文件中添加aop依赖,配置context命名空间,配置扫描,在类上只用注解
1 2 <beans xmlns:context ="http://www.springframeword.org/schema/context" > <context:component-scan bace-package ="" />
注解的开发1 2 3 4 5 6 7 8 9 10 11 @Target(value={ElementType.TYPE, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Component{ }
用于bean的注解
Component
Repository
Controller
Service
如何在spring中使用注解
加入aop的依赖
添加context命名空间
指定扫描包
使用注解即可1 2 3 <context:component-scan base-package ="被扫描包" />
选择性实例化bean
方案1:<context:component-scan base-package=”被扫描包” use-default-filters=”false”/> false时让该包下所有的带有声明Bean的注解全部失效,Beans集体去世 在此标签间可以加入(记得改为双标签): <context:include-filter type=”annotation” expression=”注解的完整类名,如org.springframework.stereotype.Repository”> 让带有某注解的失效
方案2:<context:component-scan base-package=”被扫描包” use-default-filters=”true”/> true时让该包下所有的带有声明Bean的注解全部生效 <context:exclude-filter type=”annotation”,expression=”注解的完整类名”> 让带有某注解的Bean生效
负责注入的注解
@Value 指定bean内参数值,只可以注入简单类型,可以在属性上,可以用在set方法上,也可以用在构造方法的形参上
@Autowired 不需要指定任何属性,直接使用即可 但对应类型的类只可以有一个实例对象,即便是接口实现/类的继承也会引起冲突 默认为byType,可以使用在构造方法、方法、形参、属性、注解上 UserDao->(UserDaoImplForPgSQL/UserDaoImplForMySQL),这种情况如果AutoWired注解一个UserDao对象就会引起注入冲突如果直接在代码中指定对象类型为xxxForMySQL这种会违反开闭原则哦 可以注入非简单类型(ByName/ByType) 解决方法:使用byName,@Autowired和Qualifier联合使用,在Qualifier中指定名字
@Qualifier 指定注入类名字,和Autowired搭配使用
@Resource 完成非简单类型注入,是JDK提供的注解 默认byName,未指定时使用属性名作为name,如果还找不到就byType装配 可以使用在属性和setter方法上一般推荐使用Resource替代Autowired 引用注解:1 2 3 4 5 6 7 8 9 10 <dependency > <groupId > jakarta.annotation</groupId > <artifactId > jakarta.annotation-api</artifactId > <version > 2.1.1</version > <groupId > javax.annotation</groupId > <artifactId > javax.annotation-api</artifactId > <version > 1.3.2</version > </dependency >
1 2 3 4 5 6 7 8 9 10 public A{ @Resource(name="") public String a1; @Resource(name="student1") public Student s1; @Resource public Student student2; }
全注解式开发 使用一个类代替配置文件
1 2 3 4 5 6 7 8 9 10 11 12 @Configuration @ComponentScan({packageA,packageB}) public class ConfigClass {} public void test{ AnnotationConfigApplicationContext ac=new AnnotationConfigApplicatinContext ("Configclass" ); ac.getbean(...) }
AOP-面向切面编程 AOP:Aspect Orinted Programming,是OOP的补充和延申 可以将和业务逻辑无关的、可复用的代码(即交叉业务)抽离出来,,形成组件 在程序的特定地点(切面)插入 如果不采用AOP,可能导致的问题有:
部分交叉业务代码在多个业务中反复出现,并且和业务代码掺杂,修改难度较大
代码者难以专注于核心代码的编写 优点有:
开发者可以专注于业务逻辑
交叉业务易维护
代码复用性更强
AOP的常见术语
连接点(JoinPoint):程序可以织入切面的位置
切点(Pointcut):被 织入切面的方法
通知(Advice):又被称作增强,就是具体要织入的代码体 包括( 前置:仅在Pointcut前 @Before, 后置:仅在Pointcut后 @AfterReturning, 环绕:在Pointcut前后 @Around, 异常:Pointcut位于catch @AfterThrowing, 最终:Pointcut位于finally语句块 @After )通知;
切面(Aspect):切点+通知即切面
织入(Weaving):把通知应用到目标位置的过程
代理对象(Proxy):一个目标对象被织入通知后产生的新对象
目标对象(Target):被织入通知的对象
切点表达式 用于定义通知往哪些方法上切入 基本格式:
1 execution([访问权限] 返回值类型 [全限定类名][方法名](形参列表) [异常])
访问权限:
返回值类型:
全限定类型:
可选
填写..表示当前包和子包下所有的类
省略表示所有类
方法名
必填 表示所有方法 set 表示所有以set开始的方法
形式参数列表
必填
(..,类型个数随意),(,没有参数的方法),(,有一个参数的方法),( ,String)第一个参数随意,第二个String
异常
例子:
1 2 execution(public * org.example.*(..))
使用Spring的AOP 方式1:Spring+AspectJ-注解方式 方式2:Spring+AspectJ-XML方式 方式3:Spring-注解方式 引入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 6.0.0-M2</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aop</artifactId > <version > 6.0.0-M2</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-aspects</artifactId > <version > 6.0.0-M2</version > </dependency >
使用注解实现AOP 定义切面类,使用注解指定通知类型,括号内指定切点(可以使用切点表达式,也可以同一切点)
将切面纳入Spring管理(通过注解/XML)1 2 3 4 5 6 7 8 9 10 @Component("textAspect") @Aspect public class asp { @before(切点表达式) public void enhance () { System.out.println("qwq" ); } }
开启自动代理1 2 3 4 5 6 7 8 <aop:aspectj-autoproxy />
通用切点 1 2 3 4 5 6 7 8 9 @Pointcut("切点表达式") public void poincut () { } @After(poincut()) public void afterPoincut () { }
连接点 在通知可以传入ProceedingJoinPoint类型的对象,即连接点,可以直接调用其成员方法
.proceed() 运行
.getSignature() 获取信息
全注解开发 之前提过一次,但是当时只有IoC全注解开发
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration @ComponentScan({packageA,packageB}) @EnableAspectJAutoProxy(proxyTargetClass=true) public class ConfigClass {} public void test{ AnnotationConfigApplicationContext ac=new AnnotationConfigApplicatinContext ("Configclass" ); ac.getbean(...) }
基于注解的实现 1 2 3 4 5 6 <aop:config > <aop:pointcut id ="?" expression ="切点表达式" > <aop:aspect ref ="之前注册的切点bean名称" > <aop:切点类型 method: "切面中的方法名称 " pointcut-ref: "切点名称 "> </aop:aspect > </aop:config >
AOP编程式事务解决方案 ->满足ACID特性 解决方案:利用AOP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Aspect @Conponent("transactionAspect") public void Aspect{ @Around("execution(* org.example.springtest..*(..))") public void aroundAdvice (ProceedingJoinPoint joinpoint) { try { joinpoint.proceed(); }catch (Throwable e){ } } }
Spring对事务的支持(声明式事务) 前置知识:锁和隔离 事务的四个特性(ACID)
Atomic 原子性 事务是最小的工作单元
Consistence 一致性 一个事务中的操作,要不全部执行,要不全不执行
Isolation 隔离性 事务和事务之间互不干扰
Duration 持久性 事务结束之后它带来的影响永远保存
Spring提供的事务管理API 接口:PlatformTransationManager 接口的实现:
DataSourceTransationManager 支持JdbcTemplate,Mybatis等事务管理
JtaTransationManager 支持分布式事务管理
声明式事务-注解实现方式
配置xml 命名空间:tx
1 2 3 4 5 6 <bean id ="txManager" class ="org.springframework.jdbc.datasource.DataSourceTransationManager" > <property name ="dataSource" ref ="dataSource" > </bean > <tx:annotation-driven transation-manager ="txManager" />
在方法上/类上加入注解
1 2 3 4 @Transational public class transferService { ... }
事务的传播(Propagation)行为 spring一共有七种传播行为
REQUIRED 支持当前事务,如果不存在就新建
SUPPORTS 支持当前事务,如果没有就非事务
MANDATORY 必须运行在一个事务中,如果没有事务正在发生,就异常
REQUIRES_NEW 开启新事务,挂起旧事务(若有)
NEVER 以非事务方式运行,如果有事务,异常
NESTED 如果有事务,嵌套运行事务,可以独立提交或回滚,如果外层没有,就相当于REQUIRED
设置方法
1 2 3 4 @Transational(propagation=Propagation.REQUIRED) public class transferService { ... }
事务的隔离级别 详细的参照这个->锁和隔离
未提交读(READ_UNCOMMITTED) 事务中的修改即使没有提交也对其他事务可见
提交读(READ_COMMITTED) 事务的修改只有提交后才对其他事务可见,这也是大多数数据库系统的默认隔离级别
可重复读(REPEATABLE_READ) 此级别是MySQL的默认事务隔离级别,采用多版本并发控制,避免了幻读的问题
可串行化(SERIALIZABLE) 强制事务串行执行,会在读取的每一行数据都上锁,因此会导致大量的超时和锁争用问题 设置方法
三大读级别
脏读:读取到尚未提交到数据库里的书
不可重复读:在同一次事务中,两次读取对于同一数据的结果不一样
幻读:第一次和第二次读取的数据条数不一样,多出来的行即为幻行1 2 3 4 @Transational(isolation=Isolation.READ_UNCOMMITED) public class transferService { ... }
事务超时 1 2 3 4 5 @Transational(timeout=10) public class transferService { ... }
只读事务 1 2 3 4 5 @Transational(readOnly=true) public class transferService { ... }
异常回滚事务 1 2 3 4 5 @Transational(rollbackFor=RuntimeException.class) public class transferService { ... }
异常不回滚事务 1 2 3 4 5 @Transational(noRollbackFor=RuntimeException.class) public class transferService { ... }