什么是IOC?
- 控制反转:IOC——Inversion of Control,翻转资源获取方向。之前需要我们手动创建对象才能使用,现在把创建对象的权利交给Spring的IOC容器,由它来管理对象的生命。我们只需要从IOC容器中获取对象使用即可。
- Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,加入IOC容器。
- 获取bean一般通过bean类型获取,当然也可以通过bean的id获取。
什么是DI?
- DI:Dependency Injection,翻译过来是依赖注入。IOC容器通调用新组件的setter等方法来给新组件注入 一些资源,比如依赖对象等。
Bean的作用域?
- 在应用作用域下:通过@Scope注解配置bean的作用域;singleton表示bean是单例,每次获取都是同一个;prototype表示bean是多例,每次获取都会创建一个新的。
- 此外还有session作用域,在一个会话范围内有效;request作用域,在一个请求范围内有效。
Bean的生命周期?
- bean对象创建(调用无参构造器)
- 给bean对象设置属性,依赖注入
- bean对象初始化之前操作(由bean的后置处理器负责)
- bean对象初始化,调用指定init方法(需在配置bean时指定初始化方法)
- bean对象初始化之后操作(由bean的后置处理器负责)
- bean对象就绪可以使用
- bean对象销毁(需在配置bean时指定销毁方法)
- IOC容器关闭
Bean基于注解的自动装配?
- 在成员变量上直接标记@Autowired注解即可完成自动装配。
- @Autowired注解也可以标记在构造器和set方法上
- @Autowired工作流程
- 先通过byType,再通过byName的方式查找装配,都找不着就抛异常
- 发现NoSuchBeanDefinitionException, 没有匹配的bean错误。直接看对应了类是否加上了注解。如加上了注解,再看是不是注解扫描是不是没有扫描上。
Spring如何配置包扫描路径?
-
使用xml配置
<context:component-scan base-package="com.atguigu"> </context:component-scan>
-
使用@ComponentScan()注解标识扫描路径
-
SpringBoot项目会自动扫描SpringApplication启动类的同级目录下及其递归目录里的所有组件。
总结Spring的最大特性之一就是:注解+扫描+自动装配+IOC自动管理bean
什么是AOP?
- 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功能。常用于抽取重复使用的代码注入原代码,增强原代码功能而不改变原代码。常用于日志等操作。
为什么需要AOP?
- 简化代码:我们希望只把核心业务功能代码留在方法内部,而将日志等这些重复的代码抽取出来,实现业务代码与日志代码的解耦。通过继承的方式不能解决这个问题。AOP基于代理模式实现。
- 影响隔离:将日志记录,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离独立出来,我们修改这些行为的代码的时候不影响业务逻辑的代码。
- 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
什么是代理模式?
- 所谓代理模式就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。代理类在调用目标方法之前、之后都可以做很多事情,来增强目标方法的功能。
- 这样,目标方法就可以专注于自己的核心业务功能代码,而日志这些代码就可以通过代理类附加到目标方法之前、之后。 实现 非目标方法的核心代码 从 目标方法 中剥离出来——即解耦。
- 静态代理:代理哪个对象已经写死了,代理对象使用的代理方法也写死了。需要为每一个被代理的对象都创建一个代理类。
- 动态代理:代理哪个对象可以动态改变,代理类可以动态生成,不需要开发⼈员⾃⼰实现代理类,可以通过反射生成动态代理类的实例。
这样,每个委托类都可以动态生成代理类的实例,而不用修改动态代理类的代码。
什么是动态代理?
-
静态代理:由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
-
动态代理:动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。
-
jdk动态代理:要求必须有接口,最终生成的代理类和目标类实现相同的接口
-
cglib动态代理:最终生成的代理类会继承目标类,并且和目标类在相同的包下
AOP的相关概念
- 横切关注点:插入新代码的点
- 通知:每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。包含以下通知:
- 前置通知:在被代理的目标方法前执行
- 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
- 异常通知:在被代理的目标方法异常结束后执行(死于非命)
- 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
- 切面:封装通知方法的类。(AOP最重要的地方)
AOP相关注解有什么?
- @Aspect:作用是把当前类标识为一个切面类
- @Pointcut:切入点定义表达式。比如@Pointcut(“@annotation(com.yuming.blog.annotation.OptLog)”)定义切入点为使用自定义注解@OptLog的地方。而@Pointcut修饰的方法可以当做是切入点表达式的别名,该方法体内为空即可。为了方便重用切入点表达式,因为切入点表达式一般很长
- @AfterReturning:返回通知,方法正常退出时执行
- @After: 后置通知,不管是抛出异常或者正常退出都会执行
- @Before:标识一个前置通知方法,在被代理的目标方法前执行
- @AfterThrowing:异常抛出通知,在被代理的目标方法异常结束后执行
- @Around:环绕通知,使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
- 执行顺序: 前置通知 --》目标操作 -》返回通知/异常通知 -》后置通知
切入方法可以获取目标方法的哪些信息?
通过切入点获取:JoinPoint joinPoint,可获取的信息有:
- 连接点的方法名,String methodName = joinPoint.getSignature().getName();
- 连接点方法的参数列表,String args = Arrays.toString(joinPoint.getArgs());
- 连接点方法的返回值,returning = “result”,放在通知方法的参数列表,如:public void afterReturningMethod(JoinPoint joinPoint, Object result)
- 连接点方法的异常,throwing = “ex”,放在通知方法的参数列表。
环绕通知与其他通知有什么不同?
-
环绕通知需要通过切入点ProceedingJoinPoint手动调用目标方式执行,类似于动态代理。
-
@Around(value = "optLogPointCut()") public Object aroundMethod(ProceedingJoinPoint joinPoint){ String methodName = joinPoint.getSignature().getName(); String args = Arrays.toString(joinPoint.getArgs()); 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; }
目标方法存在多个切面,执行顺序是怎么样的?
- 使用@Order注解可以控制切面的优先级:@Order(较小的数):优先级高;@Order注解放在切面类头上。也就是跟@Aspect放一块。
- 先执行优先级高的切面(外层),再执行优先级低的切面(内层)。
什么是编程式事务、声明式事务?
- 编程式事务: 事务功能的相关操作比如连接数据库、开启事务、sql操作、提交事务、异常回滚事务、释放数据库连接 都需要我们手动实现,每次都要写一遍,很繁琐。
- 声明式事务: 把连接数据库、开启事务、提交事务、异常回滚事务、释放数据库连接这部分重复的代码抽取出来,通过AOP实现事务操作。只要使用了@Transactional注解,就只需要把核心sql操作放在方法里即可,不用再考虑手动实现事务操作。
@Transactional使用有什么要注意的?
- @Transactional标识在方法上,则只会影响该方法;@Transactional标识的类上,则会影响类中所有的方法
- @Transactional(readOnly = true) 把事务设置成只读,可以提高查询的效率。前提是事务里确实只有查询操作。(修改操作会报错)
- @Transactional(timeout = 3) 设置事务最长执行时间。超过该时间就回滚。防止长时间占用数据库连接资源。
- 事务的回滚策略:声明式事务默认只针对运行时异常回滚,可以通过@Transactional(RollbackFor = ArithmeticException.class) 设置特定的异常才回滚,这里设置出现了数学运算异常才回滚。
Spring的事务隔离级别有哪几种?
隔离级别一共有四种:
-
读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
存在脏读、不可重复读、幻读问题 -
读已提交:READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。
存在不可重复读、幻读问题 -
可重复读:REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
存在幻读问题 -
串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
脏读、不可重复读、幻读问题都解决了
mysql中 可重复读:REPEATABLE READ 时采取了一些特殊机制,不会出现幻读。
一般情况下,只读,超时,回滚策略,事务隔离级别,事务传播行为 五个属性都不用配置,用默认值就行。直接在事务上面配置注解@Transaction就行。
怎么指定Spring的事务隔离级别?
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别(不指定也是默认)
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
什么是事务传播行为?
- 其实就是事务的嵌套调用,在一个事务A方法里 调用事务B、C、D的方法,该怎么处理?B,C,D是在A事务上执行,还是创建一个新事务?出错了又怎么回滚?
- 默认的事务传播行为是@Transactional(propagation = Propagation.REQUIRED),表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。则在A事务中若有某一件事务出错了,则整个事务A都回滚。
- @Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。回滚只回滚当前出错的事务。
7种事务传播行为?
1、@Transactional(propagation=Propagation.REQUIRED)
如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
2、@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不为这个方法开启事务
3、@Transactional(propagation=Propagation.REQUIRES_NEW)
重新创建一个新的事务,如果当前存在事务,延缓当前的事务。这个延缓,或者说挂起
4、@Transactional(propagation=Propagation.MANDATORY)
必须在一个已有的事务中执行,否则抛出异常
5、@Transactional(propagation=Propagation.NEVER)
必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
6、@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
7、@Transactional(propagation=Propagation.NESTED)
如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。