参考javaguide的学习笔记~
1 怎么那么多名字呀~
一开始看到这个图太劝退了,但实际上一开始只需要理解它是一个框架,包含很多个模块,能够简化开发。
使得接收HTTP请求,查数据库,写业务逻辑三部分能够分开。
并且能很方便地完成一些共同的逻辑。
2 IOC
2.1 Why?为什么要交给外部管理?
IoC(Inversion of Control)的思想是,将原本在程序中手动创建对象的控制权,交给Spring框架来管理。
- 控制:某一个类A创建另一个类的对象B,并对其进行管理的权力
- 反转:把这种控制权交给外部环境,示例化和管理由外部环境完成(Spring框架、IoC容器)
如上图所示,传统的开发方式往往是在类A中通过new关键字来new一个B的对象出来。但按IoC的思想,IoC容器可以帮我们完成实例化对象,我们不必一遍又一遍地生产出Wheel类的示例,要使用某个对象时,直接从IoC容器里拿出来即可。
比如,考虑这样一种情况,假如我们的车厂跟旧的车轮供应商闹掰了,想要把所有的轮子,全换成另一个供应商的轮子。按照旧的实现方式,我们需要把车厂中的所有车的旧轮子实例化改过来。
如果有很多个类都引用了OldWheel
的具体实例化,修改起来将会非常复杂和痛苦:你不仅需要知道哪些地方引用了OldWheel
,还需要知道NewWheel
的构造函数需要什么参数,而更糟糕的是,这些参数有可能又有它们自己的构造函数。
而使用IoC的思想,我们把对象的控制权交给IoC容器管理,在使用时直接向IoC要就好了,小车类中的代码没有改动与变化。它把你从复杂的依赖关系中拯救出来,你完全不用考虑对象是如何被创建的。
3 Spring Bean是什么东东?
简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。
我们需要通知 IoC 容器帮助我们管理哪些对象,这种“通知”可以通过 XML 文件、注解或者 Java 配置类实现。
【我还是没有搞清楚BeanFactory
巴拉巴拉的工作流程,但是啊,但是,我感觉框架存在的意义,就是我没必要知道这些东西是怎么工作的啊】
3.1 将一个类声明为 Bean 的注解有哪些?
@Component、@Repository、@Service、@Controller
-
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。 -
@Repository
: 对应持久层 (Dao 层) ,主要用于数据库相关操作。可以看到其实现方式与@Component
相同,只是明确这个类对数据库有CRUD的功能。@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Repository { @AliasFor( annotation = Component.class ) String value() default ""; }
-
@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。 -
@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用Service
层返回数据给前端页面。
3.2 @Mapper和@Repository的区别?
@Mapper
是Mybatis自带的一个注解。不需要在spring配置中设置扫描地址,通过mapper.xml
里面的namespace
属性对应相关的mapper类,Spring将动态生成Bean后注入到ServiceImpl
中。@MapperScan
加在启动类上可以替代 @Mapper
,把一个包里的内容都声明为Mapper
。
@Repository
是Spring提供的一个注解,用于声明一个Bean
。在直接使用JDBC开发时可能会用到(然鹅现在大多都直接MyBatis)。
简单理解就是:@Mapper = @MapperScan(自动扫描配置)+Repository(可省略)
3.3 @Component和@Bean的区别?
@Component
注解作用于类,而@Bean
注解作用于方法。
@Component
通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan
注解定义要扫描的路径)。@Bean
注解告诉了 Spring 这个方法将返回一个对象,这个对象要注册为Spring中的Bean,当我需要用它的时候还给我。
@Bean
注解比 @Component
注解的自定义性更强,而且很多地方我们只能通过 @Bean
注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring
容器时,则只能通过 @Bean
来实现。
@Bean
注解使用示例,按条件注入组件:
@Configuration
public class MyConfiguration {
@Bean
public User user() {
int i = 10;
if(i < 7) {
return new User("jack", 20);
} else {
return new User("david", 18);
}
}
}
@Autowired
private User user; // 将在IOC容器中寻找User对象
3.4 注入Bean的注解有哪些?
Spring 内置的 @Autowired
以及 JDK 内置的 @Resource
和 @Inject
都可以用于注入 Bean。
Annotaion | Package | Source | |
---|---|---|---|
@Autowired | org.springframework.bean.factory | Spring 2.5+ | byType |
@Resource | javax.annotation | Java JSR-250 | byName |
@Inject | javax.inject | Java JSR-330 |
@Autowired
和@Resource
使用的比较多一些。
3.5 @Autowired 和 @Resource 的区别是什么?
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为byName
(根据名称进行匹配)。- 当一个接口存在多个实现类的情况下,
@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过@Qualifier
注解来显式指定名称,@Resource
可以通过name
属性来显式指定名称。
1 @Autowired -> byType
Autowired
属于 Spring 内置的注解,默认的注入方式为byType
(根据类型进行匹配),也就是说会优先根据接口类型去匹配并注入 Bean (接口的实现类)。
这会有什么问题呢? 当一个接口存在多个实现类的话,byType
这种方式就无法正确注入对象了,因为这个时候 Spring 会同时找到多个满足条件的选择,默认情况下它自己不知道选择哪一个。
这种情况下,注入方式会变为 byName
(根据名称进行匹配),这个名称通常就是类名(首字母小写)。就比如说下面代码中的 smsService
就是我这里所说的名称,这样应该比较好理解了吧。
// smsService 就是我们上面所说的名称
@Autowired
private SmsService smsService;
举个例子,SmsService
接口有两个实现类: SmsServiceImpl1
和 SmsServiceImpl2
,且它们都已经被 Spring 容器所管理。
// 报错,byName 和 byType 都无法匹配到 bean
@Autowired
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Autowired
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean
// smsServiceImpl1 就是我们上面所说的名称
@Autowired
@Qualifier(value = "smsServiceImpl1")
private SmsService smsService;
我们还是建议通过 @Qualifier
注解来显式指定名称而不是依赖变量的名称。
2 @Autowired -> byType
@Resource
属于 JDK 提供的注解,默认注入方式为 byName
。如果无法通过名称匹配到对应的 Bean 的话,注入方式会变为byType
。
@Resource
有两个比较重要且日常开发常用的属性:name
(名称)、type
(类型)。
- 如果仅指定
name
属性则注入方式为byName
- 如果仅指定
type
属性则注入方式为byType
- 如果同时指定
name
和type
属性(不建议这么做)则注入方式为byType
+byName
。
// 报错,byName 和 byType 都无法匹配到 bean
@Resource
private SmsService smsService;
// 正确注入 SmsServiceImpl1 对象对应的 bean
@Resource
private SmsService smsServiceImpl1;
// 正确注入 SmsServiceImpl1 对象对应的 bean(比较推荐这种方式)
@Resource(name = "smsServiceImpl1")
private SmsService smsService;
3.6 Bean 的作用域有哪些?
可以通过@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
注解配置Bean
的作用域。
Spring 中 Bean 的作用域通常有下面几种:
- singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
- prototype : 每次获取都会创建一个新的 bean 实例。也就是说,连续
getBean()
两次,得到的是不同的 Bean 实例。 - request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次来自新 session 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
- websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
3.7 Bean 的生命周期?
- Bean 容器找到配置文件中 Spring Bean 的定义。
- Bean 容器利用 Java Reflection API 创建一个 Bean 的实例。
- 如果涉及到一些属性值 利用
set()
方法设置一些属性值。 - 如果 Bean 实现了
BeanNameAware
接口,调用setBeanName()
方法,传入 Bean 的名字。 - 如果 Bean 实现了
BeanClassLoaderAware
接口,调用setBeanClassLoader()
方法,传入ClassLoader
对象的实例。 - 如果 Bean 实现了
BeanFactoryAware
接口,调用setBeanFactory()
方法,传入BeanFactory
对象的实例。 - 与上面的类似,如果实现了其他
*.Aware
接口,就调用相应的方法。 - 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessBeforeInitialization()
方法 - 如果 Bean 实现了
InitializingBean
接口,执行afterPropertiesSet()
方法。 - 如果 Bean 在配置文件中的定义包含 init-method 属性,执行指定的方法。
- 如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessAfterInitialization()
方法 - 当要销毁 Bean 的时候,如果 Bean 实现了
DisposableBean
接口,执行destroy()
方法。 - 当要销毁 Bean 的时候,如果 Bean 在配置文件中的定义包含 destroy-method 属性,执行指定的方法。
4 Spring MVC的工作原理
Spring MVC 下我们一般把后端项目分为 :
- Controller 层(控制层,返回数据给前台页面)
- Service 层(处理业务)
- Dao 层(数据库操作)
- Entity 层(实体类)
-
用户发送
Request
请求, 被前置控制器DispatcherServlet
接收。DispatcherServlet
:核心的中央处理器,接收请求、分发请求、响应结果,返回结果可以是json、String等数据类型,也可以是页面。
-
DispatcherServlet
根据请求信息调用HandlerMapping
,HandlerMapping
根据 url 去匹配查找能处理的Handler
(即Controller
控制器) ,并会将请求涉及到的拦截器和Handler
一起封装。-
mappedHandler = this.getHandler(processedRequest);
HandlerExecutionChain handler = mapping.getHandler(request);
-
-
DispatcherServlet
调用HandlerAdapter
适配器执行Handler
。- 使用适配器就可以执行多种不同的
Handler
啦。
- 使用适配器就可以执行多种不同的
-
Handler
完成对用户请求的处理后,会返回一个ModelAndView
对象给DispatcherServlet
。-
ModelAndView
包含了数据模型以及相应的视图的信息。 -
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
-
-
视图解析器
ViewResolver
根据ModelAndView
来解析实际的View
,返回给DispatherServlet
。view = mv.getView();
-
DispaterServlet
渲染View
并返回给请求者(浏览器)view.render(mv.getModelInternal(), request, response);
下图展示了调用某个Controller时的栈,可以看到上述提及的各个类。