文章目录
- 1. Spring
- 1.1 Spring Beans
- 1.谈谈你对Spring的理解以及优缺点
- 2. 什么是Spring beans
- 3. 配置注册Bean有哪几种方式
- 4. Spring支持的几种bean的作用域
- 5. 单例bean的优势
- 6. 单例bean是线程安全的吗?如何优化为线程安全
- 7. 谈一谈spring bean的自动装配
- 8. Spring有哪些生命周期回调方法?有哪几种实现方式?
- 9. 解释Spring框架中bean的生命周期
- 10.Spring是如何解决Bean的循环依赖 // todo
- 11. Spring如何避免在并发下获取不完整的Bean
- 12. Spring在加载过程中Bean有哪几种形态,详解BeanDefinition的加载过程
- 13. Spring如何在所有BeanDefinition注册完后做扩展
- 14. Spring如何在所有Bean创建完后做扩展
- 15. Bean的创建顺序是什么样的?
- 1.2 Spring IOC
- 1. Spring IOC容器/依赖注入(DI)是什么
- 2. 谈一谈Spring IoC 的实现机制
- 3. IOC容器的加载过程
- 4. 谈一谈BeanFactory和FactoryBean
- 5.谈一谈Spring的常见扩展点
- 1.3 Spring AOP
- 1. 谈一谈Spring AOP
- 2. Spring AOP和AspectJ AOP的区别
- 3. JDK动态代理和CGLIB动态代理的区别
- 4. AOP有几种实现方式
- 5. 什么情况下AOP会失效,怎么解决
- 6. Spring的AOP是在哪里创建的动态代理
- 7. Spring的 Aop的完整实现流程
- 1.4 Spring注解
- 1. Spring有哪几种配置方式以及具体实现方式
- 2. @Import可以有几种用法?
- 3. 如何让自动注入没有找到依赖Bean时不报错
- 4. 如何让自动注入找到多个依赖Bean时不报错
- 5. @Autowired和@Resource之间的区别
- 6. @Autowired注解自动装配的过程?
- 7. 配置类@Configuration作用+原理解析
- 1.5 Spring事务
- 1. 事务四大特性ACID
- 2. Spring支持的事务管理类型以及实现声明式事务的方式
- 3. 说一下Spring的事务传播行为
- 4. Spring事务传播行为实现原理 // todo
- 5. Spring事务实现基本原理 // todo
- 6. Spring 框架中都用到了哪些设计模式
- 2. SpringMVC
- 1. 请描述Spring MVC的工作流程以及DispatcherServlet工作流程?
- 2. SpringMVC的拦截器和过滤器的区别以及执行顺序
- 3. 是否可以把所有Bean都通过Spring容器来管理
- 4. 是否可以把我们所需的Bean都放入Springmvc子容器里面来管理
- 5. Spring和SpringMVC为什么需要父子容器?
- 3. SpringBoot
- 1. 谈谈你对SpringBoot的理解
- 2. springboot自动配好了哪些组件?
- 3. springboot自动配置/启动原理
- 4. springmvc静态资源自动配置原理
- 5. Springboot请求映射原理
- 6. springboot参数解析原理
- 7. springboot数据响应解析原理
- 8. Springboot拦截器底层处理逻辑
- 4. 微服务
1. Spring
1.1 Spring Beans
1.谈谈你对Spring的理解以及优缺点
从狭义上来讲,Spring是一个轻量级的IOC/AOP开源容器框架,为了解决企业级应用开发的业务逻辑层和其他各层对象和对象直接的耦合问题。
IOC: 控制反转
AOP:面向切面编程
容器:包含并管理应用对象的生命周期从广义上来讲,Spring是一个生态,可以构建java应用所需的一切基础设施。
优点:
1.IOC方便解耦,简化开发: 集中管理对象,对象和对象之间的耦合度减低,方便维护对象。
2.支持AOP编程: 在不修改代码的情况下可以对业务代码进行增强 减少重复代码 提高开发效率维护方便
3.支持事务机制: 通过@Transactional声明式方式灵活地进行事务的管理,提高开发效率和质量
4.方便集成各种优秀框架: 拥有非常强大粘合度、集成能力非常强,只需要简单配置就可以集成第三方框架(如Struts,Hibernate、Hessian、Quartz)缺点:
由于spring大而全(集成这么多框架、提供非常非常多的扩展点,经过十多年的代码迭代) 代码量非常庞大,一百多万对于去深入学习源码带来了一定困难。
2. 什么是Spring beans
被Spring容器实例化、组装和管理的对象就是bean
- bean是对象,一个或者多个不限定
- bean由Spring中一个叫IoC的东西管理
3. 配置注册Bean有哪几种方式
1.xml花式注册bean:
详见/spring_ioc/src/main/resources/config.xml
2.注解
@Component(@Controller 、@Service、@Repostory)
3.javaConfig:
@Configuration
@Bean 可以自己控制实例化过程
4.@Import-3种方式
1)在配置类上配置@Import注解,并定义要创建的bean.class,容器启动后就会自动创建
2)使用ImportSelector,实现ImportSelector接口
3)使用ImportBeanDefinitionRegistrar,实现ImportBeanDefinitionRegistrar接口
4. Spring支持的几种bean的作用域
singleton: bean在每个Spring ioc容器中只有一个实例
prototype: 一个bean的定义可以有多个实例
request: 每次http请求都会创建一个bean,该作用域仅在基于web的Spring应用情形下有效。
session: 在一个HTTP Session中,一个bean定义对应一个实例,该作用域仅在基于web的Spring应用情形下有效
application: 全局Web应用程序范围的范围标识符
注意: 缺省的Spring bean的作用域是Singleton。使用prototype作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。
5. 单例bean的优势
- 减少了服务器的内存消耗。因为spring需要通过反射或者cglib来生成bean实例的消耗,而这都是耗性能的操作,其次给对象分配内存也会涉及复杂算法。
- 减少jvm垃圾回收。 由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
- 快速获取bean。 因为单例的获取bean操作除了第一次生成之外,其余的都是从缓存里获取的,速度很快。
6. 单例bean是线程安全的吗?如何优化为线程安全
不是,spring框架并没有对单例bean进行多线程的封装处理。
如果在类中声明了成员变量,并在该类成员方法中进行读写操作(有状态),此时就会出现线程不安全的情况,测试如下:
有状态就是有数据存储功能(比如成员变量读写)。
无状态就是不会保存数据。
@Configuration
public class MainConfig {
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public TestSpringBean testSpringBean(){
return new TestSpringBean();
}
}
public class TestSpringBean {
private String username;
public String testSingleBeanIfThreadSafe(String name) throws InterruptedException {
// 成员变量进行读写,线程不安全
username = name + "SingleBean";
Thread.sleep(100);
return username;
}
}
解决:
* 1. 设置为多例
* 2. 设置为局部变量
* 3. 将成员变量放在ThreadLocal
* 4. 同步锁 会影响服务器吞吐量【单线程,别用了】
// 优化一:设置为多例
@Configuration
public class MainConfig {
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public TestSpringBean testSpringBean(){
return new TestSpringBean();
}
}
// 优化二:设置为局部变量
public class TestSpringBean {
public String testSingleBeanIfThreadSafe(String name) throws InterruptedException {
// 局部变量进行读写,线程安全
String username = name + "SingleBean";
Thread.sleep(100);
return username;
}
}
// 优化三:将成员变量放在ThreadLocal
// 1. ThreadLocal采用了“空间换时间”的方式, ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突, 因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。
// 2. ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
public class TestSpringBean {
private ThreadLocal<String> username = new ThreadLocal<>();
public String testSingleBeanIfThreadSafe(String name) throws InterruptedException {
username.set(name + "SingleBean");
Thread.sleep(100);
return username.get();
}
}
// 优化四: 同步锁 会影响服务器吞吐量【单线程,别用了】
7. 谈一谈spring bean的自动装配
在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象
一般使用@Autowired自动装配:
byName: 通过bean的名称进行自动装配,如果一个bean的property与另一bean的name 相同,就进行自动装配。
byType: 通过参数的数据类型进行自动装配。
constructor: 利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
no: 默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
8. Spring有哪些生命周期回调方法?有哪几种实现方式?
有两个重要的bean生命周期方法,第一个是init,它是在容器加载bean的时候被调用。第二个方法是 destroy 它是在容器卸载类的时候被调用。分别有以下三种实现方式
public class TestSpringBean implements InitializingBean, DisposableBean {
// 方式一:通过注解@PostConstruct、@PreDestroy
@PostConstruct
public void init1() {
System.out.println("@PostConstruct注解实现初始化001...");
}
@PreDestroy
public void destroy1() {
System.out.println("@PreDestroy注解实现销毁001...");
}
// 方式二:通过实现接口方法InitializingBean, DisposableBean
@Override
public void afterPropertiesSet() {
System.out.println("InitializingBean实现初始化002...");
}
@Override
public void destroy() {
System.out.println("DisposableBean注解实现销毁002...");
}
// 方式三:通过@Bean(initMethod = "init3", destroyMethod = "destroy3")
public void init3() {
System.out.println("@Bean(initMethod = \"init3\", destroyMethod = \"destroy3\")实现初始化001...");
}
public void destroy3() {
System.out.println("@Bean(initMethod = \"init3\", destroyMethod = \"destroy3\")实现销毁001...");
}
}
9. 解释Spring框架中bean的生命周期
Bean生命周期:指定的就是Bean从创建到销毁的整个过程,主要分为四大部分
实例化bean
a. 构造器方式(反射):通过反射去推断构造函数进行实例化
b. 静态工厂方式; factory-method
c. 实例工厂方式(@Bean); factory-bean+factory-method
d. FactoryBean方式Bean属性赋值
a. 解析自动装配@Autowired,DI的体现
b. 可能会出现循环依赖的问题初始化Bean
a. 调用大量XXXAware回调方法
b. 调用初始化生命周期回调方法(三种)
c. 如果bean实现aop,则创建动态代理销毁Bean
a. 在spring容器关闭的时候进行调用
b. 调用销毁生命周期回调
10.Spring是如何解决Bean的循环依赖 // todo
11. Spring如何避免在并发下获取不完整的Bean
单例bean是线程不安全的,肯定需要线程同步,类似于如何实现单例模式,一般采用双重检查锁机制,如下所示:
- 线程1先检查一、二、三级缓存是否有bean(第一次肯定没有)
- 接着线程1开始创建bean并锁住了二三级缓存,只有当线程1将对象存到一级缓存之后,才会解锁二级、三级缓存【没有锁一级缓存】
- 此时线程2进来查看一级缓存是否存在bean,由于线程1还没有创建成功,所以拿不到bean
- 线程1创建bean成功,并将bean添加到一级缓存的同时,也remove了二级、三级缓存
- 重点:虽然线程2没有取到bean对象,但是会再调用getSingleton()进行二次检查,这次在一级缓存就可以取到bean了,获取成功。
为什么一级缓存不加到锁里面:
性能:因为一级缓存还有其他bean,避免获取其他已经创建好的Bean还要阻塞等待
12. Spring在加载过程中Bean有哪几种形态,详解BeanDefinition的加载过程
BeanDefinition的加载过程就是将概念态的Bean注册为定义态的Bean,也即是解析配置类
- 读取配置类:通过BeanDefinitionReader将配置类注册为BeanDefinition(基于注解:AnnotatedBeanDefinitionReader,基于xml文件:XmlBeanDefinitionReader)
- 解析配置类:通过ConfigurationClassParser解析注解@Component@Conroller@Bean等等
- 不同的注解有不同的解析器:比如ComponentScan通过ClassPathBeanDefinitionScanner扫描包路径下所有.class文件,,判断该类有没有@component注解,排除接口/抽象类等等
- 将读取到的Bean定义信息通过BeanDefinitionRegistry注册为一个BeanDefinition
13. Spring如何在所有BeanDefinition注册完后做扩展
通常可以实现BeanFactoryPostProcessor对已注册的BeanDefinition进行修改,实现如下
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
// 在所有的BeanDefinition注册完成之后调用
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String definitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println("definitionName = " + definitionName);
}
// 应用场景:修改某个bean的属性/动态注册bean
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("mainConfig");
if (beanDefinition != null) {
beanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
beanDefinition.setLazyInit(true);
}
}
}
14. Spring如何在所有Bean创建完后做扩展
哪里才算所有的Bean创建完?
new ApplicationContext()---->refresh()---->finishBeanFactoryInitialization(循环所有的BeanDefinition,通过BeanFactory.getBean()生成所有的Bean) 这个循环结束之后所有的bean也就创建完了
一般通过以下2种方式来做扩展:
// 方式一:实现SmartInitializingSingleton
@Component
public class MySmartInitializingSingleton implements SmartInitializingSingleton {
@Override
public void afterSingletonsInstantiated() {
System.out.println("方式一实现SmartInitializingSingleton:所有bean已经注册完,可以进行后续扩展");
}
}
// 方式二:监听ContextRefreshedEvent事件
@Component
public class MyRefreshedEventListened {
@EventListener(ContextRefreshedEvent.class)
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("event.getSource() = " + event.getSource());
System.out.println("方式二监听ContextRefreshedEvent事件:所有bean已经注册完,可以进行后续扩展");
}
}
15. Bean的创建顺序是什么样的?
Bean的创建顺序是由BeanDefinition的注册顺序来决定的, 当然依赖关系也会影响Bean创建顺序,比如A依赖B,那肯定B先创建,A后创建。
那BeanDefinition的注册顺序由什么来决定的?
主要是由注解(配置)的解析顺序来决定,顺序如下:
1. @Configuration
2. @Component
3. @Import-类
4. @Bean
5. @Import—ImportBeanDefinitionRegistrar
1.2 Spring IOC
1. Spring IOC容器/依赖注入(DI)是什么
Spring IOC即控制反转即IoC (Inversion of Control),它把传统上由程序代码直接操控的对象的控制权交给Spring容器,由Spring IOC 负责创建对象、管理对象、通过依赖注入(DI)来装配对象、配置对象,并且管理这些对象的整个生命周期。
IOC的优点是什么?
1. 最小的代价和最小的侵入性使松散耦合得以实现,降低耦合度
2. 便于集中管理对象、方便维护
// 本质不同点
IOC和DI是从不同的角度描述的同一件事,IOC是从容器的角度描述,而DI是从应用程序的角度来描述,也可以这样说,IOC是依赖倒置原则的设计思想,而DI是具体的实现方式
2. 谈一谈Spring IoC 的实现机制
Spring 中的 IoC 的实现原理就是简单工厂模式加反射机制,案例如下:
public interface Fruit {
void eat();
}
public class Apple implements Fruit{
@Override
public void eat() {
System.out.println("Apple eat...");
}
}
public class Orange implements Fruit{
@Override
public void eat() {
System.out.println("Orange eat...");
}
}
public class Factory {
public static Fruit getInstance(String className){
Fruit fruit = null;
try {
fruit = (Fruit) Class.forName(className).newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
return fruit;
}
}
public class Test {
public static void main(String[] args) {
Fruit fruit = Factory.getInstance("cn.hyperchain.servlet.Apple");
fruit.eat();
}
}
3. IOC容器的加载过程
从概念态--->定义态
1:从实例化一个ApplicationContext容器对象开始;
2:首先调用bean工厂后置处理器invokeBeanFactoryPostProcessors完成bean扫描;
3:其次循环解析扫描出来的配置类,并实例化一个BeanDefinition对象来存储解析出来的信息; 4:然后把实例化好的beanDefinition对象put到beanDefinitionMap当中缓存起来,以便后面实例化bean;
从定义态到纯净态
5:首先spring判断该eanDefinition是否满足生产标准,依次判断这个bean是否Lazy,是否prototype,是否abstract等等,若满足,调用finishBeanFactoryInitialization方法来实例化单例的bean;
6:如果验证完成spring在实例化一个bean之前需要推断构造方法,因为spring实例化对象是通过构造方法反射,故而需要知道用哪个构造方法;
7:推断完构造方法之后spring调用构造方法反射实例化一个对象;
这个时候bean对象已经实例化出来了,但此时实例化出来的对象属性是没有注入,所以不是一个完整的bean
从纯净态到成熟态
8:spring处理合并后的beanDefinition
9:判断是否需要完成属性注入,如果需要完成属性注入,则开始注入属性
初始化
10:判断bean的类型回调Aware接口
11:调用生命周期回调方法
12:如果需要代理则完成代理
创建完成
13:put到单例池——bean完成——存在spring容器当中
4. 谈一谈BeanFactory和FactoryBean
BeanFactory:
- 它是Bean的“工厂”,实现了简单工厂的设计模式,主要职责就是通过调用getBean传入标识来生产bean
- BeanFactory是Spring中非常核心的一个顶层接口,它有非常多的实现类,最强大的工厂是:DefaultListableBeanFactory,Spring底层就是使用的该实现工厂进行生产Bean
- BeanFactory它也是容器,Spring容器(管理着Bean的生命周期)
FactoryBean:
- FactoryBean是一个bean,所以也是由BeanFactory来管理的
- FactoryBean是一个特殊的Bean,它会表现出工厂模式的特性,是一个能产生或者修饰对象生成的工厂Bean
- FactoryBean是一个接口,必须被一个bean去实现,一旦被某个bean实现,则无法获取原来类的实例,而是会回的getObject自定义返回的bean,如下
@Component
public class MyFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
// 此时容器中MyFactoryBean被修改为Student
return new Student();
}
@Override
public Class<?> getObjectType() {
return null;
}
}
5.谈一谈Spring的常见扩展点
1. 实现BeanDefinitionRegistryPostProcessor接口
功能:动态注册BeanDefinition
@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition beanDefinition = new RootBeanDefinition(Student.class);
registry.registerBeanDefinition("car", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
2. 实现BeanFactoryPostProcessor接口
功能:加载完所有BeanDefinition后调用
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
// 在所有的BeanDefinition注册完成之后调用
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String definitionName : beanFactory.getBeanDefinitionNames()) {
System.out.println("definitionName = " + definitionName);
}
// 应用场景:修改某个bean的属性
BeanDefinition beanDefinition = beanFactory.getBeanDefinition("mainConfig");
if (beanDefinition != null) {
beanDefinition.setScope(ConfigurableBeanFactory.SCOPE_PROTOTYPE);
beanDefinition.setLazyInit(true);
}
}
}
3. 实现BeanPostProcessor实现类:在Bean的生命周期会调用9次Bean的后置处理器
4. 创建单例bean初始化阶段: 调用XXXAware接口的SetXXXAware方法[BeanNameAware\BeanFActoryAware]
5. 生命周期回调: 初始化、销毁[各三种]
详见spring beans 第8问
1.3 Spring AOP
1. 谈一谈Spring AOP
AOP(Aspect-Oriented Programming),一般称为面向切面编程,用于将那些与主体业务无关但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块。从而减少系统重复代码,降低模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。
切面(Aspect): 切面类,切面类会管理切点(切入点表达式)、通知(公共行为代码)
连接点(Join point): 被增强的业务方法
通知(Advice): 增加到业务方法中的公共代码(前置通知、后置通知、异常通知、返回通知、环绕通知)
切点(Pointcut): 由他决定哪些方法需要增强、哪些不需要增强,结合切点表达式进行实现
织入(Weaving) : spring aop用的织入方式:动态代理,就是为目标对象创建动态代理的过程就叫织入
2. Spring AOP和AspectJ AOP的区别
Spring Aop提供了AspectJ的支持,但只用到的AspectJ的切点解析和匹配,@Aspect、@Before.等这些注解都是由AspectJ 发明的
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId> 4 <version>1.9.5</version>
</dependency
区别:
(1) Spring AOP使用的动态代理,它基于动态代理来实现。默认地,如果使用接口的,用JDK提供的动态代理实现,如果没有接口,使用 CGLIB 实现。
(2) AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增 强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
(3) Spring AOP是基于动态代理实现的,在容器启动的时候需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如AspectJ那么好。
3. JDK动态代理和CGLIB动态代理的区别
Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理:
JDK动态代理只提供接口的代理,不支持类的代理。
步骤一:JDK会在运行时为目标类生成一个动态代理类$proxy*.class.
步骤二:该代理类是实现了接目标类接口,并且代理类会实现接口所有的方法增强代码。
步骤三:调用时通过代理类先去调用处理类进行增强,再通过反射的方式进行调用目标方法,从而实现AOP
如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。
步骤一:CGLIB的底层是通过ASM在运行时动态的生成目标类的一个子类,(还有其他相关类,主要是为增强调用时效率) 会生成多个
步骤二:重写父类所有的方法增强代码, 调用时先通过代理类进行增强,再直接调用父类对应的方法进行调用目标方法。从而实现AOP。
PS1: CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
PS2: CGLIB除了生成目标子类代理类,还有一个FastClass(路由类),可以(但不是必须)让本类方法调用进行增强,而不会像jdk代理那样本类方法调用增强会失效
4. AOP有几种实现方式
- 基于接口的配置: Spring 1.2之前的AOP是完全基于实现那几个接口(抛弃)
- 基于XML的配置: Spring 2.0以后使用XML的方式来配置,使用命名空间(抛弃)
- 基于注解的配置: 使用注解的方式来配置,这种方式感觉是最方便的
package cn.hyperchain.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//这是一个切面类:完成非业务代码
@Aspect
@Component
public class LoggerAspect {
//抽取可重用的切入点表达式
@Pointcut("execution(public int cn.hyperchain.utils.Cal.*(int,int))")
public void pointCut01() {
}
@Pointcut("execution(public int cn.hyperchain.utils.Cal.*(..))")
public void pointCut02() {
}
//1、前置通知:切入到Cal.*所有方法之前执行
@Before("execution(public int cn.hyperchain.utils.Cal.*(int,int))")
public void before(JoinPoint joinPoint) {
//获得方法名
String name = joinPoint.getSignature().getName();
//获得参数
Object[] args = joinPoint.getArgs();
System.out.println("普通前置通知@Before: " + name + "方法正在执行,参数是:" + Arrays.toString(args));
}
//2、后置通知:切入到Cal.*所有方法之后执行
@After("pointCut01()")
public void after(JoinPoint joinPoint) {
String name = joinPoint.getSignature().getName();
System.out.println("普通后置通知@After: " + name + "方法执行结束");
}
//3、返回通知:在目标方法正常返回之后执行
@AfterReturning(value = "pointCut02()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
String name = joinPoint.getSignature().getName();
System.out.println("普通返回通知@afterReturning: " + name + "方法执行返回,结果是:" + result);
}
//4、异常通知:抛出异常后执行
@AfterThrowing(value = "execution(public int cn.hyperchain.utils.Cal.*(..))", throwing = "exception")
public void afterThrowing(JoinPoint joinPoint, Exception exception) {
String name = joinPoint.getSignature().getName();
System.out.println("普通异常通知@afterThrowing: " + name + "方法抛出异常,异常是:" + exception);
}
//5、环绕通知:这就是一个动态代理,可以取代上述四个通知
@Around(value = "pointCut01()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
//得到参数
Object[] args = pjp.getArgs();
//得到方法名
String name = pjp.getSignature().getName();
Object proceed = null;
try {
//Before
System.out.println("环绕前置通知Before:" + name + "正在运行..." + "参数是:" + Arrays.toString(args));
proceed = pjp.proceed(args);//执行目标方法
//AfterReturning
System.out.println("环绕返回通知AfterReturning: 返回值是:" + proceed);
} catch (Exception e) {
//@AfterThrowing
System.out.println("环绕异常通知AfterThrowing: 异常信息:" + e);
} finally {
//@After
System.out.println("环绕后置通知After: " + name + "方法结束");
}
return proceed;
}
}
5. 什么情况下AOP会失效,怎么解决
- 方法是private会失效 解决:改成public
- 目标类没有配置为Bean也会失效 解决:配置为Bean
- 切点表达式没有配置正确 解决:修改切入点表达式
- 内部调用方法A,方法A的AOP失效 解决:必须走代理,重新拿到代理对象再次执行方法才能进行增强
// 内部调用,AOP失效
@Component
public class CalImpl implements Cal {
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
public int div(int num1, int num2) {
int result = num1 / num2;
add(num1, num2); // 内部调用,AOP失效
return result;
}
}
// 方案一:手动走代理
@Component
public class CalImpl implements Cal {
@Autowired
private Cal cal;
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
public int div(int num1, int num2) {
int result = num1 / num2;
cal.add(num1, num2);
return result;
}
}
// 方案二:手动走代理
// 设置暴露当前代理对象到本地线程
@EnableAspectJAutoProxy(exposeProxy=true)
主类
@Component
public class CalImpl implements Cal {
@Autowired
private Cal cal;
public int add(int num1, int num2) {
int result = num1 + num2;
return result;
}
public int div(int num1, int num2) {
int result = num1 / num2;
(Cal)AopContext.currentProxy().add(num1, num2); // 拿到当前正在调用的动态代理对象
return result;
}
}
6. Spring的AOP是在哪里创建的动态代理
1.正常的Bean会在Bean的生命周期的初始化后,通过BeanPostProcessor.postProcessAfterInitialization创建aop的动态代理
2.还有一种特殊情况: 循环依赖的Bean会在Bean的生命周期‘属性注入’时存在的循环依赖的情况下,也会为循环依赖的Bean通过MergedBeanDefinitionPostProcessor.postProcessMergedBeanDefinition创建aop
7. Spring的 Aop的完整实现流程
以JavaConfig为例,Aop的实现大致分为四大步:
1.当@EnableAspectJAutoProxy 会通过@Import注册一个BeanPostProcessor处理AOP
2.解析切面: 在Bean创建之前的第一个Bean后置处理器会去解析切面@Aspect中的通知和切点,解析成Advisor
3.创建动态代理: 正常的Bean初始化后调用BeanPostProcessor拿到之前缓存的advisor,再通过advisor中pointcut判断当前Bean是否被切点表达式匹配,如果匹配,就会为Bean创建动态代理
4.调用: 拿到动态代理对象, 调用方法就会判断当前方法是否增强的方法,就会通过调用链的方式依次去执行通知
1.4 Spring注解
1. Spring有哪几种配置方式以及具体实现方式
这里有三种方法给Spring容器提供配置元数据。
1. XML配置文件: spring.xml
a. 启动Spring容器: ClassPathXmlApplicationContext("xxx.xml")
b. 注册bean: Spring.xml
c. <bean scope lazy>
d. 扫描包: <component-scan>
e. 引入外部属性配置文件 <property-placeHodeler resource="xxx.properties">
f. 属性赋值: <property name="password" value="${mysql.password}"></property>
g. 指定其他配置文件: <import resource="">
2. 基于注解的配置: @component/@controller/@service/@repository
3. 基于java配置类: @Configuration @Bean ....
a. 启动Spring容器: AnnotationConfigApplicationContext(javaconfig.class)
b. 配置类: @Configuration
c. @Bean @Scope @Lazy
d. 扫描包: @ComponentScan
e. 引入外部属性配置文件 @PropertySource("classpath:db.properties")
f. 属性赋值: @Value("${mysql.password}")
g. 指定其他配置文件: @Import @Import({配置类})
2. @Import可以有几种用法?
1. 直接指定类 (如果配置类会按配置类正常解析、 如果是个普通类就会解析成Bean)
2. 通过ImportSelector/DeferredImportSelector可以一次性注册多个,返回一个string[]每一个值就是类的完整类路径,
区别在于DeferredImportSelector顺序靠后
3. 通过ImportBeanDefinitionRegistrar 可以一次性注册多个,通过BeanDefinitionRegistry来动态注册BeanDefintion
3. 如何让自动注入没有找到依赖Bean时不报错
这个注解表明bean的属性必须在配置的时候设置,通过一个bean定义的显式的属性值或通过自动装配,若@Required 注解的bean属性未被设置,容器将抛出BeanInitializationException。示例:
@Autowired(required=false)
private Role role;
4. 如何让自动注入找到多个依赖Bean时不报错
5. @Autowired和@Resource之间的区别
@Autowired默认是按照类型装配注入的,只有当找不到与名称匹配的bean才会按照名称来装配注入
@Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入
6. @Autowired注解自动装配的过程?
记住:@Autowired通过Bean的后置处理器进行解析的
1. 在创建一个Spring上下文的时候在构造函数中进行注册AutowiredAnnotationBeanPostProcessor
2. 在Bean的创建过程中进行解析
1. 在实例化后预解析: 解析@Autowired标注的属性/方法,比如把属性的类型、名称、属性所在的类、元数据缓存)
2. 在属性注入真正的解析: 拿到上一步缓存的元数据 去ioc容器帮进行查找,并且返回注入
如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;
如果查询的结果不止一个,那么@Autowired会根据名称来查找;
如果上述查找的结果为空,那么会抛出异常。
解决方法时,使用required=false。
7. 配置类@Configuration作用+原理解析
- 作用
- @Configuration用来代替xml配置方式配置文件
- 没有@Configuration也是可以配置@Bean,加了@Configuration会为配置类创建cglib动态代理(保证配置类@Bean方法调用Bean的单例)
- 原理
- 首先创建Spring上下文的时候会注册一个解析配置类的后置处理器ConfigurationClassPostProcessor(实现BeanFactoryPostProcessor和BeanDefinitionRegistryPostProcessor扩展接口)
- 然后再调用invokeBeanFactoryPostProcessor解析各种注解,包括@Bean @Configuration@Import @Component等,也就是注册BeanDefinition
- ConfigurationClassPostProcessor.postProcessBeanFactory去创建cglib动态代理
1.5 Spring事务
1. 事务四大特性ACID
(1) 原子性(Atomicity)
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚, 因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
(2) 一致性(Consistency)
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。 拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。
(3) 隔离性(Isolation)
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都 感觉不到有其他事务在并发地执行。
(4) 持久性(Durability)
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。 例如我们在使用JDBC操作数据库时,在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务以及正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
2. Spring支持的事务管理类型以及实现声明式事务的方式
编程式事务管理: 这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
声明式事务管理: 这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。
实现声明式事务的三种方式:
1. 基于接口[不推荐]
a. 基于TransactionInterceptor的声明式事务: Spring声明式事务的基础,通常也不建议使用这种方式,但是与aop 一样,了解这种方式对理解 Spring 声明式事务有很大作用。
b. 基于TransactionProxyFactoryBean的声明式事务: 第一种方式的改进版本,简化的配置文件的书写,这是Spring 早期推荐的声明式事务管理方式,但是在 Spring 2.0 中已经不推荐了。
2. 基于XML标签
<tx>和<aop>命名空间的声明式事务管理: 目前推荐的方式,其最大特点是与Spring AOP结合紧密,可以充分利用切点表达式的强大支持,使得管理事务更加灵活
3. 基于@Transactional注解[推荐]
将声明式事务管理简化到了极致。开发人员只需在配置文件中加上一行启用相关后处理Bean的配置,然后在需要实施事务管理的方法或者类上使用@Transactional指定事务规则即可实现事务管理,而且功能也不必其他方式逊色。
3. 说一下Spring的事务传播行为
事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?
4. Spring事务传播行为实现原理 // todo
5. Spring事务实现基本原理 // todo
6. Spring 框架中都用到了哪些设计模式
IOC - 单例模式(Bean实例)
AOP底层实现 - 代理模式(jdk代理+cglib动态代理)
AOP调用 - 责任链模式
BeanFactory - 简单工厂模式
FactoryBean - 工厂方法模式
Spring事件监听 - 观察者模式
BeanWrapper - 装饰器模式
SpringMVC的HandlerAdatper - 适配器模式
excludeFilters、includeFilters - 策略模式
Spring几乎所有外接扩展 - 模版方法模式
2. SpringMVC
1. 请描述Spring MVC的工作流程以及DispatcherServlet工作流程?
(1)用户发送请求至前端控制器DispatcherServlet;
(2)DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handler;
(3)处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet;
(4)DispatcherServlet调用 HandlerAdapter处理器适配器;
(5)HandlerAdapter经过适配调用具体处理器(Handler,也叫后端控制器);
(6)Handler执行完成返回ModelAndView;
(7)HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
(8)DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
(9)ViewResolver解析后返回具体View;
(10)DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
(11)DispatcherServlet响应用户。
2. SpringMVC的拦截器和过滤器的区别以及执行顺序
拦截器不依赖与servlet容器,过滤器依赖与servlet容器。
拦截器只能对action请求(DispatcherServlet映射的请求)起作用,而过滤器则可以对几乎所有的请求起作用。 拦截器可以访问容器中的Bean(DI),而过滤器不能访问(基于spring注册的过滤器也可以访问容器中的bean)。
3. 是否可以把所有Bean都通过Spring容器来管理
也就是在Spring的applicationContext.xml中配置全局扫描
不可以,这样会导致我们请求接口的时候产生404。
如果所有的Bean都交给父容器,SpringMVC在初始化HandlerMethods的时候(initHandlerMethods)无法根据Controller的handler方法注册HandlerMethod,并没有去查找父容器的bean,也就无法根据请求URI获取到 HandlerMethod来进行匹配.
4. 是否可以把我们所需的Bean都放入Springmvc子容器里面来管理
也就是在springmvc的spring servlet.xml中配置全局扫描
可以,因为父容器的体现无非是为了获取子容器不包含的bean, 如果全部包含在子容器完全用不到父容器了,所以是可以全部放在 springmvc子容器来管理的。
虽然可以这么做不过一般应该是不推荐这么去做的,一般人也不会这么干的。如果你的项目里有用到事务、aop记得也需要把 这部分配置需要放到Spring-mvc子容器的配置文件来,不然一部分内容在子容器和一部分内容在父容器,可能就会导致 你的事物或者AOP不生效。
所以如果aop或事物如果不生效也有可能是通过父容器(spring)去增强子容器 (Springmvc),也就无法增强。
5. Spring和SpringMVC为什么需要父子容器?
就功能性来说不用子父容器也可以完成(参考:SpringBoot就没用子父容器)
1. 父子容器的主要作用应该是划分框架边界,有点单一职责的味道。service、dao层我们一般使用spring框架 来管理、controller层交给springmvc管理
2. 规范整体架构。使父容器service无法访问子容器controller、子容器controller可以访问父容器service
3. 方便子容器的切换。如果现在我们想把web层从spring mvc替换成struts,那么只需要将springmvc.xml替换成Struts的配置文件struts.xml即可,而springcore.xml不需要改变。
4. 为了节省重复bean创建
3. SpringBoot
1. 谈谈你对SpringBoot的理解
SpringBoot的用来快速开发Spring应用的一个脚手架、其设计目的是用来简新Spring应用的初始搭建以及开发过程。
优点:
1.SpringBoot提供了很多内置的Starter结合自动配置,对主流框架无配置集成、开箱即用。
2.SpringBoot简化了开发,采用JavaConfig的方式可以使用零xml的方式进行开发;
3.SpringBoot内置Web容器无需依赖外部Web服务器,省略了Web.xml,直接运行jar文件就可以启动web应用;
4.SpringBoot帮我管理了常用的第三方依赖的版本,减少出现版本冲突的问题;
5.SpringBoot自带了监控功能,可以监控应用程序的运行状况,或者内存、线程池、Http请求统计等,同时还提供了优雅关闭应用程 序等功能。
核心注解:
1.@SpringBootApplication: 这个注解标识了一个SpringBoot工程
2.@SpringBootConfiguration: 这个注解实际就是一个@Configuration,表示启动类也是一个配置类
3.@EnableAutoConfiguration: 向Spring容器中导入了一个Selector,用来加载ClassPath下SpringFactories中所定义的自动配置类,将这些自动加载为配置Bean
4.@Conditional: 在自定义应用中进行定制开发
@ConditionalOnBean、 @ConditionalOnClass、 @ConditionalOnExpression、@ConditionalOnMissingBean等。
2. springboot自动配好了哪些组件?
a、自动配好Tomcat
默认引入Tomcat依赖,也可以手动排除掉,去整合jetty服务器
b、自动配好Web开发常见功能
SpringBoot帮我们配置好了所有web开发的常见场景,如字符编码问题
c、自动配好SpringMVC【核心,DispatchServlet.doDispatch( )】
引入SpringMVC全套组件
自动配好SpringMVC常用组件(功能)
d、默认的扫包原则
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来,无需以前的包扫描配置,
也可以自定义扫描路径
@SpringBootApplication(scanBasePackages=“com.atguigu”) 或者
@ComponentScan 指定扫描路径
e、各项配置字段默认值
默认配置最终都是映射到某个类上,如:MultipartProperties,将配置文件的字段映射到某个javabean,@Value、@PropertySource,配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
f、按需加载所有自动配置项
非常多的starter,引入了哪些场景这个场景的自动配置才会开启。SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面。
3. springboot自动配置/启动原理
步骤一:@SpringBootApplication引导加载自动配置类
解析:
@SpringBootConfiguration 代表当前是一个配置类
@ComponentScan 指定扫描哪些组件,Spring注解
@EnableAutoConfiguration 启动自动配置【important】
@AutoConfigurationPackage: 自动扫包,指定了默认的扫包规则
@Import(AutoConfigurationPackages.Registrar.class)
@Import(AutoConfigurationImportSelector.class) 自动导入129个自动配置类,但并不是全部生效
@EnableAutoConfiguration:
@AutoConfigurationPackage:
这里解释了为什么默认扫描启动类所在包及其父包的所有文件!!!
@Import(AutoConfigurationImportSelector.class): 自动导入129个自动配置类,但并不是全部生效
默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories【默认全部加载这些129个组件,但是按条件赋值】
129个自动配置类详情如下:
步骤二:按需开启自动配置项
虽然我们129个场景的所有自动配置启动的时候默认全部加载,但是xxxxAutoConfiguration会按照条件装配规则按需配置,核心配置类:
WebMvcAutoConfiguration AopAutoConfiguration
ElasticsearchAutoConfiguration RedisAutoConfiguration
JdbcTemplateAutoConfiguration HttpMessageConvertersAutoConfiguration
QuartzAutoConfiguration SecurityAutoConfiguration
ThymeleafAutoConfiguration
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)、@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false))
步骤三:自定义默认配置
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
}
application.yml配置文件中自定义的字段:
https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#application-properties
总结:
1、SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
2、每个自动配置类按照条件进行生效,默认都会绑定配置文件xxxProperties指定的值。
3、生效的配置类就会给容器中装配很多组件
4、定制化配置
用户直接自己@Bean替换底层的组件
用户去看这个组件是获取的配置文件什么值就去修改。
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
4. springmvc静态资源自动配置原理
为什么静态资源路径就是/public、/resource
为什么欢迎页index.html自动识别去查看自动配置类 WebMvcAutoConfiguration源码即可!
a、静态资源处理、webjars的默认规则:
spring.resources.add-mappings: false 禁用静态资源默认处理规则
b、欢迎页的处理规则
PS:若配置类只有一个有参构造器:则有参构造器所有参数的值都会从容器中确定
ResourceProperties: 获取和spring.resources绑定的所有的值的对象
WebMvcProperties: 获取和spring.mvc绑定的所有的值的对象
ListableBeanFactory: beanFactory Spring的beanFactory
HttpMessageConverters: 找到所有的HttpMessageConverters
ServletRegistrationBean: 给应用注册Servlet、Filter....
DispatcherServletPath
5. Springboot请求映射原理
- Spring MVC 的核⼼组件
- Spring MVC 的工作流程
1、客户端请求/user被 DisptacherServlet 接收
2、DisptacherServlet根据 HandlerMapping 映射到 Handler
3、HandlerMapping生成 Handler 和 HandlerInterceptor,并 HandlerExecutionChain 的形式返回给 DisptacherServlet
4、DispatcherServlet 通过 HandlerAdapter 调用 Handler 的方法完成业务逻辑处理
5、Handler 返回一个 ModelAndView 给 DispatcherServlet
6、DispatcherServlet 将获取的 ModelAndView 对象传给 ViewResolver 视图解析器器,将逻辑视图解析为物理理视图View,并返回给DispatcherServlet。
7、DispatcherServlet 根据 View 进行视图渲染(将模型数据Model 填充到视图View中)。
8、DispatcherServlet 将渲染后的结果响应给客户端。
- Springboot请求映射原理(源码解析DispatcherServlet)
即浏览器输入http://127.0.0.1:8080/login,springboot如何匹配到合适的Controller来处理这个请求?
**步骤一:**请求映射的功能分析都从 DispatcherServlet.doDispatch()开始
步骤二:
DispatchServlet总指挥通过HandlerMapping 将不同的请求映射到不同的Handler,springboot共有6个HandlerMapping,请求进来/index挨个尝试6个HandlerMapping看是否有请求信息
如果有就找到这个请求对应的handler
如果没有就是下一个 HandlerMapping
步骤三:
Handler在执行之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这些操作都是由 HandlerApater来完成,常见的HandlerApater共有4个
6. springboot参数解析原理
Controller传递的参数,springboot底层是如何处理的?
a、HandlerMapping中找到能处理请求的Handler(Controller.method())
b、为当前Handler 找一个适配器 HandlerAdapter、RequestMappingHandlerAdapter
c、适配器执行目标方法并确定方法参数的每一个值
PS:参数解析器30个
7. springboot数据响应解析原理
以响应json为例,为什么controller返回是实体类,浏览器解析得到的是json?
a、jackson.jar+@ResponseBody, 给前端自动返回json数据
b、返回值解析器
1、返回值处理器判断是否支持这种类型返回值 supportsReturnType
2、返回值处理器调用 handleReturnValue 进行处理
3、RequestResponseBodyMethodProcessor 可以处理返回值标了@ResponseBody 注解的。
4、利用 MessageConverters 进行处理 将数据写为json
1、内容协商(浏览器默认会以请求头的方式告诉服务器他能接受什么样的内容类型)
2、服务器最终根据自己自身的能力,决定服务器能生产出什么样内容类型的数据
3、SpringMVC会挨个遍历所有容器底层的 HttpMessageConverter ,看谁能处理
1、得到MappingJackson2HttpMessageConverter可以将对象写为json
2、利用MappingJackson2HttpMessageConverter将对象转为json再写出去。
返回值处理器 15个
8. Springboot拦截器底层处理逻辑
1、根据当前请求,找到HandlerExecutionChain【可以处理请求的handler以及handler的所有拦截器】
拦截器处理流程
4. 微服务
// 详见springcloud.md
// 详见springcloud_Alibaba.md