Spring 是一个轻量级、非入侵式的 控制反转 (IoC) 和 面向切面 (AOP) 的框架。
IoC(Inversion of Control)控制反转: 对象的创建控制权由程序转移到外部,这种思想称之为控制反转(目的:解耦)。
IoC容器:“外部”创建对象的容器。
Bean: IoC容器中创建或管理的对象。
DI(Dependency Injection)依赖注入: 在容器中建立bean与bean之间的依赖关系的整个过程,称之为依赖注入。
自动装配: Ioc容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配。
AOP(Aspect Oriented Programming): 面向切面编程,用来功能增强。
连接点(JoinPoint): 方法执行的任意位置。
切入点(PointCut): 匹配连接点的式子。
通知(Advice): 切入点处执行的操作。
通知类: 定义通知的类;
切面(Aspect): 描述通知与切入点的对应关系。
Java 是面向对象的编程语言,一个个实例对象相互合作组成了业务逻辑,原来,我们都是在代码里创建对象和对象的依赖。
所谓的 IOC(控制反转): 就是==由容器来负责控制对象的生命周期和对象间的关系。==以前是我们想要什么,就自己创建什么,现在是我们需要什么,容器就给我们送来什么(将对象的创建控制权由程序转移到外部,这种思想称为控制反转)。
也就是说,控制对象生命周期的不再是引用它的对象,而是容器。对具体对象,以前是它控制其它对象,现在所有对象都被容器控制,所以这就叫控制反转。
DI(依赖注入): 指的是容器在实例化对象的时候把它依赖的类注入给它。
最主要的是两个字 解耦 ,硬编码会造成对象间的过度耦合,使用IOC之后,我们可以不用关心对象间的依赖,专心开发应用就行。
Spring 的 IOC 本质就是一个大工厂 ,我们想想一个工厂是怎么运行的呢?
生产产品:一个工厂最核心的功能就是生产产品。在 Spring 里,不用 Bean 自己来实例化,而是交给 Spring,应该怎么实现呢?——答案毫无疑问,反射。
那么这个厂子的生产管理是怎么做的?你应该也知道——工厂模式。
库存产品:工厂一般都是有库房的,用来库存产品,毕竟生产的产品不能立马就拉走。Spring 我们都知道是一个容器,这个容器里存的就是对象,不能每次来取对象,都得现场来反射创建对象,得把创建出的对象存起来。
订单处理:还有最重要的一点,工厂根据什么来提供产品呢?订单。这些订单可能五花八门,有线上签签的、有到工厂签的、还有工厂销售上门签的……最后经过处理,指导工厂的出货。
在 Spring 里,也有这样的订单,它就是我们 bean 的定义和依赖关系,可以是 xml 形式,也可以是我们最熟悉的注解形式。
BeanFactory 是 Spring 的“心脏”,ApplicantContext 是完整的“身躯”。
BeanFactory (Bean工厂) 是 Spring 框架的基础设施,面向 Spring 本身。
ApplicationContext (应用上下文) 建立在 BeanFactoty 基础上,面向使用 Spring 框架的开发者。
BeanFactory 接口
BeanFactory 是类的通用工厂,可以 创建并管理各种类的对象。
BeanFactory 接口位于类结构树的顶端,它最主要的方法就是 getBean(String var1),这个方法 从容器中返回特定名称的 Bean。
BeanFactory 的功能通过其它的接口得到了不断的扩展,比如 AbstractAutowireCapableBeanFactory 定义了将容器中的 Bean 按照某种规则(比如按名字匹配、按类型匹配等)进行自动装配的方法。
ApplicationContext 接口
ApplicationContext 由 BeanFactory 派生而来,提供了更多面向实际应用的功能。可以这么说,使用 BeanFactory 就是手动档,使用 ApplicationContext 就是自动档。
ApplicationContext继承了HierachicalBeanFactory和ListableBeanFactory接口,在此基础上,还通过其他的接口扩展了BeanFactory的功能,包括:
Bean instantiation/wiring、Bean 的实例化/串联、自动的 BeanPostProcessor 注册、自动的 BeanFactoryPostProcessor 注册、方便的 MessageSource 访问(i18n)、ApplicationEvent 的发布与 BeanFactory 懒加载的方式不同,它是预加载,所以,每一个 bean 都在 ApplicationContext 启动之后实例化。
Spring 的 IOC 容器工作的过程,其实可以划分为两个阶段:容器启动阶段和Bean 实例化阶段。
其中容器启动阶段主要做的工作是 加载和解析配置文件 ,保存到对应的 Bean 定义中。
4个阶段: 刷新阶段、启动阶段、关闭阶段、停止阶段。
在 Spring 中,基本容器 BeanFactory 和扩展容器 ApplicationContext 的实例化时机不太一样,BeanFactory 采用的是 延迟初始化 的方式,也就是只有在第一次getBean()的时候,才会实例化Bean;ApplicationContext 启动之后会实例化所有的 Bean 定义。
Spring IOC 中 Bean 的生命周期大致分为四个阶段:实例化(Instantiation)、属性赋值(Populate)、初始化(Initialization)、销毁(Destruction)。
详细步骤如下:
实例化: 第 1 步,实例化一个 Bean 对象。主要包括:①读取spring配置文件相关标签封装生成BeanDefinition对象;②通过反射进行bean的实例化(此时的Bean是空对象信息)。
属性赋值: 第 2 步,为 Bean 设置相关属性和依赖。主要包括:①解析BeanDefinition的属性(beanName、beanType等)并赋值;②如果Bean对象里的属性需要引用容器内部的对象,那么需要调用aware接口的子类方法进行统一设置;③循环依赖。
初始化: 初始化阶段的步骤比较多,5、6 步是真正的初始化,第 3、4 步为在初始化前执行,第 7 步在初始化后执行,初始化完成之后,Bean 就可以被使用了。主要包括:①调用XXXAware回调方法;②调用初始化生命周期回调;③如果bean实现aop创建动态代理。
销毁: 第 8~10 步,第 8 步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第 9、10 步真正销毁 Bean 时再执行相应的方法。主要包括:①在spring容器关闭的时候进行调用;②调用初始化生命周期回调。
Spring 支持 构造方法注入、属性注入(Setter)、工厂方法注入,其中工厂方法注入,又可以分为 静态工厂方法注入和非静态工厂方法注入。
IOC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配。
Spring 中的单例 Bean不是线程安全的。
因为单例 Bean,是全局只有一个 Bean,所有线程共享。如果说单例 Bean,是一个 无状态的 ,也就是线程中的操作不会对 Bean 中的成员变量执行查询以外的操作,那么这个单例 Bean 是 线程安全 的。比如 Spring mvc 的 Controller、Service、Dao 等,这些 Bean 大多是无状态的,只关注于方法本身。
假如这个 Bean 是 有状态的 ,也就是会对 Bean 中的成员变量进行写操作,那么可能就 存在线程安全的问题 。
①将 Bean 定义为多例
这样每一个线程请求过来都会创建一个新的 Bean,但是不利于容器管理 Bean。
②在 Bean 对象中尽量避免定义可变的成员变量
③将 Bean 中的 成员变量保存在 ThreadLocal 中 ★。
我们知道 ThredLocal 能保证多线程下变量的隔离,可以在类中定义一个 ThreadLocal 成员变量,将需要的可变成员变量保存在 ThreadLocal 里,这是推荐的一种方式。
①实现ApplicationListener接口;
②实现InitializingBean接口;
③使用@PostConstruct注解。
单例bean初始化要经过3步:实例化、属性赋值、初始化。 注入就发生在属性赋值过程中,Spring通过 三级缓存解决循环依赖 问题。
不行,主要是为了生成代理对象。如果是没有代理的情况下,使用二级缓存解决循环依赖也是 OK 的。但是如果存在代理,三级没有问题,二级就不行了。
因为三级缓存中放的是生成具体对象的匿名内部类,获取 Object 的时候,它可以生成代理对象,也可以返回普通对象。使用三级缓存主要是为了保证不管什么时候使用的都是一个对象。
假设只有二级缓存的情况,往二级缓存中放的显示一个普通的 Bean 对象,Bean 初始化过程中,通过 BeanPostProcessor 去生成代理对象之后,覆盖掉二级缓存中的普通 Bean 对象,那么可能就导致取到的 Bean 对象不一致了。
实现@Autowired 的关键是: AutowiredAnnotationBeanPostProcessor
在 Bean 的初始化阶段,会通过 Bean 后置处理器BeanPostProcessor来进行一些前置和后置的处理。
实现 @Autowired 的功能,也是通过 后置处理器 来完成的。这个后置处理器就是 AutowiredAnnotationBeanPostProcessor。
Spring 在创建 bean 的过程中,最终会调用到 doCreateBean()方法,在 doCreateBean()方法中会调用 populateBean()方法,来为 bean 进行属性填充,完成自动装配等工作。
在 populateBean()方法中一共调用了两次后置处理器,第一次是为了判断是否需要属性填充,如果不需要进行属性填充,那么就会直接进行 return,如果需要进行属性填充,那么方法就会继续向下执行,后面会进行第二次后置处理器的调用,这个时候,就会调用到 AutowiredAnnotationBeanPostProcessor 的 postProcessPropertyValues()方法,在该方法中就会进行@Autowired 注解的解析,然后实现自动装配。
AutowiredAnnotationBeanPostProcessor实现了BeanPostProcessor接口,当 Spring 容器启动时,AutowiredAnnotationBeanPostProcessor将扫描 Spring 容器中所有 Bean,当发现 Bean 中拥有@Autowired 注解时就找到和其匹配(默认按类型匹配)的 Bean,并注入到对应的地方中去。
①构造函数(默认,使用反射机制)
②静态工厂
③实例工厂
④工厂对象(实现FactoryBean接口,指定泛型)
①类路径加载配置文件(ClassPathXmlApplicationContext)
②文件路径加载配置文件(FileSystemXmlApplicationContext)
①使用bean名称
②使用bean名称,同时指定类型
③使用bean类型
AOP: 面向切面编程。简单说,就是 把一些业务逻辑中的相同的代码抽取到一个独立的模块中 ,让业务逻辑更加清爽,如常见的日志记录和数据校验等。
AOP 的核心其实就是 动态代理,如果是 实现了接口的话就会使用 JDK 动态代理,否则使用 CGLIB 代理 ,主要应用于处理一些具有横切性质的系统级服务,如日志收集、事务管理、安全检查、缓存、对象池管理等。
AOP一般有5种环绕方式:前置通知(@Before)、返回通知 (@AfterReturning)、异常通知(@AfterThrowing)、后置通知(@After)、环绕通知(@Around)。
注意: 多个切面的情况下,可以通过 @Order 指定先后顺序,数字越小,优先级越高。
SpringBoot 项目中,常使用 AOP 打印接口的入参和出参日志,以及执行时间等。
使用步骤: ①引入依赖 --> ②自定义一个注解作为切点 --> ③配置AOP切面类 --> ④需要使用AOP时直接加上注解即可。
JDK 动态代理 和 CGLIB 动态代理。
Spring AOP
Spring AOP 属于 运行时增强,主要具有如下特点:
① 基于 动态代理 来实现,默认如果使用接口的,用 JDK 提供的动态代理实现,如果是方法则使用 CGLIB 实现。
② Spring AOP 需要 依赖 IOC 容器来管理,并且只能作用于 Spring 容器,使用纯 Java 代码实现。
③ 在性能上,由于 Spring AOP 是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得 Spring AOP 的性能不如 AspectJ 的那么好。
④ Spring AOP 致力于解决 企业级 开发中最普遍的 AOP(方法织入)。
AspectJ
① AspectJ 是一个易用的功能强大的 AOP 框架,属于 编译时增强, 可以单独使用,也可以整合到其它框架中,是 AOP 编程的完全解决方案。AspectJ 需要用到单独的编译器 ajc。
② AspectJ 属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的,一般有如下几个织入的时机:
两者的主要区别在于解决问题的方式不同:
OOP和AOP联系: 两者之间是一个相互补充和完善的关系。
总的来说:
AOP是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性。 主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
OOP针对业务处理过程的实体及其属性和行为进行抽象封装,以获得更加清晰高效的逻辑单元划分。
Spring 事务的本质其实就是 数据库对事务的支持 ,没有数据库的事务支持,Spring 是无法提供事务功能的。Spring 只提供 统一事务管理接口 ,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过数据库自己的事务机制实现。
Spring支持 编程式事务管理 和 声明式事务管理 。
编程式事务: 编程式事务管理使用 TransactionTemplate,需要 显式执行事务。
声明式事务: 声明式事务管理建立在 AOP 之上的。其本质是通过 AOP 功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务 ,在执行完目标方法之后根据执行情况提交或者回滚事务。优点是不需要在业务逻辑代码中掺杂事务管理的代码,只需在 配置文件中做相关的事务规则声明或通过 @Transactional 注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,声明式事务最细粒度 只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。
就是通过 AOP/动态代理 。
Spring事务的隔离级别对应数据库的事务隔离级别,以MySQL数据库为例,主要包括:读未提交、读已提交、可重复读、串行化。
Spring 事务的传播机制说的是,当多个事务同时存在的时候,一般指的是 多个事务方法相互调用时,Spring 如何处理这些事务的行为。
事务传播机制是使用简单的 ThreadLocal 实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。
Spring 默认的事务传播行为是 PROPAFATION_REQUIRED 。
@Controller:组合注解(组合了@Component注解),应用在MVC层(控制层)。
@RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
@RequestMapping:用于映射Web请求,包括访问路径和参数。如果是Restful风格接口,还可以根据请求类型使用不同的注解:@GetMapping、@PostMapping、@PutMapping、@DeleteMapping。
@ResponseBody:支持将返回值放在response内,而不是一个页面,通常用户返回 json 数据。
@RequestBody:允许request的参数在request体中,而不是在直接连接在地址后面。
@PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为Restful的接口实现方法。
@RestController:该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
@Component:表示一个带注释的类是一个“组件”,成为Spring管理的 Bean。当使用基于注解的配置和类路径扫描时,这些类被视为自动检测的候选对象。同时@Component还是一个元注解。
@Service:组合注解(组合了@Component注解),应用在service层(业务逻辑层)。
@Repository:组合注解(组合了@Component注解),应用在dao层(数据访问层)。
@Autowired:Spring 提供的工具(由Spring的依赖注入工具(BeanPostProcessor、BeanFactoryPostProcessor)自动注入)。
@Qualifier:该注解通常跟@Autowired一起使用,当想对注入的过程做更多的控制,@Qualifier可帮助配置,比如两个以上相同类型的Bean时Spring无法抉择,用到此注解。
@Configuration:声明当前类是一个配置类(相当于一个 Spring 配置的 xml 文件)
@Value:可用在字段,构造器参数跟方法参数,指定一个默认值,支持 #{} 跟 ${} 两个方式。一般将 SpringBoot 中的 application.properties 配置的属性值赋值给变量。
@Bean:注解在方法上,声明当前方法的返回值为一个Bean。返回的Bean对应的类中可以定义init()方法和destroy()方法,然后在@Bean(initMethod=”init”,destroyMethod=”destroy”)定义,在构造之后执行init,在销毁之前执行destroy。
@Scope:定义我们采用什么模式去创建Bean(方法上,得有@Bean) 其设置类型包括:Singleton 、Prototype、Request 、 Session、GlobalSession。
@Aspect:声明一个切面(类上) 使用@After、@Before、@Around定义建言(advice),可直接将拦截规则(切点)作为参数。
@After:在方法执行之后执行(方法上)。
@Before:在方法执行之前执行(方法上)。
@Around:在方法执行之前与之后执行(方法上)。
@PointCut:声明切点 在 java 配置类中使用@EnableAspectJAutoProxy 注解开启 Spring 对 AspectJ 代理的支持(类上)。
@Transactional:在要开启事务的方法上使用@Transactional 注解,即可声明式开启事务。
声明bean的注解:@Component(通用注解方式)、@Service(业务层注解)、@Repository(数据层注解)、@Controller(表现层注解)。
注入bean的注解: @Autowired(Spring提供)、@Qualifier(配和@Autowired使用)、@Inject、@Resource 。
配置类相关注解:@Configuration(声明当前类为配置类,相当于xml形式的Spring配置(类上))、@Bean(注解,方法上,声明当前方法的返回值为一个bean,替代xml中的方式(方法上))、@Configuration(声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个bean(类上))、@ComponentScan(用于对Component进行扫描,相当于xml中的(类上))。
AOP相关注解:@Aspect(声明当前类为切面类(类上))、使用@After、@Before、@Around定义通知(advice),可直接将拦截规则(切点)作为参数。(@After 在方法执行之后执行(方法上)、@Before 在方法执行之前执行(方法上)、@Around 在方法执行之前与之后执行(方法上))、@PointCut(声明切点)、在java配置类中使用@EnableAspectJAutoProxy注解开启Spring对AspectJ代理的支持(类上)。
Bean属性设置注解:@Scope(bean的生命周期)、@PostConstruct(在构造方法和init方法(如果有的话)之间得到调用,且只会执行一次。等价于xml配置文件中bean的initMethod)、@PreDestory(在Bean销毁之前执行,等价于xml配置文件中bean的destroyMethod)
@Component 和 @Bean是两种使用注解来 定义bean的方式,都可以使用@Autowired或者@Resource注解注入。
如果想将 第三方的类变成组件,你又没有没有源代码,也就没办法使用@Component进行自动配置,这种时候 使用@Bean 就比较合适了。不过同样的也可以通过xml方式来定义。
另外 @Bean注解的方法返回值是对象 ,可以在方法中为对象设置属性。
此外Spring的Starter机制,就是通过@Bean注解来定义bean。
可以搭配@ConditionalOnMissingBean注解 @ConditionalOnMissingClass注解,如果本项目中没有定义该类型的bean则会生效。避免在某个项目中定义或者通过congfig注解来声明大量重复的bean。