参考javaguide的学习笔记~
一开始看到这个图太劝退了,但实际上一开始只需要理解它是一个框架,包含很多个模块,能够简化开发。
使得接收HTTP请求,查数据库,写业务逻辑三部分能够分开。
并且能很方便地完成一些共同的逻辑。
IoC(Inversion of Control)的思想是,将原本在程序中手动创建对象的控制权,交给Spring框架来管理。
如上图所示,传统的开发方式往往是在类A中通过new关键字来new一个B的对象出来。但按IoC的思想,IoC容器可以帮我们完成实例化对象,我们不必一遍又一遍地生产出Wheel类的示例,要使用某个对象时,直接从IoC容器里拿出来即可。
比如,考虑这样一种情况,假如我们的车厂跟旧的车轮供应商闹掰了,想要把所有的轮子,全换成另一个供应商的轮子。按照旧的实现方式,我们需要把车厂中的所有车的旧轮子实例化改过来。
如果有很多个类都引用了OldWheel
的具体实例化,修改起来将会非常复杂和痛苦:你不仅需要知道哪些地方引用了OldWheel
,还需要知道NewWheel
的构造函数需要什么参数,而更糟糕的是,这些参数有可能又有它们自己的构造函数。
而使用IoC的思想,我们把对象的控制权交给IoC容器管理,在使用时直接向IoC要就好了,小车类中的代码没有改动与变化。它把你从复杂的依赖关系中拯救出来,你完全不用考虑对象是如何被创建的。
简单来说,Bean 代指的就是那些被 IoC 容器所管理的对象。
我们需要通知 IoC 容器帮助我们管理哪些对象,这种“通知”可以通过 XML 文件、注解或者 Java 配置类实现。
【我还是没有搞清楚BeanFactory
巴拉巴拉的工作流程,但是啊,但是,我感觉框架存在的意义,就是我没必要知道这些东西是怎么工作的啊】
@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
层返回数据给前端页面。
@Mapper
是Mybatis自带的一个注解。不需要在spring配置中设置扫描地址,通过mapper.xml
里面的namespace
属性对应相关的mapper类,Spring将动态生成Bean后注入到ServiceImpl
中。@MapperScan
加在启动类上可以替代 @Mapper
,把一个包里的内容都声明为Mapper
。
@Repository
是Spring提供的一个注解,用于声明一个Bean
。在直接使用JDBC开发时可能会用到(然鹅现在大多都直接MyBatis)。
简单理解就是:@Mapper = @MapperScan(自动扫描配置)+Repository(可省略)
@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对象
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
使用的比较多一些。
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为 byName
(根据名称进行匹配)。@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过 @Qualifier
注解来显式指定名称,@Resource
可以通过 name
属性来显式指定名称。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
注解来显式指定名称而不是依赖变量的名称。
@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;
可以通过@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
注解配置Bean
的作用域。
Spring 中 Bean 的作用域通常有下面几种:
getBean()
两次,得到的是不同的 Bean 实例。set()
方法设置一些属性值。BeanNameAware
接口,调用 setBeanName()
方法,传入 Bean 的名字。BeanClassLoaderAware
接口,调用 setBeanClassLoader()
方法,传入 ClassLoader
对象的实例。BeanFactoryAware
接口,调用 setBeanFactory()
方法,传入 BeanFactory
对象的实例。*.Aware
接口,就调用相应的方法。BeanPostProcessor
对象,执行postProcessBeforeInitialization()
方法InitializingBean
接口,执行afterPropertiesSet()
方法。BeanPostProcessor
对象,执行postProcessAfterInitialization()
方法DisposableBean
接口,执行 destroy()
方法。Spring MVC 下我们一般把后端项目分为 :
用户发送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时的栈,可以看到上述提及的各个类。