1.1概述及IOC概念
-
spring
- 开源的免费的框架
- 轻量级的、非侵入式的框架
- 提供了控制反转、面向切面编程的功能
- 支持事务处理、支持大多数框架的整合
- 配置十分繁琐
-
spring七大模块
-
图例
-
详解
- 核心容器(Spring Core):核心容器提供Spring框架的基本功能即依赖注入
- 应用上下文(Spring Context):一个配置文件,用于向Spring框架提供上下文信息,包括国际化、邮件处理等企业级功能
- 面向切面编程(Spring AOP):提供面向切面编程的功能
- JDBC和DAO模块(Spring DAO):提供了模块化编写DAO层代码的功能
- 对象实体映射(Spring ORM):提供了整合ORM框架实现对象实体相互映射的功能
- Web模块(Spring Web):提供了基于应用上下文的web上下文
- MVC模块(Spring Web MVC):提供了基于Spring实现的MVC框架
-
-
IOC相关概念
-
控制反转(IOC),主动创建依赖对象的控制权被反转了,依赖对象不再主动创建,从而降低程序耦合度,增强代码复用性
-
依赖注入(DI),控制反转的一种实现方式,控制权被交于IOC容器,依赖的对象将会被IOC容器以注入的方式添加到需要的地方,一般spring可以通过属性、构造方法或工厂方法的参数来确定依赖
-
set注入:通过set方法的参数来确定依赖,基于set方法,优点是按需注入,可以解决循环依赖问题,但对应属性不能以final修饰,例子如下
@Component public class FirstBean { private SecondBean sb; public SecondBean getSb() { return sb; } @Autowired public void setSb(SecondBean sb) { this.sb = sb; } } -
构造器注入:通过构造方法的参数确定依赖,基于构造方法,优点是减少了对spring的依赖,确保了注入的对象不为空且完成了初始化,同时对于属性会被加上final修饰,并且出现循环依赖时还会抛异常,但构造器的参数可能过长,例子如下
@Component public class FirstBean { private SecondBean sb; @Autowired public FirstBean(SecondBean sb) { this.sb = sb; } public SecondBean getSb() { return sb; } public void setSb(SecondBean sb) { this.sb = sb; } } -
field注入(不推荐):通过属性来确定依赖,基于反射,优点是简洁明了,缺点是对spring依赖过高,无法解决循环依赖也不会抛异常,并且对应属性也不能用final修饰,例子如下
@Component public class FirstBean { @Autowired private SecondBean sb; public SecondBean getSb() { return sb; } public void setSb(SecondBean sb) { this.sb = sb; } }
-
-
IOC容器,用于管理bean并提供依赖注入的功能,容器会通过读取configuration元数据获取有关objects实例化、配置和汇编的指令,spring中主要提供了具备完整依赖注入功能的BeanFactory与BeanFactory的超集ApplicationContext这两种容器,相较而言,ApplicationContext还会提供一些类似于国际化等的企业级功能,并且其单例bean默认非懒加载
-
Configuration元数据:是对objects创建、管理的一些描述,如该object是否为bean、是singleton还prototype、其属性是否需要注入,应该注入何种对象等,元数据可以为XML、Java注解、Java代码
- XML,不安全、查找类不方便、可读性差、配置不简洁、修改配置不用重编译、非侵入、自由度低、可注入非自己创建的类
- Java注解,安全、方便、可读性很好、配置十分简洁、修改配置要重编译、侵入、自由度高、不可注入非自己创建的类
- Java配置,安全、不方便、可读性一般、配置比较简洁、修改配置要重编译、非侵入、自由度非常高、可以注入非自己创建的类
-
使用实例
//bean类 @Component @Data public class FirstBean { private String name; } //Configuration类 @Configuration @ComponentScan("org.example.bean") public class SpringConfiguration { } //启动类 public class MyTest { public static void main(String[] args) { ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class); FirstBean fb=ac.getBean(FirstBean.class); System.out.println(fb.toString()); } }
-
-
bean,是对象,IOC容器基于我们提供给它的configuration来实例化、组装和管理这些对象,常见的元数据及对应信息如下
-
beanDefinition:一个用于存储bean相关信息的对象,如bean的类名、scope、属性、构造函数的,Spring容器启动时会通过beanDefinitionReader去获取各个bean相关属性和配置信息并将它们存放在对应的beanDefinition中,然后将这些beanDefinition存放在map中,之后根据beanDefinition中的类名、构造函数、构造函数参数,使用反射就可以创建对应的bean了,非懒加载的bean会在此时直接创建并存放在另一个map便于之后直接获取使用
-
FactoryBean:工程bean,也由Spring容器管理,可以生产和修饰一些bean,若对AOP中对应类加以修饰形成所需的bean
-
bean生命周期图
-
-
Spring循环依赖解决方式
- 前提:
- Spring实例化bean会分三步,分别为调用构造方法实例化,通过set方法填充属性,通过init方法对bean初始化
- bean存放于三级缓存中,一级缓存,用于存放完全初始化好的bean,二级缓存尚未填充属性的bean,三级存放正在实例化的bean,Spring会从一级到三级去尝试获取到对应bean
- 循环依赖解决过程如下
- A创建过程中需要 B,于是A将自己放到三级缓里面,去实例化B
- B实例化的时候发现需要A,于是B先查一级缓存,没有,再查二级缓存,还是没有,再查三级缓存,找到了,则将其作为自己的注入属性
- 然后把三级缓存里面的这个 A 放到二级缓存里面,并删除三级缓存里面的 A
- B 顺利初始化完毕,将自己放到一级缓存里面,此时B里面的A依然是创建中状态
- 然后回来接着创建A,此时B已经创建结束,直接从一级缓存里面拿到 B ,然后完成创建,并将自己放到一级缓存里面
- 前提:
-
自动装配常见值及解释
- no,默认值,即不进行自动装配,需要通过注解如@Autowire来主动指明该属性、方法需要主动装配
- byName,按照beanName进行自动装配,使用setter注入
- byType,按照beanClass进行自动装配,使用setter注入
- constructor,类似于byType,不过通过构造器注入
-
1.2IOC中常用注解
-
声明bean的相关注解
-
作用于类上的注解
- @Component,通用注解,当不知道这个类属于哪一层的时候可以通过该注解来标明为bean,这些bean的默认名称为第一个字母小写的类名,类型就是注解作用类的类型
- @Controller,组合了@Component的注解,用于声明控制层的类为bean
- @RestController,组合了@Controller和@ResponseBody的注解,同样用于声明控制层的类为bean,并指明该类返回的是数据而非视图,数据默认是json格式
- @Service,组合了@Component的注解,用于声明服务层的类为bean
- @Repository,组合里@Component的注解,用于声明数据访问层的类为bean
-
作用域方法上的注解
-
@Bean,用于指明对应方法的返回值为一个bean,需要配合@Configuration使用,该bean的名称默认为方法的名称,类型为方法的返回类型,其详尽信息如下
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Bean { @AliasFor("name") String[] value() default {}; //用于指明别名 @AliasFor("value") String[] name() default {}; /** @deprecated */ @Deprecated Autowire autowire() default Autowire.NO; //默认不启用自动装配 boolean autowireCandidate() default true; //默认开启自动条件匹配 String initMethod() default ""; //用于指明初始化方法 String destroyMethod() default "(inferred)"; //用于指明消除方法 }
-
-
-
修饰bean的相关注解
-
@Scope,用于指明bean的作用域,常见作用域如下
- singleton,默认值,单例,全局有且仅有一个实例
- prototype,原型,每次获取Bean的时候都会创建一个新的实例
- request,针对每一次http请求都会产生一个新的bean,同时该bean只在此次http请求内有效
- session,针对每一次无bean的http请求都会产生一个新的bean,同时该bean只在此个session中有效
事实上90%以上的业务使用singleton即可,这也就是为什么Spring用singleton作为默认值,不过由于singleton保证了全局只有一个实例,使得如果实例中有非静态变量时,会因共享资源的竞争而导致线程安全问题,当然因为是单例所以不用频繁GC,同时也会节省内存,使得总体性能会有所提升,@Scope实现如下
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Scope { @AliasFor("scopeName") String value() default ""; @AliasFor("value") String scopeName() default ""; ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT; } -
@Primary,指明该bean为首选bean,当有多个符合要求的bean时会首先选择该个bean
-
@Qualify,可在注解中写入名称来详细指明注入的bean,如@Qualify("firstBean")就是指明注入一个名称为firstBean的bean
-
@Profile,组合了@Condition的注解,指明bean的方法或bean在什么情况下起作用,如@Profile("test")需要在spring.profiles.active = test时才有效,对于Springboot可以直接在application.profiles中设置,也可以通过容器的getEnvironment().setActiveProfiles("test")方法设置
-
-
配置类的相关注解
- @Configuration,指明该类为bean并将其设置为配置类,配置类中可以有多个返回bean的方法,这些方法需要用@Bean注释
- @Import,用于导入其他多个配置类到一个配置类中,如@Import({Config.clsss,BasicConfig.clss})就是将Config和BasicConfig两个配置类导入到当前配置类中
- @PropertyResource,用于导入相应的资源文件,可以配合@Value来获取对应的资源信息,其属性为文件的路径,需要加上classpath:来指明是类路径,如@PropertyResource("classpath:test.properties"),在maven项目中就是导入resources目录下的test.properties文件
- @ComponentScan,用于指明扫描的范围,地址是以类路径起始的,如@ComponentScan("basic")就是在maven项目中扫描Java包下的basic包中的所有文件,默认扫描该类所在包及其子包中的所有类
-
注入bean和属性的相关注解
-
@Autowired,用于装配bean,默认按类型装配,详情如下
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Autowired { boolean required() default true; //是否必要,即是否允许为null,默认不允许 } -
@Resource,JDK1.6支持的注解,默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名为名称来按照名称查找,如果注解写在setter方法上默认取属性名作为名称来查找装配,当找不到与名称匹配的bean时才按照类型进行装配,不过只要指定了name属性则只会按照名称进行装配
-
@Value,用于注入资源信息,如MySQL相关配置等,在使用前需要通过@Properties先导入对应的资源文件,使用格式为@Value("${properties文件中对应key值}"),通过它注入属性不需要set方法
-
1.3AOP概念
-
代理模式视图如下
-
静态代理:在编译期完成,完成后代理类会是一个具体的类,静态代理只能为一个目标对象服务,不过效率高,示例如下
//接口 public interface Fly(){ void fly(); } //具体类、被代理类 public class Bird implements Fly(){ public void fly(){ System.out.println("the bird is flying"); } } //代理类 public class BirdProxy implements Fly(){ private Bird bird; //需要构造注入一个具体类 public void fly(){ System.out.println("start--------"); bird.fly(); System.out.println("end----------"); } } //测试 public class Test(){ public static void main(String[] args){ Fly fly=new BirdProxy(new Bird()); fly.fly(); } }//结果 start-------- the bird is flying end---------- -
动态代理:在运行时通过反射动态生成对应的代理类,编译时并不存在实际的代理类,动态代理可以为多个目标对象服务,比较灵活,但基于反射实现的动态代理效率较低
- JDK:基于反射实现,代理对象必须实现了一个或多个接口
- cglib:基于字节码处理框架ASM实现,代理对象不必实现一个或多个接口,它可以在运行期间扩展Java类或实现Java接口,且代理类无侵入
-
AOP:Spring AOP是综合了JDK和cglib两种技术实现的动态代理,性能很高且适用范围也很广,Spring AOP的作用为抽离与业务无关的系统代码并整合在一起统一管理,大大降低了整个软件的耦合度、繁杂度及代码量,使得程序员能更高效的实现业务并为代码迭代升级
- 相关概念
- 横切关注点:跨越应用程序多个模块的、与业务无关的方法或模块,如日志、安全、缓存、事务等
- 切面:整合后的、模块化的横切关注点对象,为一个类
- 通知:切面中进行的额外操作,如记录日志、权限检查等
- 目标:被通知或者说是被代理的对象
- 代理:向目标对象注入通知后得到的代理对象
- 切入点与连接点:前者为切面中的对应方法,后者为目标中的对应方法
- 代理过程:
- 注册自动代理器,Spring加载自动代理器AnnotationAwareAspectJAutoProxyCreator作为一个系统组件
- 构建代理工厂,当一个bean加载到Spring中时,就会触发自动代理器中的bean后置处理,bean后置处理时会先扫描bean中所有的Advisor,然后用这些Adviosr和其他参数构建ProxyFactory
- 创建代理对象,当某个bean对象与切面规则匹配时,ProxyFactory便会创建出对应的代理bean并将其放入context中,之后调用其代理的方法或将这个代理bean注入到对应的属性中
- 实现细节
- 默认情况下Spring是不会加载自动代理器的,需要在配置类上添加@EnableAspectJAutoProxy来让Spring去加载自动代理器
- 当Spring AOP基于注解配置时,会使用到AspectJ包的标准注解,因此需要在maven中添加对AspectJ的依赖来引入AspectJ的标准注解
- Advisor中包含了通知方法(Advice)和切面规则(PointCut)
- Spring AOP会根据目标是否实现了接口来选择使用JDK还是cglib来实现动态代理
- 注意事项
- 由于使用动态代理,所以final、static、private、构造器等方法是无法使用AOP的,并且如果类是final的还会触发异常而无法启动Spring
- 自调用不会被拦截,源于自调用会直接转换成this.对应方法名调用,所以会直接调用自己的方法,不会调用代理的方法,因此表现为没有被拦截的现象,推荐不要出现自调用
- 通过FactoryBean得到的bean也是可以被aop代理的
- 正常情况下切面相关方法执行顺序为Around、Before、Around、After、AfterReturning
- 有异常抛出时切面相关方法执行顺序为Around、Before、Around、After、AfterThrowing
- 有多个切面拦截同一个方法时,多个切面执行的先后顺序是随机的,可以添加@Order(int)注解,其值越小则优先级越高
- 切面类及被代理类都必须是bean才能使用spring AOP
- 相关概念
1.4AOP相关注解
-
注明切面的相关注解
- @Aspect,此为AspectJ的注解,并非spring原生的,所以也就没有组合@Component注解,因此标明切面类时还需要为切面类添加额外的@Component注解指明该切面类为bean,@Before、@After、@PointCut等注解只能在切面类的方法上使用才能起作用
-
注明切点的相关注解
-
@PointCut,同样为Aspect的注解,用于标明切点,方便之后复用,具体情况如下
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Pointcut { String value() default ""; //用于指明切点位置、参数等 String argNames() default ""; //用于指明切点方法参数 }其详尽使用实例如下
//execution指明了切断位置,args指明了参数,注意args的参数名必须与方法中的参数名相同 @Pointcut("execution(public * org.example.bean.SecondBean.printTest(..))&&args(i)") public void basic(int i){ } //指明了切点,参数名可与原切点不同,但注解中的参数名必须与方法中的参数名相同 @Before("basic(j)") public void beforeExecution(JoinPoint i,int j){ System.out.println("before"); }-
execution详解
-
简介:用于指明切点,格式为execution(访问修饰符 返回值 包名.类名.方法名(参数)),访问修饰符可省
-
特殊符号
- *,可以表示任意方法、任意访问修饰符等
- ..,表示此包及其所有子包或任意参数
-
示例
//拦截org.example包下类名为bean的、公共的、返回值为空的,拥有一个int参数的test方法 execution(public void org.example.bean.test(int)) //拦截org.example包下以Imp结尾的类的公共方法,不包含子包 execution(public * org.example.*Imp.*(..)) //拦截org.example包及其子包下的任意类的任意方法 execution(* org.example..*.*(..)) //拦截所有公共方法 execution(* *(..))
-
-
args详解
- 简介:用于指明参数,不用写类型,但其方法上要写,格式为args(参数名1,参数名2,....),当配合execution()使用是两者之间要加&&来连接
- 注意点
- 注解中的参数名和方法中的参数名必须相同
- 每一个通知方法都会默认被传入一个JoinPoint或ProceedingJoinPoint对象,这些参数不用显示指明,但必须位于方法参数的第一位
-
@target详解
- 简介:用于增加匹配规则,只有对应类有指定注解时才会被拦截
- 注意点
- 同样需要用&&来连接
- 其值应是一个全注解名
-
@annotation,类似于@target,只有对应方法有指定注解时才会被拦截
-
JoinPoint详解
-
简介:一个包含了拦截方法相关信息的对象,而ProceedingsJoinPoint会比该类多一个proceed()方法用于放行,会在环绕通知时替代JoinPoint作为默认参数传入对应方法
-
常见方法
-
getSignature(),用于获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
-
getArgs(),用于获取包含了目标方法参数的Object数组
-
getTarget(),用于获取被代理对象,为Object类型
-
getThis(),用于获取代理对象,也是Object类型
-
proceed(),执行被拦截的方法,如果有参数应记得传入其中
-
示例如下
@Before("execution(public * org.example.bean.SecondBean.printTest(..))&&args(i)") public void beforeExecution(JoinPoint j, int i){ System.out.println(j.getSignature().getName()); System.out.println(j.getArgs()[0]); System.out.println(((SecondBean) j.getTarget()).getFb().getI()); }
-
-
-
-
-
注明通知的相关注解
-
@Before、@After
-
简介:用于定义前置通知和后置通知,必定会被执行
-
属性:其value可以是一个切点配置("execution()..."),也可也是一个切点方法,后一个argsName用于指明参数,详细如下
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Before { String value(); String argNames() default ""; //不常用,一般使用args()来指明参数 }
-
-
@Around
-
简介:用于定义环绕通知即被拦截方法执行前后需要再执行哪些操作,实例如下
@Around("execution(public * org.example.bean.SecondBean.printTest(..))&&args(i)") public void beforeExecution(ProceedingJoinPoint j, int i){ System.out.println("around before"); try { j.proceed(); //用于放行或者说执行被拦截方法 } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("around after"); }
-
-
@AfterReturning
-
简介:用于定义返回通知,方法正确返回后才会执行,多了一个返回值属性,其他类似于@After
-
属性:returning用于指明返回值名,不用指明类型,不过注解的返回值名必须与方法的返回值名相同,其他类型与@After,示例如下
@AfterReturning(value = "execution(public * org.example.bean.SecondBean.printTest(..))&&args(i)",returning = "k") public void beforeExecution(JoinPoint j, int i,int k){ System.out.println(k); //输出返回值 }
-
-
@AfterThrowing
- 简介:用于定义抛出异常时的通知,方法在抛出异常后才会执行,多了一个抛出异常属性,其他类似于@After
- 属性:throwing用于指明抛出异常名,不用指明类型,不过注解的抛出异常名必须与方法的抛出异常名相同
-
1.5事务
-
spring事务管理的三个接口
-
PlatformTransactionManager,事务管理器,提供了提交事务、回滚事务、获取事务三个方法,该接口针对不同的平台如Hibernate与mybatis会有不同的修改,最终的实现还是交给的各个平台,可以看出Spring在事务管理中只起一个辅助作用,真正进行事务管理还是各个平台
-
TransactionDefinition,事务定义,事务管理器会基于事务定义来获取事务,事务定义包括隔离级别、传播行为、回滚规则、是否只读、事务超时这五个方面的信息,详细如下
- 隔离级别
- TransactionDefinition.ISOLATION_DEFAULT,采用后端数据库的默认隔离级别
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED,不提交读
- TransactionDefinition.ISOLATION_READ_COMMITTED,提交读
- TransactionDefinition.ISOLATION_REPEATABLE_READ,可重复读
- TransactionDefinition.ISOLATION_SERIALIZABLE,序列化
- 传播行为,当一个事务方法被另一个事务方法调用时,事务该如何传播
- 支持当前事务
- TransactionDefinition.PROPAGATION_REQUIRED,如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务的方式继续运行
- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常
- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行,如果当前没有事务,则创建一个事务,内嵌事务实际上就是一个savepoint,外部事务才是真正的事务,所有内嵌事务不能单独提交,只有外部事务提交时才能提交
- 不支持当前事务
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起
- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常
- 支持当前事务
- 回滚规则:这些规则定义了哪些异常会导致事务回滚而哪些不会,默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚
- 是否只读:指明事务是否会修改资源,如果不会则表明为只读事务从而提高事务性能
- 事务超时:指明一个事务必须在多长时间内完成,否则自动回滚,单位是秒
- 隔离级别
-
TransactionStatus,事务状态,包括事务是否完成、是否为新事务、是否为回滚、是否有恢复点等,用于事务回滚和事务提交,详细如下
public interface TransactionStatus{ boolean isNewTransaction(); // 是否是新的事物 boolean hasSavepoint(); // 是否有恢复点 void setRollbackOnly(); // 设置为只回滚 boolean isRollbackOnly(); // 是否为只回滚 boolean isCompleted; // 是否已完成 }
-
-
声明式事务:spring支持编程式事务和声明式事务,后者更为便捷,基于spring aop实现,以下是基于注解的声明式事务使用方式
-
在maven中引入spring-jdbc依赖,实例如下
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.1.10.RELEASE</version> </dependency> -
在配置类上加@EnableTransactonManagement来开启注解事务
-
创建返回对应事务管理器的被@Bean修饰的方法
//注意这个dataSource是必须的,在此处希望作为bean注入 @Bean public DataSourceTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } -
在要使用事务管理的方法上加上@Transactional标签
-
-
@Transactional
-
详情
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { //指明事务管理器,不指明则spring会自动使用一个transactionManager @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; //事务传播行为,默认是有则加入,没有则创建 Propagation propagation() default Propagation.REQUIRED; //隔离级别,默认是使用数据库的隔离级别 Isolation isolation() default Isolation.DEFAULT; //超时,默认是无超时限制 int timeout() default -1; //是否只读,默认否 boolean readOnly() default false; //回滚异常 Class<? extends Throwable>[] rollbackFor() default {}; String[] rollbackForClassName() default {}; //不回滚异常 Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {}; } -
注意点
- 由于是基于AOP实现的,所有@Transactional一般只在public方法上才起作用,且对自调用无效
- 尽量不要从@Transactional注释的方法内捕获异常,因为spring事务会将异常从方法外抛出
- 默认情况下Spring只会对非受查异常回滚,如空指针异常,受查异常如IO异常时不回滚的
- 当@Transactional注释到类上时,其所有public方法都会被纳入spring事务管理,并使用相同的管理方式
-