文章目录
摘要
摘要:IoC;AOP;自动配置;起步依赖;Spring Bean循环依赖
说明:IoC、DI均为软件工程中思想,致力提高“高内聚,低耦合”的软件设计思路。
1 IoC(Inverse Of Controller)
什么是IOC?(Inverse Of Controller)
控制反转
,把对象的创建权(new对象), 反转为Spring容器管理
,IOC创建的对象称之为Bean对象
。
1.1 声明bean的注解?(即把对象放入容器中的注解)
注解 | 说明 |
---|---|
@Component | 不属于以上三类时,用此注解 |
@Controller | 标注在控制层类上 |
@Service | 标注在业务类上 |
@Repository | 标注在数据访问层类上 |
2 DI(Dependency Injection)
什么是DI?(Dependency Injection)
依赖注入
,给容器中bean的属性赋值,常用使用方式,使用@Autowired注解声明在需要注入的类上
,此时就会去spring容器中
寻找Bean对象
。
2.1 DI注解类型
注解 | 说明 | 指定属性值 |
---|---|---|
@Autowired | 按照类型注入 | ① @Autowired(required=true):注入时,该bean必须存在,否则就注入失败,默认就是; ② @Autowired(required=false):注入bean时,若有直接注入,没有则跳过,不会报错。 |
@Qualifier | 按照名称注入 要和@Autowired搭配使用 | – |
@Resource | 按照名称注入 = @Autowired + @Qualifier | – |
2.2 DI注解类型方法示例
2.2.1 @Autowired
@Autowired
private Date date ;
2.2.2 @Qualifier
@Autowired
@Qualifier("birth")
private Date birthday ;
2.2.3 @Resource
@Resource(name = “apple”)
3 AOP(面向切面编程)
3.1 什么是AOP?
面向切面编程
3.2 AOP的作用?
在不改变原始代码的基础上进行功能增强
3.3 AOP的核心概念
名词 | 解释 |
---|---|
连接点 | 所有可以进行功能增强的方法都是连接点 |
切入点 | 进行了功能增强的方法就是切入点 |
通知 | 共性功能 |
切面 | 切入点 + 通知 |
目标对象 | 通知所应用的对象就是目标对 |
切入点表达式 | 指定哪些方法是切入点 |
3.4 AOP常见通知类型
通知类型 | 解释 | 注意事项 |
---|---|---|
@Before | 前置通知 | |
@After | 后置通知 | |
@Around | 环绕通知 | ① @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑; ② @Around环绕通知方法返回值,必须指定为Object ,来接收原始方法的返回值 ,否则原始方法执行完毕,获取不到返回值。 |
@AfterReturning | 返回后通知 | |
@AfterThrowing | 异常后通知 |
3.5 切入点表达式
写法一:
@PointCut("execution(访问修饰符 返回值 包名.类名.方法名(参数))")
- 通配符:
*
单个独立的任意符号,通配任意返回值、包名、类名、方法名、任意类型的参数,也通配包、类、方法名的一部分
..
多个连续任意符号,用于参数省略或包名省略
写法二:
@PointCut("@annotation(注解的全路径类名)")
- 通过连接点对象获取目标方法的信息
获取类名:joinPoint.getTarget().getClass().getName()- 获取方法名:joinPoint.getSignature().getName()
- 获取参数:joinPoint.getArgs()
- 执行目标方法:joinPoint.proceed()
- 获取返回值:Object result = joinPoint.proceed()
切入点表达式的语法规则:
- 方法的访问修饰符可以省略
- 返回值用
*
号代替(任意返回值类型)- 包名使用
*
号代替,代表任意包(一层包使用一个*
)- 使用
..
配置包名,标识此包及此包下所有子包- 类名使用
*
号代替,标识任意类- 方法名使用
*
号代替,表示任意方法- 使用
*
配置参数,一个任意类型的参数- 使用
..
配置参数,任意个任意类型的参数
3.5.1 切入点表达式练习一
注意事项:
- 根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式
execution(* com.sesameseed.service.DeptService.list(..)) || execution(* com.sesameseed.service.DeptService.delete(..))
解释:切入点为DeptService接口(全路径)下,参数为任意类型和个数,且返回值为任意的list方法,或DeptService接口下,参数为任意类型和个数,且返回值为任意的delete方法。
*com | * 任意返回值. |
(. .) | 表示任意类型和个数的参数 |
.DeptService.list(. .) | DeptService接口下,参数为任意类型和个数的list方法表示 |
3.5.2 切入点表达式练习二
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
解释:切入点为mapper包下,任意包名,任意方法,且被@AutoFill注解声明。
3.5.3 抽取冗余切入点表达式
引入,对于下方代码来说,每一个注解里面都用到相同切入点表达式,所以我们可以把它抽取出来。
//前置通知
@Before("execution(* com.itheima.service.*.*(..))")
//环绕通知
@Around("execution(* com.itheima.service.*.*(..))")
//后置通知
@After("execution(* com.itheima.service.*.*(..))")
//返回后通知(程序在正常执行的情况下,会执行的后置通知)
@AfterReturning("execution(* com.itheima.service.*.*(..))")
//异常通知(程序在出现异常的情况下,执行的后置通知)
@AfterThrowing("execution(* com.itheima.service.*.*(..))")
3.5.4 抽取冗余切入点表达式示例
注解 | 说明 |
---|---|
@PointCut | 将公共的切入点表达式抽取出来 |
@Slf4j
@Component
@Aspect
public class MyAspect1 {
//切入点方法(公共的切入点表达式)
@Pointcut("execution(* com.itheima.service.*.*(..))")
private void pt(){
}
//前置通知(引用切入点)
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info("before ...");
}
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
//原始方法在执行时:发生异常
//后续代码不在执行
log.info("around after ...");
return result;
}
注意:在当前类外部引切入点表达式,需要把private改为public,语法为全类名.方法名(),示例如下:
public class MyAspect2 { //引用MyAspect1切面类中的切入点表达式 @Before("com.itheima.aspect.MyAspect1.pt()") public void before(){ log.info("MyAspect2 -> before ..."); } }
3.6 连接点
连接点:可以被AOP控制的方法
在Spring中
JoinPoint
抽象连接点,可以获得方法执行时相关信息,如目标类名、方法名、方法参数等。
通知类型 | 获取连接点信息方法 |
---|---|
@Around | ProceedingJoinPoint类型 |
其他四种通知 | JoinPoint类型 |
3.6.1 代码示例
@Pointcut("@annotation(com.itheima.anno.MyLog)")
private void pt(){}
//前置通知
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");
}
//后置通知
@Before("pt()")
public void after(JoinPoint joinPoint){
log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");
}
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//获取目标类名
String name = pjp.getTarget().getClass().getName();
log.info("目标类名:{}",name);
//目标方法名
String methodName = pjp.getSignature().getName();
log.info("目标方法名:{}",methodName);
//获取方法执行时需要的参数
Object[] args = pjp.getArgs();
log.info("目标方法参数:{}", Arrays.toString(args));
//执行原始方法
Object returnValue = pjp.proceed();
return returnValue;
}
4 自动配置
4.1 简述:SpringBoot自动配置的原理
- ① 在Springboot启动的时候,会用到
核心注解@SpringBootApplication
。- ② 这个注解中有一个
自动配置注解@EnableAutoConfiguration
和@SpringBootConfiguration
,其中@SpringBootConfiguration
可以让我们在启动类上跟@Bean
完成第三方jar包的自动装配。- ③ 其次,自动配置注解会通过@Import注解导入AutoConfigurationImportSelector,这个类实现了一个导入器接口ImportSelector,自动寻找
META-INF/spring.factories 和 AutoConfiguration.imports
文件中的xxxAutoConfiguration自动配置类,在自动配置中使用@Bean会根据条件注解所指定的条件,把一些配置类和bean对象放到了Spring容器中,就完成了自动配置
- 自动配置注解:@EnableAutoConfiguration
- 自动配置文件:spring.factories 和 AutoConfiguration.imports 文件
- 自动配置类:xxxAutoConfiguration(@Bean).
注解 | 说明 |
---|---|
@Bean | 告诉方法,产生一个Bean对象并交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后被放在Spring容器中。 |
4.2 源码跟踪
spring.factories
Spring Boot 中的SPI 机制(Java SPI 机制):为某个接口寻找服务的实现的机制,类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制很重要。
- 当需要实现SDK 或Sring boot starter 给别人调用,使用
Factories机制(Spring Boot 中的SPI 机制)
可以让SDK或Stater的使用只需很少或不需要进行配置,只需在服务中引入我们的Jar包就即可。
5 起步依赖
springboot的起步依赖主要依靠
maven的继承
与版本控制
,从而完成起步依赖。
6 Spring Bean循环依赖
6.1 什么是Spring Bean循环依赖
Spring Bean循环依赖:值在Bean间存在相互依赖的情况,互相依赖的Bean无法实例化。
- 举例:在A类中声明了成员变量B,B中也声明的成员变量A,即双方相互依赖,若在初始化Bean时不加限制的注入会导致循环依赖问题。
6.2 如何解决Spring Bean循环依赖
6.2.1 三级缓存
三级缓存
:即提前暴露出来的单例对象缓存,实现Bean的提前曝光,Spring会首先实例化Bean。但是Bean中的依赖项只能通过代理方式设置,这样Spring可以在对象创建前就处理好依赖关系,若Bean中属性正在被注入,Spring容器会提供一个**代理对象**,以处理互相依赖的管理
,即若存在Bean的依赖关系,spring会在添加代理之后,提前将一个缓存对象返回并注册
,这样Bean间就可以保持互相依赖关系
。