目录
Spring的两个核心概念
- 控制反转(IoC)
- 面向切面编程(AOP)
全注解下的Spring IOC
IOC简介
Spring IOC是一个管理Bean的容器,所有Ioc都要实现BeanFactoty接口,它是一个顶级容器接口。
isSingleton() 方法判断Bean对象是否是单例,如果是单例,getBean()方法返回的都是同一个对象
isPrototype()方法与isSingleton()方法相反,如果不是单例,调用getBean()时,Spring Ioc容器会创建一个的Bean返回给调用者。
实现中我们使用的大部分Spring Ioc容器是实现ApplicatuionContontext接口。
(ApplicatuionContontext是BeanFactoty的子接口之一,其扩展了BeanFactoty的方法,功能更强大)
装配Bean
如何将Bean装配到Ioc容器中
①Component
ackage com.example.demo.testbean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("user")
public class User {
@Value("1")
private Long id;
@Value("user_name_1")
private String username;
@Value("note_1")
private String note;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
②
@Configuration
@ComponentScan
package com.example.demo.testbean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan
public class AppConfig {
}
@ComponentScan 默认只会扫描类AppConfig所在的当前和其子包,
@ComponentScan还可以我们自定义扫描的包。
例如:
③测试
package com.example.demo.testbean;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class IocTest {
public static void main(String [] args){
AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
User user=ctx.getBean(User.class);
System.out.println(user.getId());
}
}
依赖注入(Dependency Injection ,DI)
将Bean装配到Ioc容器后中后,如何获取,即Bean之间的依赖
在Spring IoC的概念中,称为依赖注入
@Autowired
@Autowired会根据属性的类型找到对应的Bean进行注入
上述例子中Spring Ioc容器会把Dog的实例注入到BussinessPerson中。
这样通过Spring Ioc容器获取BussinessPerson实例的时候就能够使用Dog实例来提供服务了。
注解@Autowired
如果 Cat类和Dog类都实现Animal接口
那么写如下注入会出现问题
这是因为
Spring Ioc容器并不知道你需要注入什么动物(是狗?是猫?)给BussinessPerson类对象,从而引起错误。
使用如下方法解决这个问题
将animal修改为dog:
这是为什么呢?
因为@Autowired提供这样的规则,首先它会根据类型找到对应的Bean,如果对应类型的Bean不是唯一的,那么他会根据其属性名称(例如上面的dog)和Bean的名称进行匹配。如果匹配得上,就会使用该Bean;如果还无法匹配,就会抛出异常。
消除歧义性——@Primary和@Quelifier
还是上述问题
另一种解决方法
使用@Primary
但是如果Ca和Dog类上都有@Primary注解仍然无法区分
另一种解决方法
@Quelifier的配置项value需要一个字符串去定义,它将与@Autowires组合在一起,通过类型和名称一起找到Bean。我们知道Bean名称在Spring Ioc容器中是为的的标识,通过这个就可以消除歧义性了。
这是怎么实现的呢???
因为BeanFactory接口中有如下方法。
带有参数的构造方法类的装配
生命周期
四个部分:
- Bean的定义
- Bean的初始化
- Bean的生存期
- Bean的销毁
Bean定义
Bean初始化
ComponentScan中海油一个配置项lazyInit,只可以配置Boolean值。且默认为false,也就是默认不进行延迟初始化,因此默认情况下Spring 会对Bean进行实例化和依赖注入对应的属性值。
测试;
在断点出,我们并没有获取Bean的实例,而日志就已经打出了,可见它在Spring Ioc 容器初始化时就执行了实例化和依赖注入。
为了 让当我们取出来的时候才做实例化和依赖注入等操作。
可以:
Spring在完成依赖注入之后,还会进行以下流程来完成它的生命周期。
整个生命中周期如下
对于没有实现ApplicationContext接口只实现BeanFactory接口的容器,在生命周期对应的ApplicationContextAware定义的方法不能被调用。
Spring IoC容器最低要求是实现BeanFactory接口
测试代码
①定义一个bean类
@Component
public class MyBean implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanFactoryAware的setBeanFactory");
}
@Override
public void destroy() throws Exception {
System.out.println("【" + this.getClass().getSimpleName() + "】DisposableBean方法");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("【" + this.getClass().getSimpleName() + "】调用InitializingBean的afterPropertiesSet");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
System.out.println("【" + this.getClass().getSimpleName() + "】调用ApplicationContextAware的setApplicationContext");
}
@Override
public void setBeanName(String name) {
System.out.println("【" + this.getClass().getSimpleName() + "】调用BeanNameAware的setBeanName");
}
@PostConstruct
public void init(){
System.out.println("【" + this.getClass().getSimpleName() + "】注解@PostConstruct定义的自定义初始化方法");
}
@PreDestroy
public void destroy1(){
System.out.println("【" + this.getClass().getSimpleName() + "】注解@PreDestroy定义的自定义销毁方法");
}
}
②定义一个处理类,涉及上图中postProcessBeforeInitialization(预初始化)、postProcessAfterInitialization(后初始化)过程。
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用postProcessBeforeInitialization方法,参数【" + bean.getClass().getSimpleName() + "】【" + beanName + "】");
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeanPostProcessor调用postProcessAfterInitialization方法,参数【" + bean.getClass().getSimpleName() + "】【" + beanName + "】");
return bean;
}
}
③运行结果如下
BeanPostProcessor调用postProcessAfterInitialization方法,参数【ServletWebServerFactoryConfiguration$EmbeddedTomcat$$EnhancerBySpringCGLIB$$4506f63e】【org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat】
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【ServletWebServerFactoryConfiguration$EmbeddedTomcat$$EnhancerBySpringCGLIB$$4506f63e】【org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryConfiguration$EmbeddedTomcat】
BeanPostProcessor调用postProcessAfterInitialization方法,参数【TomcatServletWebServerFactory】【tomcatServletWebServerFactory】
...
省略部分其它Bean的预初始化和后初始化日志。
BeanPostProcessor调用postProcessAfterInitialization方法,参数【BaseConfig$$EnhancerBySpringCGLIB$$ec4dd4fe】【baseConfig】
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【BaseConfig$$EnhancerBySpringCGLIB$$ec4dd4fe】【baseConfig】
【MyBean】调用BeanNameAware的setBeanName
【MyBean】调用BeanFactoryAware的setBeanFactory
【MyBean】调用ApplicationContextAware的setApplicationContext
BeanPostProcessor调用postProcessAfterInitialization方法,参数【MyBean】【myBean】
【MyBean】注解@PostConstruct定义的自定义初始化方法
【MyBean】调用InitializingBean的afterPropertiesSet
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【MyBean】【myBean】
BeanPostProcessor调用postProcessAfterInitialization方法,参数【WebConfig$$EnhancerBySpringCGLIB$$b79cfd7d】【webConfig】
BeanPostProcessor调用postProcessBeforeInitialization方法,参数【WebConfig$$EnhancerBySpringCGLIB$$b79cfd7d】【webConfig】
【MyBean】注解@PreDestroy定义的自定义销毁方法
【MyBean】DisposableBean方法
使用属性文件
application.properties或自定义配置文件
application.properties
属性文件依赖
有了这个依赖,就可以直接使用application.properties文件了
例如:
引用pplication.properties文件的方法如下
通过@Value注解,使用${…}这样的占位符读取配置文件在属性文件的内容。
@ConfigurationProperties
使用载入属性文件
将配置从application.properties中迁移到jdbc.properties中,然后使用@ProperSourse去定义对应的属性文件,把他加载到Spring上下文。
value可以配置多个文件。使用classpath前缀,意味着去类文件路径下找到属性文件。
ignoreResourseNotFound则是是否忽略配置文件找不到的问题。默认为false,找不到会报错。
true,找不到就忽略掉。
条件装配Bean
满足一定条件才进行bean的装配
@Conditional
这里加入了@Conditional,并且配置了类DataConditional,那么这个类就必须实现Condition接口,对于这个接口,要求实现matches方法
matches方法首先读取其上下文环境,然后判定是否配置了对应的数据库信息。这样,这些都已经配置好后则返回true。这时候,Spring会装配数据库连接池的Bean,否则是不安装配的。
Bean的作用域
使用@Profile
项目往往要面临
开发环境、测试环境、准生产环境、生产环境
为了方便Spring 提供了Profile机制,使我们可以很方便地实现各个环境之间的切换。
引入XML配置Bean
@ImportResourse
使用Spring EL
${…} 代表占位符 ,它将读取上细纹的属性值装配到属性中。
#{…}代表启用spring表达式,它将具有运算功能
T(…)代表引入的类
System是java.lang.*包的类,这是java默认加载的包,因此可以不必写全限定名,如果其他包需要写出全限定名。
开始约定编程——Spring AOP
AOP:面向切面编程
约定是Spring AOP的本质。
实际上Spring AOP 是一种约定流程的编程。
AOP概念
为什么会用AOP
AOP最典型的应用实际就是数据库事务的管控。
例如,当我们需要保存一个用户时,可能要连同它的角色信息一并保存到数据库中。
这里的用户信息和用户角色信息,我们可以使用面向对象编程OOP进行设计,但是它们在数据库事务中的要求是,要么一起成功,要么一起失败,这样OOP就无能为了了。
AOP可以解决这个问题。
AOP还可以减少大量的重复工作。在Spring流行之前,我们可以使用JDBC代码来实现很多的数据库操作。例如
Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。
从上图可以看到,关于数据库的打开和关闭以及事务的提交和回滚都有流程默认给你实现。换句话说,你不需要完成它们,系需要完成的完成的任务是编写SQL这一步而已,然后织入流程中。于是你可以看到大量在工作中的雷士局域Spring开发的代码。
这里看到仅仅使用一个注解@Transactional,表明该方法需要食物运行,没有任何数据库打开和关闭的代码,也没有事务的提交和回滚代码,却实现了数据库资源的打开和关闭、事务的回滚和提交。那么Spring是怎么做到的呢?
大致流程是:Spring帮助我们把insertUser方法织入类似于上图的流程中。
So:Spring AOP可以处理一些无法使用OOP实现的业务逻辑。其次,通过约定,可以将一些业务逻辑织入到流程中,而且可以将一些通用的逻辑抽取出来,然后给予默认实现。后面的数据库事务和Redis的开发中,可以再次见识到它的威力。
AOP术语和流程
连接点(join point):对应的是具体被拦截的对象(Spring 支持方法)
切点(point cut):有时候,切面不单单应用于单个方法,也可能是多个类的不同方法,这时,可以通过正则式和指示器的规则去定义,从而适配连接点。切点就是提供这样一个功能的概念。
通知(advice):
就是按照约定的流程下的方法,分为前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)、事后返回通知(afterReturning advice)、异常通知(afterThrowing advice),它会根据约定织入流程中。
目标对象(target):被代理对象。
引入(introdution):指引入新的类和其方法,增强现有Bean的功能。
织入(weaving):它是一个动态代理技术,委员由的服务对象生成代理对象,然后将与切入点定义匹配的连接点拦截,并按约定将各类通知织入流程的过程。
切面(aspect):是一个可以定义切点、各类通知和引入的内容。Spring AOP将通过它的信息来增强Bean的功能或者将对应的方法织入流程。
AOP开发详解
@AspectJ