🚩Spring
IOC 控制反转 || Bean 的生命周期
使用对象的时候,不再通过 new 产生对象,而是将对象交给外部的IoC容器(ApplicationContext,接口)来管理,这个容器是 map 结构,存在三级缓存。
一、实例化 bean 对象
- 通过 createBeanFactory 创建出一个 bean 工厂 DefaultListableBeanFactory
- 循环创建对象,因为容器中的 bean 默认是单例的,所以优先尝试使用 getBean、doGetBean 获取 bean
- 找不到的话,通过 createBean、doCreateBean 以反射的方式创建对象,一般情况采用的是无参构造方法 clazz.getDeclaredConstructor().newInstance() ,可以灵活访问到私有的属性。
Q:BeanFactory 和 FactoryBean 的区别?
它们都是用来创建对象的,区别在于:
- BeanFactory 由 spring 来管理生命周期,它的实现类包括
ApplicationContext
和XmlBeanFactory
等 - FactoryBean 对象创建过程由用户自己控制,更加灵活。
getObject()
:返回由工厂创建的对象实例。getObjectType()
:返回由工厂创建的对象的类型。isSingleton()
:指示由工厂创建的对象是否是单例。
五个作用域
其中最基础的有下面两种:
- Singleton,这是 Spring 的默认作用域,也就是为每个 IOC 容器创建唯一的一个 Bean 实例。【适合无状态】
- Prototype,针对每个 getBean 请求,容器都会单独创建一个 Bean 实例。【适合有状态】
如果是 Web 容器:
- Request,为每个 HTTP 请求创建单独的 Bean 实例。
- Session,很显然 Bean 实例的作用域是 Session 范围。
- GlobalSession,用于 Portlet 容器,因为每个 Portlet 有单独的 Session,GlobalSession 提供一个全局性的 HTTP Session。
二、属性赋值(Population)
依赖注入(Dependency Injection)或属性赋值 populateBean()
- setter注入(建议):更加灵活,可以在对象创建以后动态更改依赖关系。set方法上添加@Autowired注解,类定义上添加@Component注解。
- 构造方法注入:一旦创建完成,依赖关系就不能再改变
🌟Q1:循环依赖如何解决?三级缓存 or @lazy
初始化过程中的循环依赖
- 一级 singletonObjects:单例池,Map 结构,存放完整的 bean
- 生成完整对象之后放到一级缓存,删除 二、三级缓存:addSingleton()
- 二级 earlySingletonObjects:Map 结构,存放生命周期未走完的 bean
- 第一次从三级缓存确定是代理对象还是普通对象的时候放入,同时删除三级缓存 getSingleton()
- 三级 singletonFactories:是一个函数式接口,保证在整个容器运行过程中,同名的 bean 只有一个
- createBeanInstance 之后:addSingletonFactory()
- 一级 singletonObjects:单例池,Map 结构,存放完整的 bean
构造方法中的循环依赖:采用 @lazy 进行懒加载
Q2:如果对象要被代理,是否需要优先生成一个普通对象?
需要。在实际调用对象过程中,先判断对象是否需要被代理,如果需要被代理,调用 getEarlyBeanReference() 用代理对象来覆盖普通对象。因此,所有 bean 对象在创建的时候都要优先放到三级缓存中,再根据是否被代理来返回代理对象或普通对象。
三、调用 Aware 相关接口的方法
invokeAwareMethod 进行 BeanName、BeanFactory、BeanClassLoader 的属性设置。
可以拿到容器的其他对象。
四、BeanPostProcessor 前置处理
- ApplicationContextPostProcessor
- ApplicationContext
- Environment
- ResourceLoader
五、执行 init-method 方法
- InitializingBean 的 afterPropertiesSet()
- 自定义的初始化方法
六、BeanPostProcessor 后置处理
AOP 是 IOC 的一个扩展功能,是在 bean 生命周期中的一个扩展点。
Spring 的 AOP 在此处通过 AbstractAutoProxyCreator 实现,注册 Destruction 相关的回调接口
return 一、2.
七、销毁
@PreDestroy 注解
实现 DisposableBean 接口
🌟AOP
Aspect Oriented Programming,面向切面编程
作用:无侵入式增强。
流程
- 创建代理对象(advice、切面、切点)
- 通过 jdk 或 cglib 生成代理对象
- 执行方法调用:找到 DynamicAdvicoredInterceptor 类中的 intercept() 方法,开始执行
- 根据定义好的通知来生成拦截器链
- 利用 CglibMethodInvocation 对象从 -1 的位置开始遍历通知
AOP 基本概念
在 pom.xml 中导入 aspectjweaver ,配置 @EnableAspectJAutoProxy
- 连接点(JoinPoint):方法【可利用的机会】
- 切入点(PointCut):是连接点的子集,要追加功能的方法 【解决了切面编程中的 Where 问题,让程序可以知道哪些机会点可以应用某个切面动作】
- @Pointcut(“execution(void Pointcut() com.dao.xxxDao.update())”) 修饰的方法 pt()
- 通知(Advice):有共性的功能 【明确了切面编程中的 What,也就是做什么;同时通过指定 Before、After 或者 Around,定义了 When,也就是什么时候做。】
- @Before(“pt()”)
- 形参:JoinPoint
- @After(“pt()”)
- @Around (“pt()”) public void around(ProceedingJoinPoint pjp) throws Throwable{ pjp.proceed();} (重点,常用)
- 形参:ProceedingJoinPoint(必须放在参数的第一位)
- 要注意返回值和原始方法一致。
- 如果不执行pjp.proceed(),可以实现对原始方法的隔离(权限校验)
- @Before(“pt()”)
|
|
- AfterReturning(“pt()”) 只有不抛异常才运行
- AfterThrowing(“pt()")只有抛异常才运行
- 切面(Aspect):通知和切入点的联系(多对多)
- 通知类:定义通知的类
- @Aspect
AOP 工作流程
AOP的本质是代理模式
- Spring容器启动
- 读取切面中配置的切入点
- 初始化bean,若类中方法匹配到切入点,则创建代理对象
- 获取bean执行方法
切入点表达式
标准格式:execution(返回值 包.类.接口(推荐)/实现类.方法())
通配符:*
匹配任意符号,..匹配多个连续的任意符号,如:
execution(_ _..Service+.(..))`匹配业务层所有接口
书写技巧:
- 所有类命名需规范,否则以下失效
- 描述切入点通常描述接口,而不描述实现类
- 增删改类使用精准类型加速匹配,查询类使用*通配快速描述
- 方法名以动词进行精准匹配
- 包名书写尽量不使用 .. 匹配,效率太低,常用 * 做单个包描述匹配
- 不使用异常作为匹配规则
静态代理和动态代理
- 静态代理:在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。接口一旦增加方法,目标和代理对象都要进行修改。
- 动态代理:在运行时生成类字节码,并加载到 JVM 中。可以用来包装 RPC 调用、面向切面的编程(AOP)
- JDK :只能代理实现了接口的类,运用反射的原理。Spring AOP将使用JDK动态代理来生成代理对象。代理对象实现了与目标对象相同的接口,并在方法调用前后织入切面逻辑。JDK动态代理是通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口实现的。
- CGLIB:可以代理未实现任何接口的类,通过修改字节码,生成子类实现,然后在子类中织入切面逻辑。
- 基于 ASM,可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。
Spring AOP 基于动态代理方式实现;AspectJ 基于静态代理方式实现。
Spring 事务
流程
Spring 事务通过 AOP 实现,生成具体的代理对象。
事务是通过 TransactionInterceptor 来实现的,然后调用 invoke 来实现具体的逻辑。
- 解析事务相关的属性,来判断是否开启新事务
- 当需要开启的时候,获取数据库连接,关闭自动提交功能,开启事务
- 执行 SQL 逻辑操作
- 如果执行失败,通过 completeTransactionAfterThrowing 进行事务的回滚,回滚的具体逻辑是通过 doRollBack() 方法获取 jdbc 连接来实现的
- 通过 doCommit() 方法获取连接来提交
- 当事务执行完毕后清除相关事务信息:cleanupTransactionInfo
分类
- 编程式:TransactionTemplate 对业务有侵入性,少用。
- 声明式:@EnableTransactionManagement + @TranSactional
- 通过 AOP 将事务处理的功能编织到拦截的方法中(方法前开启事务,方法后酌情回滚)
事务失效场景
- 异常捕获 => 在 catch 中 throw 异常
- 抛出检查异常 => 指定 rollbackfor,因为默认只回滚 unchecked 异常(继承自 RuntimeException 或 Error)
- 修饰的方法不是 public
- 数据库引擎 MyISAM 不支持事务
- propagation 属性设置为 SUPPORTS、NOT_SUPPORTED、NEVER 时
3 类事务传播特性
指的是不同方法的嵌套调用过程中,事务应该延用还是新建,当出现异常的时候是回滚还是提交。
- 支持当前事务
- 不支持当前事务
- 嵌套事务
- 异常统一放在外层方法处理
- 内层方法能影响外层,但是外层不会影响内层
🌟7 种事务传播参数
REQUIRED(默认):事务管理员有事务就加入,没事务就新建
REQUIRED_NEW:无论如何都新建事务
Q:如何保证无论转账是否成功都记录日志留痕?
A:日志对应的事务不要加入转账的事务中,在插入日志的方法上加上
@Transactional(propagation = Propagation.REQUIRES_NEW)
SUPPORTS:事务管理员有事务就加入,没有就算了(@Transactional 失效)
NOT_SUPPORTS:无乱如何都不支持事务(@Transaction al 失效)
MANDATORY:事务管理员有事务就加入,没有就报错
NEVER:事务管理员没事务没关系,有事务就报错(@Transactional 失效)
NESTED:延用外层方法的事务,设置 savePoint
Spring Cloud 是如何调用的?
- RestTemplate方式调用
- 注入 RestTemplate 的bean,添加@Bean注解
- 调用
.getForObject()
或.postForObject()
方法 - Feign方式调用
- 添加 pom 依赖
- 启动类添加 @EnableFeignClients 注解
- 写接口:
|
|
- 使用 FeignClient 中定义的方法取代 RestTemplate(并集成了 ribbon,实现了负载均衡) IRule 接口决定负载均衡策略,并选择某个服务: 1. ZoneAvoidanceRule(默认):对Zone区域内多个服务轮询,zone值可配置 2. RoundRobinRule:轮询 3. WeightedResponseTimeRule:服务响应时间越长,服务器权重就越小,概率就越低 4. RandomRule:随机
SpringMVC 执行流程?
与 DispatcherServlet(前端控制器) 依次交互的有:
- HandlerMapping (处理器映射器):找到类名#方法名
- HandlerAdaptor(适配器):处理参数、返回值
- ViewResolver(视图解析器,用于 JSP):逻辑视图 -> 视图
对于前后端分离项目,在方法上添加 @ResponseBody,通过 HttpMessageConverter 响应 JSON 数据 @RestController = @Controller + @ResponseBody
JWT
JWT 数据结构?
- head
- payload
- 用户信息
- 加密信息
- 过期时间
- signature
JWT 和 token 的区别?
token 需要查数据库,JWT 不需要。
security和jwt怎么融合?
- 认证:主要 jwt 的 token 实现
- 鉴权:springSecurity实现,RBAC = Role-Based Access Control
从cookie到jwt解决了什么?
- 可以转化为 json 串,可读性强;
- RESTful 设计原则,避免在服务端保存会话状态,降低了服务器端的负担;
- 签名机制,安全性强。
token 泄露怎么处理?
采用非对称加密:给前端一个公钥,加密数据传到后台,用私钥解密后处理数据。
Springboot的自动装配原理?
@SpringbootApplication
- @SpringBootConfiguration 声明当前类为配置类
- @ComponentScan 组件扫描
- @EnableAutoConfiguration 自动化配置
- @Import 读取了项目和项目引用的 jar 包,位于 classpath 路径下 META-INF/spring.factories 文件
- @ConditionalOnClass 有则加载
注入方式
对于引用类型:
按类型 byType(推荐):
@Autowired
使用的是暴力反射,不需要 setter方法按名称 byName(变量名与配置耦合,不推荐)
@Autowired
+@Qualifier("beanName ")
@Resource("名称")
按构造方法(不常用)
setter
对于简单属性:使用 @Value(“xxx”)注入,可以来自外部 property,写法:
@Value("${name}")
加载外部properties 使用 @PropertySource(“classpath:jdbc.properties”),不可以使用星号*
如何对静态成员注入?
static 对象不能使用 Autowired 注入,只能通过set 方法注入。
|
|
拦截器(Interceptor)与过滤器(Filter)的区别?
- 归属不同:Filter属于Servlet技术,Interceptor属于SpringMVC技术(基于 AOP)
- 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMVC的访问进行增强,可以精细到方法
执行流程
- preHandle(return true 才走下一步,false结束)
- controller
- postHandle
- AfterCompletion
拦截器(Interceptor)
是一种动态拦截方法调用的机制,作用:
- 在指定的方法调用前后执行预先设定的代码
- 组织原始方法的执行
|
|
拦截器链
preHandle在前的拦截器,postHandle 和 afterCompletion 在后。
- 123 都为 true:pre 123 -> controller -> post 321 -> after 321
- 12 true 3 false:pre12 -> after 21
- 13 true 2 false: pre1 -> after 1
- 23 true 1 false:都不执行
全局异常处理
表现层通过AOP处理
|
|
- 业务异常:提醒用户规范操作
- 系统异常:安抚用户,通知运维,记录日志
REST风格(Representational State Transfer)表现形式状态转换
资源名称采用复数,如users、books
- GET:查询
- POST:新增
- PUT:修改
- DELETE:删除
Q:如何避免Spring加载SpringMVC已加载的Controller?
A:加载Spring控制的bean的时候,排除掉SpringMVC控制的bean。
@ComponentScan(value = "edu.zjut",excludeFilters=@ComponentScan.Filter(type=FilterType.ANNOTATION,classes=Controller.class))
按照注解排除@ComponentScan({"edu.zjut.service","edu.zjut.dao"})
- 不区分Spring和SpringMVC的环境,都加载
参数传递
@RequestParam("xxx")
:请求参数和形参不同时需配置,不写的话默认取相同。@RequestParam
修饰 List 就可以实现集合传参。参数对象中有对象时采用
address.city=beijing
这种形式。@RequestBody
:设置controller方法返回值为响应体。传字符串(json)用- HttpMessageConverter
日期参数:接收可以用 Date 接,加上
@DateTimeFormat(pattern = "yyyy-MM-dd")
@PathVariable
从路径中取值
SpringMVC
基于 Java 实现 MVC 模型的轻量级 web 框架。
后端服务器:
表现层:SpringMVC(取代 Servlet),封装数据(code、msg、data),用postman测试
业务层:Spring,用JUnit测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
数据层:Mybatis
返回 json 数据,需要加 @ResponseBody
注解。