前言
本文spring框架相关知识包括Spring、Spring MVC,Spring Boot 以及Mybatis,Hibernate等常见与Spring集成相关的框架。
常见问题
- 对Spring IOC、AOP的理解
- AOP的实现方式都有哪些?
- AOP中切面、切点是指什么?
- Spring MVC请求流程,拦截器在哪里执行的? 监听器是指什么?
- Mybatis中怎样在一次插入中返回该行主键(如果是自增主键的话)?
- Mybatis中 #{}和${}有什么区别
- Mybatis中怎样一次插入多行数据
- 怎样获得Spring的全局上下文类?
- Mybatis和Hibernate有什么区别?
- 10.Spring Boot 启动流程
- spring 用到了哪些设计模式?分别是在哪用的
- spring 两个注解区别,@Autowired 那个如果有多个类会怎样
Spring IOC和AOP
spring有3大核心组件,beans(bean的定义和管理,beanFactory相关),context(bean的上下文相关,运行环境)和core(定义了资源的访问形式)
- beans包,创建和管理bean,beanFactory创建,对bean的包装等。使用BeanFactory表示Bean容器,是Bean容器的最底层表示。
- context包,bean的运行时环境,保存各个对象状态。使用ApplicationContext表示应用环境的基本信息。是context包的最顶端接口。比BeanFactory功能更多一些。
- core包,spring核心工具类包。定义了访问资源的统一方式,将资源抽象成接口的形式。
Resoource
是资源表示的接口,可以是各种文件形式的资源。
spring可分为两类容器,一类是BeanFactory(以及直接实现它的类),是最简单的容器,是对Ioc的简单封装。最常使用的是
XmlBeanFactory
。对一个xml文件进行解析并创建bean容器。(目前已废弃)
ApplicationContext 容器包括 BeanFactory 容器的所有功能,所以通常建议超过 BeanFactory。其下子类有
FileSysytemXmlApplicationContext
,ClassPathXmlApplicationContext
,WebXmlApplication
等。分别表示从文件系统,classpath或是web环境中加载配置文件用以完成spring环境配置。
spring 元数据
有3种方式可以配置bean。
- 基于xml配置文件
- 基于注解
- 基于java配置
基于xml文件形式的,对bean的定义有以下属性
- lazy-init,是否延时加载,即初次访问时加载,默认随容器启动加载。(lazy-init=false)
- init-method方法用于该bean初始化完成后调用的方法。(指定的是该bean的方法)
- destroy-method 方法用于该bean销毁前执行的方法。
<!--延时加载,并指定了初始和销毁时的回调方法-->
<bean id="helloService" class="com.wthfeng.learn.spring.HelloService" lazy-init="true" init-method="init"
destroy-method="destroy">
<!--构造器传参,指定bean的依赖关系-->
<constructor-arg name="helloDao" ref="helloDao"/>
<!--属性传参,指定依赖关系-->
<property name="message" value="wthfeng"/>
</bean>
<bean name="helloDao" class="com.wthfeng.learn.spring.HelloDao"/>
spring 作用域
主要常用的有singleton
(单例,只有一个单例实例,默认)和prototype
(原型模式,每次访问创建一个实例)。
注意的是,如果将bean设为prototype
模式,那么每次访问该bean时都会创建新实例,也意味着如果有init-method和destroy-method方法,则会执行多次。
生命周期
可以在bean初始化完成以及销毁前做一些工作,对于xml文件,可使用init-method
和destroy-method
指定该bean下特定方法。也可以在beans
中指定默认方法。如下
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-init-method="init"
default-destroy-method="destroy">
或者使用硬编码,实现InitializingBean
和DisposableBean
接口实现回调。
依赖注入
依赖注入主要有2种方式。一个是构造器注入,一个是setter方法注入。
如果你想要向一个对象传递一个引用,你需要使用 标签的 ref 属性,如果你想要直接传递值,那么你应该使用如上所示的 value 属性。
<bean id="helloService" class="com.wthfeng.learn.spring.HelloService" lazy-init="false">
<!--构造器传参,指定bean的依赖关系-->
<constructor-arg name="helloDao" ref="helloDao"/>
<constructor-arg name="worldDao" ref="worldDao"/>
<!--属性传参,指定依赖关系-->
<property name="message" value="wthfeng"/>
</bean>
自动装配
自动装配分为byType
、byName
和constructor
,一个是按bean的类型,一个是按名称,以及按构造器参数。注意当使用byType
时,当查找到多余一个类型时会报错。按构造器参数其实是按byType
的一种特例,它会按构造器中参数类型进行查找。
如今spring推荐使用构造器注入的方式(针对注解方式而言),原因:
- 保证依赖不为空,spring注入时会对依赖进行检查,确保存在
- 保证依赖不可变
- 返回时是完全初始化的(不需要再调用set方法设值)
- 避免了循环依赖(有循环依赖立即报错)
使用注解 @AutoWire
表示按类型装配。若发现多余一个类型bean时会抛出异常,NoUniqueBeanDefinitionException,可以使用@Qualifier
使用名称指定唯一的bean。
// 使用构造器注入的方法
@Autowired
public UserService(@Qualifier(value = "userDao") UserDAO userDAO) {
this.userDAO = userDAO;
}
或
// setter注入
@Autowired
@Qualifier(value = "userDao")
private UserDAO userDAO;
@Resource
注解是JSR-250的注解,spring也支持。默认@Resource
按名称进行匹配,若没有找到则按类型匹配。有name属性,按名称匹配,和type属性,按类型匹配。
@PostConstruct
加在方法上,是当bean创建完成后执行的初始化方法,是xml形式的init-method
的注解版本。@PreDestroy
表示销毁前方法回调,是destroy-method
方法的注解版本。
// 等于在xml格式定义的bean上添加 init-method='init'
@PostConstruct
public void init() {
System.out.println("init dao hello");
}
// 等于在xml格式定义的bean上添加 destroy-method='destroy'
@PreDestroy
public void destroy() throws Exception {
System.out.println("destroy dao ");
}
Spring AOP
几个概念
aop指面向切面编程。将若干横切的东西(方法或属性等)提取出来单独处理。比如需要在项目中所有添加方法中添加日志表示处理完了,不需要再每个方法都添加一行重复的日志。只需定义切面,由AOP完成。
切点(PointCut):定义需要在哪里织入一些增强或其他共性的处理。(比如需要在所有save()
方法里加一行日志,那切点就是用来表示所有方法的)
通知(Advice):定义什么时增加处理和增加什么处理。(接上个例子,就是在save()
方法哪里加日志呢,是在方法执行完返回前还是还没执行的时候,或是抛异常的情况;加什么日志内容呢(或者不加日志做一些其他操作))等等,都是通知定义的。
切面(Aspect):上述两个概念理解了,切面就是切点和通知的结合。一个切面就是需要在哪里做增加,以及什么时增强,增强什么的完整定义。
xml格式的aop定义, Spring只支持方法连接点。
<!--aop配置元素,aop配置都需在其内部-->
<aop:config>
<!--定义切点-->
<aop:pointcut id="allService" expression="execution(* com.wthfeng.learn.spring.aop.*.printLog(..))"/>
<!--切面定义-->
<aop:aspect id="logAspect" ref="logBean">
<!--通知定义了干什么和什么时干-->
<!--通知,包括前置、后置、返回、抛出异常,环绕通知等-->
<aop:before method="before" pointcut-ref="allService"/>
<aop:after method="after" pointcut-ref="allService"/>
<aop:around method="around" pointcut-ref="allService"/>
</aop:aspect>
</aop:config>
也可以使用注解定义
// 切面定义
@Aspect
@Component
public class AspectModel {
// 定义切点
@Pointcut(value = "execution(* com.wthfeng.learn.spring.aop.*Service.save(..))")
public void selectSave() {
}
// 通知(前置)
@Before(value = "selectSave()")
public void before() {
System.out.println("before method...");
}
// 通知(后置)
@After(value = "selectSave()")
public void after() {
System.out.println("after method...");
}
}
Spring AOP可由JDK动态代理和CGLib实现。
JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
- 实现
InvocationHandler
接口 - 使用
Proxy.newProxyInstance(classloader, interfaces, invocationHandler)
创建代理类。
CGLIB会让生成的代理类继承当前对象,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。在CGLIB底层底层实现是通过ASM字节码处理框架来转换字节码并生成新的代理类
注意如果一个类是final类,则不能使用CGLib代理。
spring aop实现
spring aop的实现主要依赖于BeanPostProcessor
,在回调的初始化方法中,判断bean是否符合表达式规范,符合则生成代理,返回代理bean。这样就为实现aop提供依据。
public class PostServiceBean implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 做bean做一些修改
return bean;
}
}
Spring MVC
首先需要了解serlvet
及其工作机制,spring MVC说到底就是为解决serlvet过多导致的地址映射杂乱,以及如何统一处理返回资源的问题。也就是说,如果不用MVC,直接用serlvet也不是不行,只是太麻烦了。
spring MVC解决的问题:
- 管理映射,当请求过来时找到匹配的方法进行处理。感觉这应该是它的核心功能。
- 处理返回资源,将资源统一格式处理,如返回jsp,json等。
spring mvc 涉及的要点
DispatcherServlet
核心servlet,负责处理所有请求,并返回响应。spring mvc最核心的类。由它(及子类)负责初始化spring mvc环境,可以在它的配置文件中指定一些其他设置等。具体的,该servlet初始化时会执行initWebApplicationContext
方法,该方法最终会调用AbstractApplicationContext.refresh()
方法创建spring IOC容器用来管理bean。
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:mvc/DispatcherServlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
listener监听器,serlvet可以设置监听器,
ServletContextListener
监听器用于监听servle上下文启动和销毁的事件,实现此接口即可。spring的ContextLoaderListener
就实现了此接口。用于完成spring环境的创建工作。这里说的spring环境不是上文spring mvc的上下文环境,实际上,先执行监听器,创建了spring上下文(像dao、service、jdbc等bean环境),然后执行DispatcherServlet
的初始化方法创建mvc环境。mvc环境会判断当前是否有spring上下文,有的话则设置为父上下文。至此完成spring和spring mvc两个环境的创建(spring mvc主要针对controller的bean以及映射管理等)。
spring MVC执行流程,在doDispatch()
方法中
- 根据
getHandler()
找到处理器执行链(包括handler(即封装的controller方法),以及前后拦截器链)。 - 根据handler得到适配器(HandlerAdapter)
- 执行前置拦截器
- 执行handler,根据有无
@ResponseBody
注解及返回结果类型判断处理结果,并返回ModelAndView(若为json格式,则返回null) - 执行后置拦截器
- 处理返回结果,渲染视图
// doDispatch()方法,mvc核心处理流程
// 去掉了其他无关代码和异常处理,只保留了主要流程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 根据请求获取执行器链(handler处理器和拦截器链)
HandlerExecutionChain mappedHandler = null;
mappedHandler = getHandler(processedRequest);
// 根据处理器找到相应适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 执行前置拦截器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
//执行hanler方法,返回ModelAndView
//注意返回的ModelAndView可能为null(如返回json格式等)
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// 执行后置拦截器
mappedHandler.applyPostHandle(processedRequest, response, mv);
// 处理结果,渲染等
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
spring mvc有适配器模式。用于处理不同类型的控制器,比如使用注解的控制器(@Controller),或是直接实现Controller接口的控制器,或是实现HttpRequestHandler
接口的控制器。各种控制器的需要不同的调用方式,否则不利于解耦。这里就使用了适配器模式。
有以下适配器
- SimpleControllerHandlerAdapter,简单适配器,适配直接实现
Controller
接口的控制器。返回ModelAndView
。调用执行时直接调用原方法 - HttpRequestHandlerAdapter,Http请求处理适配器,需要实现
HttpRequestHandler
接口。没有返回值,类似于servlet的处理方式。 - RequestMappingHandlerAdapter,最常用的适配器,可处理注解类型的控制器。可以使用
<mvc:annotation-driven />
开启。最常使用@Controller
和@RequestMapping
注解。
对于前2种适配器,需要进行url映射处理,如下
<bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/simple1">simple1</prop>
<prop key="/simple2">simple2</prop>
</props>
</property>
</bean>
<bean id="simple1" class="com.wthfeng.learn.spring.mvc.controller.SimpleController"/>
<bean id="simple2" class="com.wthfeng.learn.spring.mvc.controller.HandlerController"/>
SimpleController实现了Controller接口,如下
public class SimpleController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
ModelAndView mv = new ModelAndView();
mv.setViewName("index");
mv.addObject("message", "simple controller");
return mv;
}
}
而HandlerController实现了HttpRequestHandler接口,如下
public class HandlerController implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter printWriter = response.getWriter();
response.setContentType("text/html;charset=UTF-8");
printWriter.write("<html><head><head><body><h1>");
printWriter.write("Hello message!");
printWriter.write("</h1></body></html>");
printWriter.close();
}
}
无论是SimpleController还是HandlerController,一个类只能有一个servlet。
ApplicationContextAware
实现该接口可得到Spring上下文ApplicationContext。该接口只有一个方法setApplicationContext(ApplicationContext applicationContext)
,可使用静态变量接受这个上下文。
Spring会检查所有的bean,如果有bean实现了ApplicationContextAware
接口,会在创建该bean后,自动调用该bean的setApplicationContext
传入容器本身上下文。由此可得到Spring上下文。
拦截器和过滤器
filter是serlvet的过滤器,只能过滤serlvet方法,而拦截器是mvc容器实现的,
filter定义的web.xml中,或使用注解
intercept由spring mvc定义,在mvc的配置文件定义
<!--Spring MVC拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/"/>
<bean class="com.wthfeng.learn.spring.mvc.interceptor.SecondInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/hello"/>
<bean class="com.wthfeng.learn.spring.mvc.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
后记
- github - tiny-spring项目简要实现了spring框架,可参考
- Spring 技术内幕
- java代理模式与JDK代理
- Servlet工作机制解析