Spring MVC

使用Spring Context

使用ClassPathXmlApplicationContext:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/spring-main.xml");
A a = context.getBean(A.class);

直接使用 DefaultListableBeanFactory:

Resource resource = new ClassPathResource("spring-core.xml");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions(resource);
MyBean myBean = (MyBean) beanFactory.getBean("myBean");
myBean.sayHello();

使用Bean

Spring 基于 Ioc 和 DI 的方式 创建 & 装配 Bean :

  • 控制反转(Inversion of Control): 使用者不自己创建依赖的对象, 而交由第三方(IoC容器)创建. 从IOC容器中获取(和自动注入).
    而不必由用户调用new来创建Bean对象, 通过IoC则可以减少它们之间的耦合度.
  • 依赖注入(Dependency Injection): 将依赖对象传递给使用者. 在Spring中, bean的装配是依赖注入的具体行为,依赖注入的时候需要根据bean的名称或类型等进行装配。

创建Bean的几种方式

基于注解 & XML:

基于注解

① 基于 @Component

  1. 通过注解方式创建容器:

    @Configuration
    @ComponentScan
    public interface ThisIsConfig {
    }
    • @Configuration 来标注该接口是用于定义配置的, Spring 会视为该java文件为一个xml配置
    • @ComponentScan Spring 将会扫描该类所在的包下的所有 bean注解(@Component, @Service等等), 等同于在 Spring的xml里写:
      <context:component-scan base-package="com.bigdata"></context:component-scan>
      如果要指定要扫描的包的路径(而不是 这个类所在的包) 可以用 @ComponentScan(value="包路径") 指定;
  2. 带有 @Component注解的类被Ioc方式创建:
  3. 通过 @Autowired 用 DI 方式进行装配:

关于@Component,@Service,@Controler,@Repository注解
这几个注解都是同样的功能,被注解的类将会被Spring 容器创建单例对象。
@Component : 侧重于通用的Bean类
@Service:标识该类用于业务逻辑
@Controler:标识该类为Spring MVC的控制器类
@Repository: 标识该类是一个实体类,只有属性和Setter,Getter

② 基于 @Bean

@Configuration
@ComponentScan
public class SwaggerConfig {
@Bean
public SwaggerSpringMvcPlugin customImplementation() {
...
}
}

@Bean 注解在这里的意思是 : 该方法会返回一个 SwaggerSpringMvcPlugin 类型的 bean

基于XML

① 基于构造器: 下面的类JedisPortsFactory 具有一个构造器(该构造器 有两个参数: config 和 autoFlush)
config 引用到了另一个bean, autoFlush 是个boolean型

<bean id = "jedisPortsFactory" class="com.bigdata.console.tools.online.JedisPortsFactory">
<constructor-arg name="config" ref="jedisEvictionPoolConfig"/>
<constructor-arg name="autoFlush" value="true"/>
</bean>

② 基于 setter: // CommonsMultipartResolver 要有property对应的 Setter方法

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8" />
<property name="maxUploadSize" value="-1" />
</bean>

③ 基于静态工厂: 指定 工厂类的class, 适用于 静态工厂方法:

<bean id="jp_featurePv" class="com.bigdata.consoleJedisPortsFactory" factory-method="getJedisMSServers">
<constructor-arg type="java.lang.String" value="xxx"/>
</bean>

④ 基于动态工厂: 指定 动态工厂的bean 和方法, 下面的例子中工厂方法 getJedisMSServers 有一个字符串型的参数, 适用于动态工厂方法:

<bean id="jp_featurePv" factory-bean="jedisPortsFactory" factory-method="getJedisMSServers">
<constructor-arg type="java.lang.String" value="xxx"/>
</bean>

bean的属性

scope
  • scope=”singleton”: 单例, Spring 在每次需要时都返回同一个bean实例
  • scope=”prototype”: Spring 在每次需要时都产生一个新的 bean 实例
  • scope=”request”
  • scope=”session”

singleton的实现: Spring 维护一个Map, key是 bean的变量名, 用单例方式创建 Bean, 会先放入这个map中, 这个map可以看成是一个缓存;
如果是 prototype , 则没有这个map,
下面是Spring源码中的 singleton实现方法。以下的源码在 Spring的Bean包中的 DefaultSingletonBeanRegistry.java类中

Spring-Singleton-Init-Register

autowire
  • autowire=”byName”: 只能用于setter注入。比如我们有方法“setHelloApi”,则“byName”方式Spring容器将查找名字为helloApi的Bean并注入
  • autowire=”no”: 意思是不支持自动装配,必须明确指定依赖
<bean id="bean" class="bean.HelloApiDecorator" autowire="byName"/>
depends-on

Spring保证该Bean所依赖的其他bean已经初始化, 用元素建立对其他bean的依赖关系, Sprign 会确保创建 bean的顺序:

<bean id="helloApi" class="helloworld.HelloImpl"/>
<bean id="decorator" class="helloworld.HelloApiDecorator"
depends-on="helloApi">
<property name="helloApi"><ref bean="helloApi"/></property>
</bean>
lookup-method

单例模式的beanA需要引用另外一个非单例模式的beanB,为了在我们每次引用的时候都能拿到最新的beanB

<bean id="prototypeBean" class="bean.PrototypeBean" scope="prototype"/>
<bean id="singletonBean" class="bean.SingletonBean">
<!-- SingletonBean.getBean()方法被代理 -->
<lookup-method name="getBean" bean="prototypeBean"/>
</bean>

下面是java代码

public abstract class SingletonBean{
// 抽象方法, 每次获取一个新的PrototypeBean实例
protected abstract PrototypeBean getBean();
}

ApplicationContext app = new ClassPathXmlApplicationContext("classpath:resource/applicationContext.xml");
SingletonBean b= (SingletonBean)app.getBean("singletonBean");
b.getBean(); // 每次返回一个新的PrototypeBean

Bean的初始化/销毁回调

Spring-Bean-Lifecycle

基于代码

InitializingBean接口为bean提供了属性初始化后的处理方法,它只包括afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法:

public class ExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}

DisposableBean接口为bean提供销毁方法

public class ExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work
}
}

基于XML配置

<bean id="helloWorld"
class="com.dropNotes.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="message" value="Hello World!"/>
</bean>

上面的init-method属性和 destroy-method属性, 指定了HelloWorld类的初始化/销毁回调方法名字, 接下来在HelloWorld类中定义无参的方法即可.

何时调用

ApplicationContext.registerShutdownHook()被调用时

使用AOP

(Aspect Oriented Program) 面相切面编程: 主要实现的目的是针对业务处理过程中的切面进行提取,它所面对的是处理过程中的某个步骤或阶段,以获得逻辑过程中各部分之间低耦合性的隔离效果。

Spring 的AOP是通过Java语言提供的代理(Proxy)实现的, Java语言的代理包括如下几种方式: JDK动态代理, Cglib动态代理.

AOP的一些概念

  • 连接点(Jointpoint)连接点是能够插入切面的一个点,连接点可能是类初始化,可以是调某方法时,抛出异常时,修改某字段时
  • 切入点(Pointcut):一组连接点集合
  • 通知(Advice):定义在连接点上“要做什么”,以及“何时去做”
    • 包括前置通知(before advice)、后置通知(after advice)、环绕通知(around advice)
  • 切面(Aspect):可以认为是”通知”和”切入点”的集合
  • 引入(inter-type declaration):为已有的类添加额外新的字段或方法,Spring允许引入新的接口(必须对应一个实现)到所有被代理对象(目标对象), 在AOP中表示为“做什么”;
  • 目标对象(Target Object):需要被织入横切关注点的对象,即该对象是切入点选择的对象,需要被通知的对象,从而也可称为“被通知对象”;由于Spring AOP 通过代理模式实现,从而这个对象永远是被代理对象,在AOP中表示为“对谁做”;
  • AOP代理(AOP Proxy):AOP框架使用代理模式创建的对象,从而实现在连接点处插入通知(即应用切面),就是通过代理来对目标对象应用切面。在Spring中,AOP代理可以用JDK动态代理或CGLIB代理实现,而通过拦截器模型应用切面。
  • 织入(Weaving):织入是一个过程,是将切面应用到目标对象从而创建出AOP代理对象的过程,织入可以在编译期、类装载期、运行期进行。

基于XML配置aspect

<bean id="aspect" class="cn.javass.spring.chapter6.aop.HelloWorldAspect"/>
<aop:config>
<!-- 定义了一个id="pointcut"的切点, 范围是com.javass包下的所有类 -->
<aop:pointcut id="pointcut" expression="execution(* cn.javass..*.*(..))"/>
<!-- 定义切面的集合, ref="aspect"表示要引入"aspect"这个bean -->
<aop:aspect ref="aspect">
<!-- 定义一个切点, 包括用哪些切点, 以及在切点处要插入aspect.beforeAdvice()方法 -->
<aop:before pointcut-ref="pointcut" method="beforeAdvice"/>
<!-- 定义另一个切点, 在切点处要插入aspect.afterFinallyAdvice()方法 -->
<aop:after pointcut="execution(* cn.javass..*.*(..))" method="afterFinallyAdvice"/>
</aop:aspect>
</aop:config>

基于注解配置aspect

下面的代码定义一个切面(@Aspect): 哪里切入(@Pointcut), 切入的行为(@Advice)

@Aspect
public class ControllerLogAspect { //定义了一个切面

// 定义切点"logPointCut", 在哪些类里切入
@Pointcut("execution(public * com.xxx.*.controller..*.*(..)) && " +
"!execution(public * com.xxx.*.controller..CheckController.*(..))")
public void logPointCut() {
}

// 环绕通知
@Around("logPointCut()")
public void advice(ProceedingJoinPoint joinPoint){

}

// 前置通知, 在切点"logPointCut"之前
@Before("logPointCut()")
public void doBefore(JoinPoint joinPoint) {
}

// 后置通知
@AfterReturning(returning = "ret", pointcut = "logPointCut()")
public void doAfter(Object ret) throws Throwable {
}
}

一次请求的处理流程

@TODO [[#Spring Spring MVC一次请求的处理流程]]

DispatcherServlet

要使用Spring MVC只需要在web.xml(Java Servlet 规范里Java Web项目的部署描述符文件)里增加一个Servlet:

<servlet>
<servlet-name>comment</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:appcontext-core-web.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>comment</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>

DispatcherServlet 工作流程

对于Spring MVC程序来说, 首先调用的是DispatcherServlet.service(ServletRequest, ServletResponse), 实现是在
HttpServlet.service(ServletRequest req, ServletResponse resp), 这个方法里把ServletRequest对象转换为HttpServletRequest, 在这个方法里又调用进了
FrameworkServlet.service(HttpServletRequest req, HttpServletResponse resp), 在这个方法里如果method!=PATCH则调用进super.service(HttpServletRequest, HttpServletResponse), 也就是
HttpServletservice(HttpServletRequest, HttpServletResponse), 这里根据不同的method调用不同的doX()方法,
调用了this.doGet()(以GET方法为例), 因为在FrameworkServlet 重写了doGet(), 所以这里调用的代码是FrameworkServlet.doGet(), 在这个方法里
调用了FrameworkServlet.processRequest(), 然后又调用了this.doService(),
DispatcherServlet重写了doService(), 所以最终调用到DispatcherServlet.doService(), 该方法逻辑大致如下:

void doDispatch(HttpServletRequest request, HttpServletResponse response)  {
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
  1. getHandler()主要就是通过this.handlerMappings中的HandlerMapping实例来对具体request映射一个handler(Spring MVC中的Controller类) ;
  2. 如果看过中对于this.handlerMappings初始化的解读,便知道HandlerMapping的具体实现有3个:
    • RequestMappingHandlerMapping : 用来映射Controller和URL
    • BeanNameUrlHandlerMapping
    • SimpleUrlHandlerMapping

DispatcherServlet 工作流程
上图中组件处理顺序分别是:

  • Dispatcher Servlet分发器
  • Handler Mapping 处理器映射
  • Controller 控制器
  • ModelAndView 模型和视图对象
  • ViewResolver 视图解析器

拦截器(Interceptor)

处理器映射处理过程配置的拦截器,必须实现 org.springframework.web.servlet包下的 HandlerInterceptor接口。
这个接口定义了三个方法:
preHandle(..),它在处理器实际执行 之前 会被执行;
postHandle(..),它在处理器执行 完毕 以后被执行;
afterCompletion(..),它在 整个请求处理完成 之后被执行。

通过xml定义拦截器

<beans>
<bean id="handlerMapping" class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
<property name="interceptors">
<list>
<ref bean="officeHoursInterceptor"/>
</list>
</property>
</bean>

<bean id="officeHoursInterceptor" class="samples.TimeBasedAccessInterceptor">
<property name="openingTime" value="9"/>
<property name="closingTime" value="18"/>
</bean>
<beans>

通过注解定义拦截器


@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LocaleInterceptor());
registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
}

}

控制器(Controller)

传入类型

  • @RequestParam注解: @RequestParam(value = "client_id") String appId
  • Model类型: 这种通常返回String类型的view路径
  • HttpServletResponse:
  • HttpServletRequest:

返回类型

  • 返回ModelAndView: 返回视图return new ModelView("/view/111", map)
    • 通过ModelAndView也可以重定向: return new ModelAndView("redirect:/controller2");
    • 如果modelView是以参数传入的: model.setViewName("forward:index.jsp"); return model;
  • 返回RedirectView: 专门用来处理转发的视图, 见后面的代码.
  • 返回String: 返回字符串可以指定逻辑视图名, 通过视图解析器解析为物理视图地址
    • 通过String也可以重定向: return "redirect:/resource/page2.jsp";
    • 如果Controller带有@ResponseBody注解, 可以直接返回String字面值;
  • 以json返回对象: 借助@ResponseBody注解, 项目导入Jackson.jar, 并且在Spring配置文件启用了<mvc:annotation-driven /> 1
  • 返回Map:
    • 借助@ResponseBody注解, return new HashMap<>();会返回一个json
    • 没有@ResponseBody注解, map.put("key1", "value-1"); return map;, 在jsp页面中可直通过${key1}获得到值
  • 返回void: 需要通过形参传入request和response
    • 使用request转向页面: request.getRequestDispatcher("index.html").forward(request, response);
    • 通过response页面重定向: response.sendRedirect("http://www.xxx.com");
    • forward和Redirect的区别: forward是由Servlet直接转给另一个Controller处理, Redirect相当于302, 返回给浏览器, 然后浏览器再发一次新的请求到Controller2
    • 通过response指定响应结果:
      • 返回json: response.setContentType("application/json;charset=utf-8"); response.getWriter().write("this_is_json");
      • 返回Html: response.getWriter().println("<title>HelloWorld</title></head><body>");

用RedirectAttributes带参跳转:

@RequestMapping("/")
public RedirectView hello(RedirectAttributes attrs) {
attrs.addAttribute("message", "hello");
attrs.addFlashAttribute("username", "sudoz");
return new RedirectView("hello");
}
@RequestMapping("hello")
Map<String, String> hello(@ModelAttribute("username") String username,
@ModelAttribute("message") String message) {
Map<String, String> map = Maps.newHashMap();
map.put("username", username);
map.put("message", message);
return map;
}

1 mvc:annotation-driven是一种简写形式,完全可以手动配置替代这种简写形式,<mvc:annotation-driven />会自动注册DefaultAnnotationHandlerMappingAnnotationMethodHandlerAdapter 两个bean,是Spring MVC为@Controllers分发请求所必须的。
并提供了:数据绑定支持,@NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,读写XML的支持(JAXB),读写JSON的支持(Jackson)。

Spring是如何处理返回类型的?

DispatchServlet.viewResolvers的类型是List<ViewResolver>, Controller返回的类型转给DispatchServlet, 最终交给不同的ViewResolver处理的

视图(View)

所有web应用的MVC框架都提供了视图相关的支持。Spring提供了一些视图解析器,它们让你能够在浏览器中渲染模型,并支持你自由选用适合的视图技术而不必与框架绑定到一起。
Spring原生支持JSP视图技术、Velocity模板技术和XSLT视图等。

有两个接口在Spring处理视图相关事宜时至关重要,分别是视图解析器接口ViewResolver和视图接口本身View。
视图解析器ViewResolver负责处理视图名与实际视图之间的映射关系。
视图接口View负责准备请求,并将请求的渲染交给某种具体的视图技术实现。

使用ViewResolver接口解析视图

Spring MVC中所有控制器的处理器方法都必须返回一个逻辑视图的名字,无论是显式返回(比如返回一个String、View或者ModelAndView)还是隐式返回(比如基于约定的返回)。
Spring中的视图由一个视图名标识,并由视图解析器来渲染。Spring有非常多内置的视图解析器。

资源(Resource)

Resource接口

Resource接口提供了足够的抽象,足够满足我们日常使用。而且提供了很多内置Resource实现:ByteArrayResource、InputStreamResource 、FileSystemResource 、UrlResource 、ClassPathResource、ServletContextResource、VfsResource等。

路径通配符

  • ?匹配一个字符,如config?.xml将匹配config1.xml
  • *匹配零个或多个字符串,如cn/*/config.xml将匹配cn/javass/config.xml,但不匹配匹配cn/config.xml
  • **匹配路径中的零个或多个目录,如cn/**/config.xml
// 加载Resource例子1:
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//只加载一个绝对匹配Resource,且通过ResourceLoader.getResource进行加载
Resource[] resources=resolver.getResources("classpath:META-INF/INDEX.LIST");

// 加载Resource例子2:
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
//将加载多个绝对匹配的所有Resource
//将首先通过ClassLoader.getResources("META-INF")加载非模式路径部分
//然后进行遍历模式匹配
// classpath*: 用于加载类路径(包括jar包)中的所有匹配的资源
Resource[] resources=resolver.getResources("classpath*:META-INF/INDEX.LIST");

静态资源

静态资源包括:HTML、CSS、JS、图像、视频、PDF/Office等不需要服务器端处理的文件。

静态资源文件的位置:

  • Java Web默认的静态资源文件夹是 src/main/webapp/
  • Spring Boot自动将src/main/resource/下的「/static」「/public」「/resources」「/META-INF/resources」识别为资源文件夹。 下面的css可以通过访问http://localhost:8080/css/a.css获取
    Project Root
    └─src
    └─ main
    └─ resources
    ├─ static
    | └─ css
    | └─ a.css
    ├─ public
    | └─ css
    | └─ b.css
    ├─ resources
    | └─ css
    | └─ b.css
    └─ META-INF
    └─ resources
    └─ css
    └─ d.css

异常处理(Exception)

  1. Controller的匹配. 除了value指定url, 还可以通过product指定MIME-TYPE(参考网络协议HTTP)
  2. 调试的时候需要注意, cURL实际是使用了Accept: */*, 浏览器发出的请求是Accept:text/html
@RequestMapping(value = "/return-text-plain", produces = MimeTypeUtils.TEXT_PLAIN_VALUE)
@ResponseBody
public String returnPlainText() throws SomeException {
throw new SomeException();
}

How to自定义Error页面:

@Configuration
public class CustomDefaultErrorViewConfiguration {

@Autowired
private ThymeleafViewResolver thymeleafViewResolver;

@Bean
public View error() throws Exception {
return thymeleafViewResolver.resolveViewName("custom-error-page/error", Locale.CHINA);
}

}

注解(Annotation)

Spring注解

@Bean, @Configuration, @ContextConfiguration

  • @Autowired: 可以写在属性上, 和setter方法上, 或者构造函数上, 默认按照类型进行装配
  • @Bean: 用于方法上, 该方法必须返回一个类型对象, 该对象被注册为Spring上下文中的bean, 注意方法名字将会作为bean的ID, 相当于在xml中定义<bean>
    • @Bean(initMethod=”aa”,destroyMethod=”bb”): 指定aa和bb方法分别在在构造之后/销毁之前执行
  • @Configuration: 用于类上, 说明这个类可以使用Spring IoC容器作为bean定义的来源, 相当于在xml中定义<beans>
  • @ContextConfiguration(classes=KnightConfig.class) 使用在类上, 表示使用@Configuration标注的类当作bean的定义来源
// 定义要注入的bean
@Configuration
public class TextEditorConfig {
@Bean
public TextEditor textEditor(){
return new TextEditor( spellChecker() );
}
@Bean
public SpellChecker spellChecker(){
return new SpellChecker( );
}
}
// 上面的等同于在xml里定义了两个<bean>

// 使用从@Configuration标注类里注入的bean
@ContextConfiguration(classes=KnightConfig.class,loader=AnnotationConfigContextLoader.class)
public class Test {
@Autowired
TextEditor textEditor;

@Autowired
SpellChecker spellChecker;
}

@Component, @ComponentScan

  • @ComponentScan: 使用在类上, 可以扫描到@Component注解的类
  • @Component: 使用在类上, 表示可以被@ComponentScan标注的类扫描到

比较: @Configuration + @Bean 的方式需要在@Configuration的类里定义”返回每种Bean类型的方法”, @ComponentScan + @Component的方式省去了定义方法返回Bean的类型
@Configuration, @ComponentScan, @Component注解通常联合起来使用, 免去了在xml里定义bean, 也不必写@Bean

@Component
public class CompactDisc {
}

@Component
public class MediaPlayer {
private CompactDisc cd;

@Autowired
public CDPlayer(CompactDisc cd) { this.cd = cd; }
}

@Configuration
@ComponentScan
public class CDPlayerConfig {
}

// 使用扫描到的Bean:
@ContextConfiguration(classes=CDPlayerConfig.class)
public class Test {
@Autowired
private MediaPlayer player;

@Autowired
private CompactDisc cd;
}

组件注解: @Service, @Controller, @Repository, @Component

  • @Service: 用于注解Service层, 默认是单例的
  • @Controller: 定义控制器类一般这个注解在类中,通常方法需要配合注解 @RequestMapping
  • @RestController相当于@ResponseBody@Controller的合集, 默认是单例的
  • @Repository用于注解DAO,这个注解修饰的DAO类会被ComponetScan发现并配置,同时也不需要为它们提供xml配置项
  • 如果一个类不好归类, 则使用@Component注解
  • @PostConstruct/@PreDestroy 用在方法上

Spring会自动扫描base-package指定的包下面用@Service注解的所有类, 并注册到beans容器里.
需要在Spring配置文件里增加: <context:component-scan base-package="com.xxx.product.core.service"/> 来说明启用自动扫描

装配注解: @Autowired, @Resource, @Inject, @Primary

  • @Autowired和@Inject: 通过AutowiredAnnotationBeanPostProcessor来实现依赖注入, 顺序:
    1. 按照类型匹配
    2. 使用限定符进行类型限定
    3. 按照名称匹配
  • @Resource: 使用CommonAnnotationBeanPostProcessor来实现注入, 顺序:
    1. 按照名称匹配
    2. 按照类型匹配
    3. 使用限定符进行类型限定

数据库注解: @Transcational, @Cacheable

  • @Transcational : 事务处理
  • @Cacheable : 数据缓存

@Scope

默认是@Scope("singleton")单例的, 此外还有:

  • singleton 单例的
  • prototype 表示每次获得bean都会生成一个新的对象
  • request 表示在一次http请求内有效
  • session 表示在一个用户会话内有效

@Qualifier

public class CarFactory
{
@Autowired
@Qualifier("ImplementedClass")
private AbstractClass a;
}

当抽象类AbstractClass的实现类有多个时, 如果没有Qualifier注解则会报错, 因为Spring不知道应该注入哪个类型, 注意@Qualifier()括号里是类的名字

@Aspect

  • @After @Before. @Around 定义切面,可以直接将拦截规则(切入点 PointCut)作为参数
  • @PointCut : 专门定义拦截规则 然后在 @After @Before. @Around 中调用
  • @EnableAaspectJAutoProxy : 开启Spring 对 这个切面(Aspect )的支持

JDK注解

  • @Resource: 可以写在属性上, 和setter方法上, 默认按照名称进行装配

Spring中的线程安全性

参考自: 聊一聊Spring中的线程安全性 | SylvanasSun’s Blog @Ref

Spring作为一个IOC/DI容器,帮助我们管理了许许多多的“bean”。但其实,Spring并没有保证这些对象的线程安全,需要由开发者自己编写解决线程安全问题的代码。

Spring对每个bean提供了一个scope属性来表示该bean的作用域。它是bean的生命周期。例如,一个scopesingleton的bean,在第一次被注入时,会创建为一个单例对象,该对象会一直被复用到应用结束。

singleton:默认的scope,每个scope为singleton的bean都会被定义为一个单例对象,该对象的生命周期是与Spring IOC容器一致的(但在第一次被注入时才会创建)。
prototype:bean被定义为在每次注入时都会创建一个新的对象。
request:bean被定义为在每个HTTP请求中创建一个单例对象,也就是说在单个请求中都会复用这一个单例对象。
session:bean被定义为在一个session的生命周期内创建一个单例对象。
application:bean被定义为在ServletContext的生命周期中复用一个单例对象。
websocket:bean被定义为在websocket的生命周期中复用一个单例对象。

我们交由Spring管理的大多数对象其实都是一些无状态的对象,这种不会因为多线程而导致状态被破坏的对象很适合Spring的默认scope,每个单例的无状态对象都是线程安全的(也可以说只要是无状态的对象,不管单例多例都是线程安全的,不过单例毕竟节省了不断创建对象与GC的开销)。

无状态的对象即是自身没有状态的对象,自然也就不会因为多个线程的交替调度而破坏自身状态导致线程安全问题。无状态对象包括我们经常使用的DO、DTO、VO这些只作为数据的实体模型的贫血对象,还有Service、DAO和Controller,这些对象并没有自己的状态,它们只是用来执行某些操作的。例如,每个DAO提供的函数都只是对数据库的CRUD,而且每个数据库Connection都作为函数的局部变量(局部变量是在用户栈中的,而且用户栈本身就是线程私有的内存区域,所以不存在线程安全问题),用完即关(或交还给连接池)。

有人可能会认为,我使用request作用域不就可以避免每个请求之间的安全问题了吗?这是完全错误的,因为Controller默认是单例的,一个HTTP请求是会被多个线程执行的,这就又回到了线程的安全问题。当然,你也可以把Controller的scope改成prototype,实际上Struts2就是这么做的,但有一点要注意,Spring MVC对请求的拦截粒度是基于每个方法的,而Struts2是基于每个类的,所以把Controller设为多例将会频繁的创建与回收对象,严重影响到了性能。

通过阅读上文其实已经说的很清楚了,Spring根本就没有对bean的多线程安全问题做出任何保证与措施。对于每个bean的线程安全问题,根本原因是每个bean自身的设计。不要在bean中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用ThreadLocal把变量变为线程私有的,如果bean的实例变量或类变量需要在多个线程之间共享,那么就只能使用synchronizedlockCAS等这些实现线程同步的方法了。

对”约定优于配置”的支持

约定优于配置(convention over configuration),也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。
本质是说,开发人员仅需规定应用中不符约定的部分。例如,如果模型中有个名为Sale的类,那么数据库中对应的表就会默认命名为sales。只有在偏离这一约定时,例如将该表命名为”products_sold”,才需写有关这个名字的配置。
许多新的框架使用了约定优于配置的方法,包括:Spring,Ruby on Rails,Kohana PHP,Grails,Grok,Zend Framework,CakePHP,symfony,Maven,ASP.NET MVC,Web2py(MVC),Apache Wicket。
比如Maven对目录做了”约定优于配置”的设定:

src/main/resources: 资源文件目录;
src/main/java: Java源码目录;
src/main/webapp: web应用文件目录(当打包为war时),如WEB-INF/web.xml


对JDBC的支持

Spring主要提供JDBC模板方式、关系数据库对象化方式和SimpleJdbc方式三种方式来简化JDBC编程,这三种方式就是Spring JDBC的工作模式:

  • JDBC模板方式:Spring JDBC框架提供以下几种模板类来简化JDBC编程,实现GoF模板设计模式,将可变部分和非可变部分分离,可变部分采用回调接口方式由用户来实现:如JdbcTemplate、NamedParameterJdbcTemplate、SimpleJdbcTemplate。
  • 关系数据库操作对象化方式:Spring JDBC框架提供了将关系数据库操作对象化的表示形式,从而使用户可以采用面向对象编程来完成对数据库的访问;如MappingSqlQuery、SqlUpdate、SqlCall、SqlFunction、StoredProcedure等类。这些类的实现一旦建立即可重用并且是线程安全的。

JDBC模板

<!--数据源的配置 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring"></property>
<property name="username" value="root"></property>
<property name="password" value=""></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
public class JdbcTemplateTest {
private static JdbcTemplate jdbcTemplate;

@Test
public void testQuery() {
String sql = "select * from INFORMATION_SCHEMA.SYSTEM_TABLES";
jdbcTemplate.query(sql, new RowCallbackHandler() {
@Override
public void processRow(ResultSet rs) throws SQLException {
String value = rs.getString("TABLE_NAME");
System.out.println("Column TABLENAME:" + value);
}
});
}

@Test
public void testUpdate() {
jdbcTemplate.update("insert into test(name) values('name1')");
jdbcTemplate.update("delete from test where name=?", new Object[]{"name2"});
jdbcTemplate.update("update test set name='name3' where name=?", new Object[]{"name1"});
}
}

关系数据库对象化

对MyBatis的支持

参考mybatis-spring – MyBatis-Spring | 第二章 入门 @Ref

1. 引入mybatis-spring依赖

2. SqlSessionFactoryBean

  • 增加dataSource的定义, dataSource可以使用DruidDataSource或者自己实现的类
  • 增加sqlSessionFactory的bean, mapperLocations指定mapper.xml的位置
  • 增加transactionManager的bean, 开启Spring事务
  • 增加MapperScannerConfigurer, 它将会查找类路径下的映射器并自动将它们创建成MapperFactoryBean, 而不是注册xml配置文件中注册所有的Mapper
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="maxActive" value="10"/>
<property name="minIdle" value="5"/>
</bean>

<!-- 要注意 SqlSessionFactory 需要一个 dataSource -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/**/*.xml"/>
</bean>

<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

<!--定义注解驱动事务-->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- 配置扫描包,加载mapper代理对象 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.kuaizhan.kzweixin.dao.mapper"/>
</bean>

对Transaction的支持

  • @Transactional(value="transactionManagerPrimary", isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED)
    • value: 事务管理器
    • 隔离级别(isolation):
      • DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是:READ_COMMITTED。
      • READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
      • READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值
      • REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
      • SERIALIZABLE:提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
    • 传播行为(Propagation):所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。
      • REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常见的选择。
      • SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
      • MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
      • REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
      • NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
      • NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
      • NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。

Spring MVC Step by Step @Deprecated

  1. Pom.xml
    • build - resources # 定义资源文件?
  2. webapp/WEB-INF/web.xml
    • context-param: contextConfigLocation=classpath:spring/appcontext-.xml # 指定Spring配置路径
    • listener: # listen优先级>Servlet
      • ContextLoaderListener=org.springframework.web.context.ContextLoaderListener
      • RequestContextListener=org.springframework.web.context.request.RequestContextListener
    • servlet: org.springframework.web.servlet.DispatcherServlet
      • init-param: contextConfigLocation=classpath:appcontext-core-web.xml #指定Servlet配置路径
  3. Spring配置xml: 默认去找classpath下的application-Context.xml,这是一种约定优于配置的概念
    • context:property-placeholder: 指定*.properties位置
    • mvc:interceptors #定义拦截器
    • mvc:annotation-driven # 注册DefaultAnnotationHandlerMapping/AnnotationMethodHandlerAdapter, 用于支持@Controller等注解风格
    • mvc:resources # css/js/htm等静态资源映射
    • 增加View解析器:
      • bean id=”velocityConfigurer” class=”org.springframework.web.servlet.view.velocity.VelocityConfigurer”
      • bean id=”viewResolver” class=”org.springframework.web.servlet.view.velocity.VelocityViewResolver”
    • 增加多数据源
      • bean id=”parentDataSource” class=”org.springframework.jdbc.datasource.DriverManagerDataSource”
      • bean id=”adminDataSource” parent=”parentDataSource” # 数据源1
      • bean id=”userDataSource” parent=”parentDataSource” # 数据源2
      • bean id=”dataSource” class=”com.frogking.datasource.DynamicDataSource” # 多数源映射关系, property增加上面两个bean
      • bean id=”sessionFactory” class=”org.springframework.orm.hibernate3.LocalSessionFactoryBean”

附: Configuration XML说明

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/appcontext-*.xml</param-value>
</context-param>

<listener id="ContextLoaderListener">
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<listener id="RequestContextListener">
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/api/2/*</url-pattern>
</filter-mapping>

<servlet>
<servlet-name>comment</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:appcontext-core-web.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>comment</servlet-name>
<url-pattern>/api/*</url-pattern>
</servlet-mapping>

<error-page>
<error-code>400</error-code>
<location>/error.jsp</location>
</error-page>