POJO :普通的 java 类
MVC:将软件划分为模型,视图,控制器划分的架构。
- M:model,模型层。指工程中的 JavaBean(JavaBean :主要用来传递数据,即把一组数据组合成一个类便于传输 )
- 一类 JavaBean 称为实体类 Bean :存储业务数据(Student,User等)。
- 一类被称为业务处理 Bean :指的就是 Service 或者 Dao (DataAccessobjectsbase)对象,专用于处理业务逻辑和数据访问
- V:View,视图层。指工程中的 JSP 或者 HTML 等页面,作用是与用户交互,展示数据。
- C:Controller,控制层。指工程中的 Servlet,作用是接收请求和相应服务器。即在 V 与 M 之间起到连接作用。
而又有一种经典的三层分层结构。三层架构分为
- 表述层(或表示层):,表述层表示前台页面和后台 Servlet
- 业务逻辑层:Servlet传来的数据进行处理的部分,Servlet 一般只是作为一个传递者作用。
- 数据访问层:该层接收业务逻辑层的指令、数据,对数据库进行增删改查。
SpringMVC:为Spring表达层开发提供一套完备的解决方案。在表述层框架历经Struts、WebWork、Struts2等诸多产品迭代后,的一种普遍使用的表述层开发首选方案。
流程:
另外:前端控制器 所能处理的请求中,jsp不会被 SpringMVC 处理,因为 jsp 本质就是一种 Servlet ,需要服务器的特殊指定的 Servlet处理。
目录
一、SpringBoot 特点
1.1 依赖管理
parent
maven中的父项目一般都是用来作为依赖管理的。子项目继承了父项目后,依赖将不再需要版本号。
我们查看我们导入的 starter 场景启动器会发现它也是一个继承父类的项目。
本项目的父项目:
通过按住 ctrl 键点击,我们可找到其父项目。
父项目的父项目:
再次点入会发现,其几乎声明了开发中我们常用的jar包版本号。
因此我们的 spring boot 相关依赖,不再需要在关注版本号,这就是它的自动版本仲裁 机制。
如果jar不在范围内,仍然需要些版本依赖。
如果我们想要修改jar包而不用自动仲裁的部分,我们需要在我们 pom.xml 中写入如下代码:
<project>
...
<properties>
...
<property>
<!--如果我们对MySQL版本不满意
--我们就需要在父类中,找到MySQL对应的key
--然后在我们当前项目中,写入我们想要的版本进行重写
--这是maven的特性,就近使用设置的版本号-->
<mysql.version>5.1.43</mysql.version>
</property>
</properties>
</project>
starter
我们在项目中还引入了一个依赖:
starter 是一组依赖的集合描述。
一般来说,一个starter的引入,代表着某个场景的全部依赖被导入。
命名规范:
-
官方 :spring-boot-starter-*
-
第三方: *-spring-boot-starter
所有场景启动器最底层的依赖(自动配置核心依赖)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.6.5</version>
</dependency>
1.2 自动配置
- 自动配好了 Tomcat
- 引入 Tomcat 依赖
- 配置 Tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.6.5</version>
</dependency>
-
自动配好了MVC
- 引入了 SpringMVC 全套组件
- 自动配好了 SpringMVC 常用组件(功能)
-
配好的 Web 常见功能,如:字符编码
- SpringBoot 帮助我们配置好了所有 Web 开发的场景
-
默认的包结构
- 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
- 无需像以前一样对包扫描进行配置
- 当然也可以配置扫描范围,通过修改下列代码:
@SpringBootApplication(scanBasePackages="路径")
- 或者用下列代码指定扫描路径
@ComponentScan("包路径") /* *但当你要单独使用时,@ComponentScan与@SpringBootApplication不能同时存在 *因为其中也含有@ComponentScan,这样会导致重复。 *此外也不能不写@SpringBootApplication,因此需要将@SpringBootApplication中包含的其它注释也单独写 */
-
各种配置拥有默认值
- 默认配置最终都是映射到 MultipartProperties
- 配置文件的值最终会绑定到某个类上,这个类会在容器中创建对象
-
按需加载所有自动配置项
- 非常多的starter 不会同时被配置
- 引入哪些场景,哪些场景的自动配置才会开启
- 所有自动配置功能都在
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> <version>2.6.5</version> </dependency>
-
等
二、SpringApplication
2.1SpringApplication的初始化
- SpringApplication的构造方法
-
deduceMainApplicationClass方法
通过异常信息(new runtimeException().getStackTrace())判断main函数,找到具体的类,返回。 -
getSpringFactoriesInstances方法
- loadFactoryFactoryNames方法
- loadSpringFactories方法
源码
/*
* 初始化时,这里时,先调用下方代码
*/
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
/*
*随后,调用下方代码
*此时resourceLoader为空
*获取
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList<>(
getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
2.2 Run方法
基础信息
源码
ic ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try{
...
}
}
try部分
//main参数作为ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
//将所有我们获得的boot相关数据封装起来,并存入。方便读取和加载。
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
//springboot的图标打印
Banner printedBanner = printBanner(environment);
//现阶段Application上下文对象创建
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
/*将我们的boot上下文,application上下文,boot环境,监听器、args参数,springboot的图标
/*prepareContext
*在其中进行了,
*将context设置了环境environment
*然后通过postProcessApplicationContext处理context
*应用于初始化器中,为其初始化。initializer.initialize(context);
*告知观察者准备完成listeners.contextPrepared(context)
*在bootstrapContext中关闭该对象(因为完成了)bootstrapContext.close(context);
*等操作
*/
/*
*ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
*这里获取了beanFactory,并在之后注册了三个BeanFacotryPostProcessor
* 以及beanFactory在此处被强转为DefaultListableBeanFactory类,进行了相应的配置,方便日后使用
*Set<Object> sources = getAllSources();
*我们在application初始化的primarySource(启动类)会被getAllSources()调用,方便之后解析启动类注解
*load(context, sources.toArray(new Object[0]));
*其中会创建一个BD(BeanDefinition)加载器,BS保存了Bean的定义信息,属性配置
*通过加载器加载启动类,匹配类的类型然后加载
*判断是否有@Component注解(启动类肯定有,不过被包含在其他注解里面了)
*如果可以找到,会被注册到annotatedReder,完成标识,方便之后调用
*/
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
/*
* 有refresh前缀,一定会调用refresh(核心)方法
* 用于Bean的创建
* 里面就是Spring部分
* 见后面
*/
refreshContext(context);
afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);
}
/*
* 告知已启动
*/
listeners.started(context, timeTakenToStartup);
/*
*runners.addAll(context.getBeansOfType(ApplicationRunner.class).values())
*runners.addAll(context.getBeansOfType(CommandLineRunner.class).values())
* 执行context中,ApplicationRunner,CommandLineRunner下的对象。此处是取出来,之后通过循环执行它们的run方法,参数就是applicationArguments中的参数
*/
callRunners(context, applicationArguments);
/*记录就绪消耗的时间*/
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
/*
* 告知就绪
*/
listeners.ready(context, timeTakenToReady);
2.3 refresh方法
从此处开始,类似于Spring的生成流程。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
StartupStep contextRefresh = this.applicationStartup.start("spring.context.refresh");
// Prepare this context for refreshing.
/*
* 内部调用了父类的prepareRefresh()
* 设置启动时间
* 设置活跃状态
* 日志
* 初始化资源配置
* 获取环境对象
* 等操作
* 完成准备工作
*/
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
/*
* 获取一个Bean工程(也即是在prepareContext方法中,我们的beanFactory)
* 在记录中,其名为DefaultListableBeanFactory
*/
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
/*
* 设置工厂的属性值等
*/
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
/*
* 向工厂中添加和设置额外的东西
* 为何不一次性增加完?
* post一般就是作为扩展预留,方便在以前的基础上增添新的东西
* 或作为在基础功能上,作为功能增强(额外)部分,使用时并不一定需要用到
* 此外将功能分为多块,方便维护
*/
/*
*如果只有Spring,此处应该是空方法。
*/
postProcessBeanFactory(beanFactory);
/*
* Create a new step and marks its beginning.
* <p>A step name describes the current action or phase. This technical
* name should be "." namespaced and can be reused to describe other instances of
* the same step during application startup.
* @param name the step name
*/
/*
* 创建一个新的步骤,并标记它的开始
*/
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
// Invoke factory processors registered as beans in the context.
/*
* Instantiate and invoke all registered BeanFactoryPostProcessor beans,
* 核心,其中处理了注释
* 实例化并执行所有注册在beanfactory的BeanFactoryPostProcessor beans
*/
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
/*
* Instantiate and register all BeanPostProcessor beans,
* 先向注册beanfactory后实例化所有的BeanPostProcessor bean
*/
registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
// Initialize message source for this context.
/*
* 国际化操作,i18n
*/
initMessageSource();
// Initialize event multicaster for this context.
/*
* 初始化事件多波器,监听事件的广播
*/
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
/*
*Spring中应该是空方法
* 调用父类的onRefresh,注册主宾信息
* 随后 createWebServer(),服务器源码在里面
*/
onRefresh();
// Check for listener beans and register them.
/*
* 注册监听器信息
*/
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
/*
* 完成bean对象的实例化
* 其中可以找到getBean,createBean,createBeanInstance等方法
* 与Spring创建Bean的流程大致一致
*/
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
contextRefresh.end();
}
}
上述为大流程。与Spring类似。
invokeBeanFactoryPostProcessors
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
/*
*于此处的invokeBeanFactoryPostProcessors在往下翻阅则是实际处理流程
*篇幅太长,不列出
*/
/*
* 它要执行所有的BeanFactoryPostProcessors
*
* 虽然很长,但他很大一部分,都是如下流程:
* ------------------------------
* 类型X = beanFactory.getBeanNamesForType(XXXXX)|寻找
* ->for循环遍历类型X中的所有,对每一个判断,为true则添加进currentRegistryProcessors(Bean),processedBeans(名)
* ->sortPostProcessors|排序currentRegistryProcessors和beanFactory
* ->registryProcessors|注册currentRegistryProcessors
* ->invokeBeanDefinitionRegistryPostProcessors|执行它们
* -- 实际由ConfigurationClassPostProcessor类的postProcessBeanDefinitionRegistry方法处理
* -- 在postProcessBeanDefinitionRegistry中会执行processConfigBeanDefinitions
* -- 来扫描,识别注解后,处理相应的注解(尤其是@import注解)
* ->currentRegistryProcessors.clear();|清空
* -------------------------
* 类型有三类,代表着执行顺序
* 先识别子类接口
* 再识别父类接口
* 最后识别无父子类接口
* 执行
*/
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
// (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
if (!NativeDetector.inNativeImage() && beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
}
}
processConfigBeanDefinitions对注解的扫描
此处笔者仅截取部分代码
configCandidates此处是我们的程序的启动类(主类)
...
// Return immediately if no @Configuration classes were found
//没有找到@Configuration就返回
if (configCandidates.isEmpty()) {
return;
}
// Sort by previously determined @Order value, if applicable
//如果有@Order,就按照其值排序
configCandidates.sort((bd1, bd2) -> {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return Integer.compare(i1, i2);
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
//检测通过外围应用程序上下文提供的任何定制bean名称生成策略
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {
sbr = (SingletonBeanRegistry) registry;
if (!this.localBeanNameGeneratorSet) {
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
//环境为空,创建新的
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// Parse each @Configuration class
//解析每一个有@Configuration的类
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
//存储相应的分类
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
do {
//同之前StartupStep的作用
StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");
//这里开始进一步解析
parser.parse(candidates);
....
}
...
步入parse
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
}
}
this.deferredImportSelectorHandler.process();
}
再步入parse
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
/*实际处理扫描
*判断是否用了一下注解,有就处理,无就跳过
*@PropertySource,@ComponentScan,@Import
*处理时,如果是内含有多层的话,就用递归扫描内部
*比如@Import,就需要识别里面的包、类
*/
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
三、自动装配流程
自动装配就是让应用程序上下文为你找出依赖项的过程。
标识装配位置:SpringMVC注释
装配过程:Spring
其中的主要类有:
- BFPP:BeanFactoryProcessor(接口)
- BPP:BeanPostProcessor
- BDRPP:BeanDefinitionRegistryPostProcessor(接口,实现了BFPP)
大致流程
- 当启动 SpringBoot 应用程序的时候,会先创建 SpringApplicationt 的的对象, 在对象的构造方法中进行参数初始化工作。
- 其中最主要的是判断应用程序的类型以及初始化器和监听器,在整个过程中,会加载 Spring.factories 文件,将文件内容放在缓存对象中,方便获取。
- SpringApplicationt 对象创建完成后,开始执行 run 方法。其中核心的方法为: prepareContext 和 refreshContext 。它们完成了自动装配的核心功能。同时也会创建上下文对象,banner的打印,异常报告的准备等其他准备工作,方便以后调用。
- prepareContext 完成了上下对象的初始化操作(属性值的设置,环境对象)。
- 其中的 load 方法将当前启动类作为一个 beanDefinition 注册到了 registry ,方便后续在进行 BeanFactoryProcessor调用执行的时候,找到主类,完成对注解的解析工作。(@SpringBootApplication)
- refreshContext 方法中会进行整个容器的刷新过程,完成装配。会调用 Spring 中的 refresh 方法,refresh 中有13个非常关键的方法,来完成整个Spring 应用程序的启动以及刷新。
- 其中会调用invokeBeanFactoryPostProcessor 方法。主要调用ConfigurationClassPostProcessor 类进行处理和执行(该类实现了BDRPP)。调用时,先调用它中的postProcessBeanDefinitionRegistry,随后调用postProcessBeanFactory。在执行postProcessBeanDefinitionRegistry时,会调用processConfigBeanDefinitions解析各种注解。比如:@PropertySource, @ComponentScan, @ComponentScans, @Bean, @Import(最重要)
- 在解析 @Import 时,会有getImports的方法。从主类,开始递归解析注解,把所有包含@Import的注解都解析到。随后在processImport方法中,对Import进行分类(主要是在识别时,将AutoConfigurationImportSelect(在其中将指定的一个包下的所有组件进行批量导入) 归属于ImportSelect的子类)后续过程中调用deferredImportSelectorHandler中的process方法,来完成EnableAutoConfiguration的加载。
- finishBeanFactoryInitialization中完成bean对象的实例化
- prepareContext 完成了上下对象的初始化操作(属性值的设置,环境对象)。
四、容器功能
Spring 容器看作 beans
4.1 Spring自动装配中的autowire
使用autowire,减少或者消除属性或构造器参数的设置。
如property、ref标签过多的问题。
在beans中使用default-autowire,设置全局bean
在bean中使用autowire,设置当前bean
分为byName 与byType
4.1.1 byName
在XML配置文件中bean的autowire属性设置为byName。
然后,装配时,会寻找与我们类的成员类名称相同(id)的bean中的一个进行匹配和连线。。如果找到匹配项,它将注入这些其中,否则,它将抛出异常。
无autowire
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="user_1" class="boot.entity.User">
<property name="name" value="张三"/>
<property name="age" value="18" />
<property name="cat" ref="cat" />
</bean>
<bean id="cat" class="boot.entity.Pet">
<property name="name" value="tomcat"/>
</bean>
</beans>
如下配置,在其他类中如果有,就会通过名称自动装配。
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="user_1" class="boot.entity.User" autowire="byName">
<property name="name" value="张三"/>
<property name="age" value="18" />
</bean>
<bean id="cat" class="boot.entity.Pet">
<property name="name" value="tomcat"/>
</bean>
</beans>
4.1.2 byType
在XML配置文件中bean的autowire 属性设置为 byType。
然后,装配时,会寻找成员类类型相同(class)的bean中的一个进行匹配和连线。如果找到匹配项,它将注入成员变量,否则,它将抛出异常。
无autowire
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="user_1" class="boot.entity.User">
<property name="name" value="张三"/>
<property name="age" value="18" />
<property name="cat" ref="cat" />
</bean>
<bean id="cat" class="boot.entity.Pet">
<property name="name" value="tomcat"/>
</bean>
</beans>
如下配置,在其他类中如果有,就会通过类型自动装配。
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="user_1" class="boot.entity.User" autowire="byType">
<property name="name" value="张三"/>
<property name="age" value="18" />
</bean>
<bean id="cat" class="boot.entity.Pet">
<property name="name" value="tomcat"/>
</bean>
</beans>
4.1.3 constructor
在XML配置文件中bean的autowire 属性设置为 constructor。
然后,装配时,会寻找与我们构造函数的参数名称相同(id)的bean中的一个进行匹配和连线。如果找到匹配项,则注入,否则,它会抛出异常。
注意,需要在当前类里准备一个构造方法(类似User(Cat cat,String name,int age){this.cat = cat;this.name = name;this.age = age;})
无autowire
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="user_1" class="boot.entity.User">
<constructor-arg ref="cat" />
<constructor-arg value="张三" />
<constructor-arg value="18" />
</bean>
<bean id="cat" class="boot.entity.Pet">
<property name="name" value="tomcat"/>
</bean>
</beans>
如下配置,在其他类中如果有,就会通过类型自动装配。
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="user_1" class="boot.entity.User" autowire="byType">
<constructor-arg value="张三" />
<constructor-arg value="18" />
</bean>
<bean id="cat" class="boot.entity.Pet">
<property name="name" value="tomcat"/>
</bean>
</beans>
4.2 组件添加
我们在 boot 包下,创建一个新包 entity ,在准备两个类 User 和 Pet 。
package boot.entity;
public class User {
private String name;
private Integer age;
/*
* 构造
*/
public User() {
}
public User(String name,Integer age) {
this.name = name;
this.age = age;
}
/*
* 方法
*/
@Override
public String toString() {
return "User{name='" + name + "',age=" + age.intValue() + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package boot.entity;
public class Pet {
private String name;
/*
* 构造
* */
public Pet(){
}
public Pet(String name){
this.name = name;
}
/*
*方法
* */
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Pet{name = '" + name +"'}";
}
}
4.2.1 使用Spring的装配方式(旧)
也即是使用 xml
如果我们想要将这两个类自动装配,用之前的IOC的方式。我们需要配置 .xml
创建如下位置的文件 bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="user_1" class="boot.entity.User">
<property name="name" value="张三"/>
<property name="age" value="18" />
</bean>
<bean id="cat" class="boot.entity.Pet">
<property name="name" value="tomcat"/>
</bean>
</beans>
创建如下位置的test.java文件
package boot;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import boot.entity.Pet;
import boot.entity.User;
public class test {
public static void main(String[] args) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("/beans.xml");
User user = (User)beanFactory.getBean("user_1");
System.out.println(user);
Pet pet = (Pet)beanFactory.getBean("cat");
System.out.println(pet);
}
}
如今更多的使用Config 方式。即通过注释与设置类的方式。
4.2.2 @Configuration
在 boot 包下,创建 config 包
package boot.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import boot.entity.Pet;
import boot.entity.User;
/*
* @Configuration
* 告知SpringBoot这是一个配置类 == 配置文件
* */
@Configuration
public class MyConfig {
/*
* 给容器添加组件,以方法名作为组件id,返回值为组件类型
* */
@Bean//此处组件名为User01
public User User01() {
return new User("张三 ",18);
}
@Bean("Tom")//此处组件名为Tom
public Pet tomcat() {
return new Pet("tomcat");
}
}
package boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import boot.config.MyConfig;
import boot.entity.Pet;
import boot.entity.User;
/*
*
*@SpringBootApplication:告知这是一个springboot应用
*主程序类
*/
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
//1.SpringApplication.run会返回我们的IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
//2.查看容器里面的组件
String[] names = run.getBeanDefinitionNames();
for(String name : names) {
System.out.println(name);
}
//3.从容器中获取组件
//此处你会发现,它默认时单例
Pet tom_01 = run.getBean("Tom",Pet.class);
Pet tom_02 = run.getBean("Tom",Pet.class);
System.out.println("组件 tom_01 == tom_02?" + (tom_01==tom_02));
User user_01 = run.getBean("User01",User.class);
User user_02 = run.getBean("User01",User.class);
System.out.println("组件 user_01 == user_02?" + (user_01==user_02));
//获取配置类
//配置类同时也是组件
MyConfig bean = run.getBean(MyConfig.class);
System.out.println(bean);
/*
*获取组件方法可写为
*User user_01 = bean.User01();
*Pet tom_01 = bean.tomcat();
**/
}
}
与SpringBoot1 不同
在SpringBoot2,多出了
@Configuration(proxyBeanMethods = true)
proxyBeanMethods :代理bean的方法
默认为true:
它使得,外部无论对配置类中的这个组件注册方法调用多少次,都是单实例对象。
即使用代理对象调用方法,有该代理对象则直接调用,无则创建。以此保持组件单实例。
改为false后,不再相等。
因此此多出了
Full (proxyBeanMethods = true):全模式
Lite (proxyBeanMethods = false):轻量级模式
与此相关的就有组件依赖问题。
( Spring 三级缓存解决单例下的循环依赖)
4.2.3 @Component
在类路径扫描期间, SpringBoot 找到装饰有 @Component 的Java 类,并在上下文中注册为 Bean。我们不在设置类中通过 @Bean 去添加它们。
也就是说被标识后,还需要被扫描后,才能作为一个组件被管理。
同时默认也是单实例。可以用 @Scope修改
package boot.entity;
import org.springframework.stereotype.Component;
@Component
/*
* 设置作用域,默认也是单实例
* @Scope("prototype"),为原型,即每次会创建新的对象
*/
@Scope("singleton")//单实例
public class Pet {
private String name;
/*
* 构造
* */
public Pet(){
}
public Pet(String name){
this.name = name;
}
/*
*方法
* */
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Pet{name = '" + name +"'}";
}
}
package boot.entity;
import org.springframework.stereotype.Component;
@Component
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name,Integer age) {
this.name = name;
this.age = age;
}
/*
* 方法
*/
@Override
public String toString() {
return "User{name='" + name + "',age=" + age.intValue() + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
4.2.4 @Autowired
@Autowired可以用于单独注释setter,构造函数,成员变量、有任意名称和多个参数的方法等。或者混合注释成员变量和构造函数。
当将 @Autowired 注解添加到需要该类型数组的字段或方法,则会从 (Configuration)ApplicationContext中搜寻符合指定类型的所有bean。
有四个模式:
- byName
- byType
- constructor
- autodectect:自省机制,判断用constructor还是byType
@Autowired(required=true)
内部有一个required,默认为true。表示解析被标记的字段或方法,一定有对应的bean存在。为false时,没有对应的bean存在不会报错。
优先用byType,但需要多个相同类的实例时,再用byName细分。
推断构造方法
- 在有多个构造方法时,且没有用**@Autowired**标注用哪一个,会优先用无参构造。
- 在有多个构造方法时,且没有用**@Autowired**标注用哪一个时,且无无参构造时,会报错。
- 在只有一个构造函数时,且没有用**@Autowired**标注用哪一个时,使用唯一的构造函数
- 在有**@Autowired**标注时,但标注了多个构造方法,也会报错。
4.2.5 @Controller
用于标注控制层组件。
SpringMVC虽然有前端控制器对请求统一处理了,但是需要创建处理具体请求的类请求控制器。
请求控制器中的每一个处理请求的方法,成为了控制器方法。
因为 SpringMVC 的控制器由一个POJO担任,因此需要通过 @Controller 注解将其标识为一个控制层组件,交由IoC容器管理
。
被标识且被扫描后,才能作为一个组件管理。
4.2.6 @Service
@Service(value = “”),不填value则是用默认命名方式。标注在类上,用于标注业务层组件。
并将创建好的对象直接注入给Action(Controller)使用。
通过从容器中获取的方式使用。默认命名,需要XX.class方式。
getBean("XX");
getBean(XX.class)
被标识且被扫描后,才能作为一个组件管理。
4.2.7 @Repository
@Repository(value=“”),不填value则是用默认命名方式。注释在类上,用于标注数据访问组件,即DAO组件。
Service层使用@Resource注解(标注在成员类上)告诉SpringBoot,然后它把创建好的Dao注入给Service即可。
也可以@Resource(name=“”)用于指定具体的实例。
使用方式都是
被标识且被扫描后,才能作为一个组件管理。
4.2.8 @Required
注解应用于 bean 属性的 setter 方法,它表明受影响的bean属性在配置时必须放在 XML配置文件中,否则容器就会抛出一个BeanInitializationException异常。
4.2.9 @Qualifier
当创建多个具有相同类型的bean时,我们想要指定当前的一个成员变量用哪一个实例时,我们 同时使用 @Autowired与 @Qualifier 来指定实例名。
因此,某种意义上,@Qualifier(“id”),就是byName。
4.2.10 @Import
@Import(User.class,Cat.class)
通过上述方式,给容器中自动创建出这两个类型的组件。名字默认为全类名。
4.2.11 @Conditional
满足指定条件则装配。
@Conditional
(value = { null })
比如
package boot.config;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import boot.entity.Pet;
import boot.entity.User;
@Configuration(proxyBeanMethods = false)
public class MyConfig {
@Bean
public Pet cat() {
return new Pet("tomcat");
}
@ConditionalOnBean(name = "cat")//有叫cat的Bean时,我们才装配user1
@Bean
public User user1() {
return new User("zhangsan",18);
}
}
package boot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.web.filter.FormContentFilter;
import boot.entity.Pet;
import boot.entity.User;
@SpringBootApplication
public class MainApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
boolean user = run.containsBean("user1");
boolean cat = run.containsBean("cat");
System.out.println(user);
System.out.println(cat);
}
}
4.3 原生文件导入
用于引入xml文件。
@ImportResource(“classpath:beans.xml”)//在resources下
4.4 配置绑定
将Properties代码绑定到JavaBean中。
4.4.1 @Component+@ConfigurationProperties
在applicatio.properties里,添加如下配置
user2.name=xiaoming
user2.age=20
User类
package boot.entity;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix="user2")//查找前缀为user2的配置
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name,Integer age) {
this.name = name;
this.age = age;
}
/*
* 方法
*/
@Override
public String toString() {
return "User{name='" + name + "',age=" + age.intValue() + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
再用一个Controller
package boot.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import boot.entity.User;
@Controller
public class test {
@Autowired
User user;
@ResponseBody
@RequestMapping("/")
public String testFwd(){
return user.toString();
}
}
4.4.2@EnableConfigurationProperties+@ConfigurationProperties
package boot.config;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import boot.entity.User;
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(User.class)//开启User的组件配置功能
public class MyConfig {
}
package boot.entity;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix="user2")//查找前缀为user2的配置
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name,Integer age) {
this.name = name;
this.age = age;
}
/*
* 方法
*/
@Override
public String toString() {
return "User{name='" + name + "',age=" + age.intValue() + '}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
package boot.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import boot.entity.User;
@Controller
public class test {
@Autowired
User user;
@ResponseBody
@RequestMapping("/")
public String testFwd(){
return user.toString();
}
}