Spring集成Junit

  1. 导入Spring集成Junit的坐标
  2. 使用@Runwith注解替换原来的运行期
  3. 使用@ContextConfiguration指定配置文件或配置类
  4. 使用@Autowired注入需要测试的对象
  5. 创建测试方法进行测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")

Spring集成web环境

ApplicationContext应用上下文获取方式

应用上下文对象是通过new ClasspathXmlApplicationContext (Spring配置文件)方式获取的,但是每次从容器中获得Bean时都要编写new ClasspathXmlApplicationContext(Spring配置文件),这样的弊端是配置文件加载多次,应用上下文对象创建多次

在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了

Spring提供获取应用上下文的工具

上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象

所以我们需要做的只有两件事:

  1. web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)

    <dependency>
    	<groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
    	<version>5.0.5.RELEASE</version>
    </dependency>
    <!-- 全局参数 -->
    <context-param>
    	<param-name>contextConfigLocation</param-name>
        <param-value>
            classpath:applicationContext.xml
        </param-value>
    </context-param>
    <!-- Spring的监听器 -->
    <listener>
    	<listener-class>
      org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>
    
  2. 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext

    ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    Object obj = applicationContext.getBean("id");
    

知识要点

Spring集成web环境步骤

  1. 配置ContextLoaderListener监听器
  2. 使用WebApplicationContextUtils获得应用上下文

SpringMVC

概述

SpringMVC是一种基于Java的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于SpringFrameWork的后续产品,已经融合在Spring Web Flow中

SpringMVC已经成为目前最主流的MVC框架之一,并且随着Spring 3.0的发布,全面超越Structs2,成为最优秀的MVC框架。它通过一套注解,让一个简单的Java类成为处理请求的控制器,而无需实现任何接口,同时它还支持RESTful编程风格的请求

快速入门

需求:客户端发起请求,服务端接收请求,执行逻辑并进行视图跳转

开发步骤:

  1. 导入SpringMVC相关坐标

  2. 配置SpringMVC核心控制器DispathcerServlet

  3. 创建Controller类和视图页面

  4. 使用注解配置Controller类中业务方法的映射地址

  5. 配置SpringMVC核心文件spring-mvc.xml

    <!-- 1.mvc注解驱动 -->
    <mvc:annotation-driven/>
    
    <!-- 2.配置视图解析器 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    
    <!-- 3.静态资源权限开放 -->
    <mvc:default-servlet-handler/>
    
    <!-- 4.组件扫描 -->
    <context:component-scan base-package="全类名">
    
  6. 客户端发起请求测试

执行流程

  1. 用户发送请求至前端控制器DispatcherServlet
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找),生成处理器对象及处理器拦截器(如果有则生成)并返回给DispatcherServlet
  4. DispatcherServlet调用HandlerAdapter处理器适配器
  5. HandlerAdapter经过适配调用具体的处理器(Controller,也叫后端控制器)
  6. Controller执行完成返回ModelAndView
  7. HandlerAdaptercontroller执行结果ModelAndView返回给DispatcherServlet
  8. DispatcherServletModelAndView传给DispatcherServlet
  9. ViewResolver解析后返回具体View
  10. DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中),DispatcherServlet响应用户

组件解析

注解解析

@RequestMapping

作用:用于建立请求URL和处理请求方法之间的对应关系

位置:

  1. 类上,请求URL的第一级访问目录,此处不写的话,就相当于应用的根目录
  2. 方法上,请求URL的第二级访问目录,与类上的使用@RequestMapping标注的一级目录一起组成访问虚拟路径

属性:

  1. value:用于指定请求的URL,它和path属性的作用是一样的
  2. method:用于指定请求的方式
  3. params:用于指定限制请求参数的条件,它支持简单的表达式,要求请求参数的keyvalue必须和配置的一模一样

例如:

params = {"accountName"},表示请求参数必须有accountName

params = {"money!100"},表示请求参数中money不能是100

mvc命名空间引入

命名空间:xmlns:context = "http://www.springframework.org/schema/context"

xmlns:mvc = "http://www.springframework.org/schema/mvc"

约束地址:http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context.xsd

http://www.springframework.org/schema/mvc

http://www.springframework.org/schema/mvc/spring-mvc.xsd

组件扫描

SpringMVC基于Spring容器,所以在进行SpringMVC操作时,需要将Controller存储到Spring容器中,如果使用@Controller注解标注的话,就需要使用<context:component-scan base-package="包名"/>进行组件扫描

XML配置解析

视图解析器

SpringMVC有默认组件配置,默认组件都是DispatcherServlet.properties配置文件中配置的,该配置文件地址org/springframework/web/servlet/DispatcherServlet.properties,该文件中配置了默认的视图解析器,如下:

org.springframework.web.servlet.ViewResolver = org.springframework.web.servlet.view.InternalResourceViewResolver

翻看该解析器源码,可以看到该解析器的默认设置,如下:

REDIRECT_URL_PREFIX = "redirect:"  -- 重定向前缀
FORWARD_URL_PREFIX = "forward:"    -- 转发前缀(默认值)
prefix = "";    -- 视图名称前缀
suffix = "";	-- 视图名称后缀

知识要点

SpringMVC的相关组件

  1. 前端控制器:DispatcherServlet
  2. 处理器映射器:HandlerMapping
  3. 处理器适配器:HandlerAdapter
  4. 处理器:Handler
  5. 视图解析器:View Resolver
  6. 视图:View

SpringMVC的注解和配置

  1. 请求映射注解:@RequestMapping

  2. 视图解析器配置:

    REDIRECT_URL_PREFIX = "redirect:"

    FORWARD_URL_PREFIX = "forward:"

    prefix = "";

    suffix = "";

数据响应

SpringMVC的数据响应方式:

页面跳转

  • 直接返回字符串

    这种方式会将返回的字符串与视图解析器的前后缀拼接后跳转

  • 通过ModelAndView对象返回

回写数据

  • 直接返回字符串

    Web基础阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用response.getWriter().print("hello world")即可,那么在Controller中想直接回写字符串该怎么样呢

    • 通过SpringMVC框架注入的response对象,使用response.getWriter().print("hello world")回写数据,此时不需要视图跳转,业务方法返回值为void
    • 将需要回写的字符串直接返回,但此时需要通过@RepsponseBody注解告知SpringMVC框架,方法返回的字符串不是跳转是直接在http响应体中返回
  • 返回对象或集合

    通过SpringMVC帮助我们对对象或集合进行json字符串的转换或回写,为处理器适配器配置消息转换参数,指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中进行如下配置:

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
            	<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                </bean>
            </list>
        </property>
    </bean>
    

    在方法上添加@ResponseBody就可以返回json格式的字符串,但是这样配置比较麻烦,配置的代码比较多,因此,我们可以使用mvc的注解驱动代替上述配置

    <!-- mvc的注解驱动 -->
    <mvc:annotation-driven/>
    

    在SpringMVC的各个组件中,处理器映射器、处理器适配器、视图解析器称为SpringMVC的三大组件

    使用<mvc:annotation-driven>自动加载RequestMappingHandlerMapping(处理映射器)和RequestMappingHandlerAdapter(处理适配器),可用在Spring-xml.xml配置文件中使用<mvc:annotation-driven>替代注解处理器和适配器的配置

    同时使用<mvc:annotation-driven>默认底层就会集成jackson进行对象或集合的json格式字符串的转换

知识要点

SpringMVC的数据响应方式

  1. 页面跳转
    • 直接返回字符串
    • 通过ModelAndView对象返回
  2. 回写数据
    • 直接返回字符串
    • 返回对象或集合

获得请求数据

客户端请求参数的格式是:name=value&name=value...

服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数:基本类型参数、POJO类型参数、数组类型参数、集合类型参数

获得基本类型参数

Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配

http://localhost:8080/demo1/func?username=xxx&age=12

获得POJO类型参数

Controller中的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配

http://localhost:8080/demo1/func?username=xxx&age=12

获得集合类型参数

当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以直接接收集合数据而无需使用POJO进行包装

<!-- 开发资源的访问 -->
<mvc:resources mapping="/js/**" location="/js/"/>
<!-- 交给原始的容器(Tomcat)帮你返回对应的静态资源 -->
<mvc:default-servlet-handler/>

请求数据乱码问题

当post请求时,数据会出现乱码,我们可以设置一个过滤器来进行编码的过滤

<filter>
	<filter-name>CharacterEncodingFilter</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>
</filter>
<filter-mapping>
	<filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

参数绑定注解@requestParam

当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam注解显式的绑定

注解@RequestParam还有如下参数可以使用:

  1. value:与请求参数名称
  2. required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错
  3. defaultValue:当没有指定请求参数时,使用指定的默认值赋值

获得Restful风格的参数

Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。

Restful风格的请求是使用“url+请求方式”表示一次请求目的,HTTP协议里面四个表示操作方式的动词如下:

  1. GET:用于获取资源
  2. POST:用于新建资源
  3. PUT:用于更新资源
  4. DELETE:用于删除资源

例如:

/user/1  GET:     得到id=1的user
/user/1  DELETE:  删除id=1的user
/user/1  PUT:     更新id=1的user
/user    POST:    新增user

上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定,地址/user/1可以写成/user/{id},占位符{id}对应的就是1的值,在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作

自定义类型转换器

SpringMVC默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。

自定义转换器的开发步骤:

  1. 自定义转换器类实现Converter接口
  2. 在配置文件中声明转换器
  3. <annotation-driven>中引用转换器
<!-- 声明转换器 -->
<bean id="" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
        	<bean class="全类名"></bean>
        </list>
    </property>
</bean>

获得Servlet相关API

SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象如下:

HttpServletRequest

HttpServletResponse

HttpSession

获得请求头

@RequestHeader

使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)

@RequestHeader注解的属性如下:

  1. value:请求头的名称
  2. required:是否必须携带此请求头

@CookieValue

使用@CookieValue可以获得指定Cookie的值

@CookieValue注解的属性如下:

  1. value:指定cookie的名称
  2. required:是否必须携带此cookie

文件上传

文件上传客户端三要素:

  1. 表单项type = "file"
  2. 表单的提交方式是post
  3. 表单的enctype属性是多部分表单形式,及enctype = "multipart/form-data"

文件上传原理:

  1. 当form表单修改为多部分表单时,request.getParameter()将失效
  2. enctype = "application/x-www-form-urlencoded"时,form表单的正文内容格式是:key=value&key=value&key=value
  3. 当form表单的enctype取值为Multipart/form-data时,请求正文内容就变成多部分形式

单文件上传的步骤:

  1. 导入fileuploadio坐标

    <dependency>
    	<groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.2.2</version>
    </dependency>
    <dependency>
    	<groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.4</version>
    </dependency>
    
  2. 配置文件上传解析器

    <bean id="multipartResolver"
          class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    	<!-- 上传文件总大小 -->
        <property name="maxUploadSize" value="5242800"/>
        <!-- 上***个文件的大小 -->
        <property name="maxUploadSizePerFile" value="5242800"/>
        <!-- 上传文件的编码类型 -->
        <property name="defaultEncoding" value="UTF-8"/>
    </bean>
    
  3. 编写文件上传代码

    String filename = uploadFile.getOriginalFilename();
    uploadFile.transferTo(new File("C:\\upload\\" + filename));
    

知识要点

MVC实现数据请求方式:

  1. 基本类型参数
  2. POJO类型参数
  3. 数组类型参数
  4. 集合类型参数

MVC获取数据细节:

  1. 中文乱码问题
  2. @RequestParam@PathVariable
  3. 自定义类型转换器
  4. 获得Servlet相关API
  5. @RequestHeader@CookieValue
  6. 文件上传

Spring JdbcTemplate

概述

它是Spring框架中提供的一个对象,是对原始繁琐的Jdbc API对象的简单封装。Spring框架为我们提供了很多的操作模板类,例如:操作关系型数据库的JdbcTemplateHibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等。

开发步骤

  1. 导入spring-jdbcspring-tx坐标
  2. 创建数据库表和实体
  3. 创建JdbcTemplate对象
  4. 执行数据库操作

坐标:

<dependency>
	<groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>
<dependency>
	<groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency>

测试JdbcTemplate开发步骤:

// 创建数据源对象
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("root");

JdbcTemplate jdbcTemplate = new JdbcTemplate();

// 设置数据源对象,知道数据源在哪
jdbcTemplate.setDataSource(dataSource);

// 执行操作
int row = jdbcTemplate.update("INSERT INTO account VALUES(?,?)","tom",5000);

Spring产生JdbcTemplate对象

我们可以将JdbcTemplate的创建权交给Spring,将数据源DataSource的创建权也交给Spring,在Spring容器内部将数据源DataSource注入到JdbcTemplate模板对象中,配置如下:

<!-- 如果写jdbc.properties的话需要加载 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- 数据源DataSource -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql:///test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
</bean>

<!-- JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

测试Spring产生jdbcTemplate对象

ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
JdbcTemplate jdbcTemplate = app.getBean(JdbcTemplate.class);

// 执行操作
int row = jdbcTemplate.update("INSERT INTO account VALUES(?,?)","tom",5000);

BeanPropertyRowMapper

将数据库查询结果转换为Java类对象, 常应用于使用Spring的JdbcTemplate查询数据库,获取List结果列表,数据库表字段和实体类自动对应

知识要点

  1. 导入spring-jdbcspring-tx坐标

  2. 创建数据库表和实体

  3. 创建JdbcTemplate对象

    JdbcTemplate jdbcTemplate = new JdbcTemplate();
    jdbcTemplate.setDataSource(dataSource);
    
  4. 执行数据库操作

    更新操作:

    jdbcTemplate.update(sql,params)
    

    查询操作:

    jdbcTemplate.query(sql,Mapper,params)
    jdbcTemplate.queryForObject(sql,Mapper,params)
    

SpringMVC拦截器

拦截器(interceptor)的作用

SpringMVC的拦截器类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理。

将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(Interceptor Chain),在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。

拦截器和过滤器区别

区别 过滤器(Filter) 拦截器(Interceptor)
使用范围 是servlet规范中的一部分,任何Java Web工程都可以使用 是SpringMVC框架自己的,只有使用了SpringMVC框架的工程才能使用
拦截范围 在url-pattern中配置了/*之后,可以对所有要访问的资源拦截 在<mvc:mapping path=""/>中配置了/**之后,也可以对所有资源进行拦截,但是可以通过<mvc:exclude-mapping path=""/>标签排除不需要拦截的资源

快速入门

自定义拦截器很简单,只有如下三步:

  1. 创建拦截器类实现HandlerInterceptor接口
  2. 配置拦截器
  3. 测试拦截器的拦截效果
<!-- 配置拦截器 -->
<mvc:interceptors>
	<mvc:interceptor>
        <!-- 对哪些资源执行拦截操作 -->
    	<mvc:mapping path="/**"/>
        <!-- 哪些资源排除拦截操作 -->
        <mvc:exclude-mapping path="/user/login"/>
        <bean class="拦截器的全类名"/>
    </mvc:interceptor>
</mvc:interceptors>

拦截器方法说明

方法名 说明
preHandle() 方法将在请求处理之前进行调用,该方法的返回值是布尔值和Boolean类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为true时就会继续调用下一个Interceptor的preHandle方法
postHandle() 该方法是在当前请求处理之后被调用,前提是preHandle方法的返回值为true时才能被调用,且它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中对Controller处理之后的ModelAndView对象进行操作
afterCompletion() 该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行,前提是preHandle方法的返回值为true时才能被调用

知识要点

自定义拦截器步骤:

  1. 创建拦截器类实现HandlerInterceptor接口
  2. 配置拦截器
  3. 测试拦截器的拦截效果

SpringMVC异常处理

EmptyResultDataAccessException,DAO层抛到业务层,转成null

异常处理的思路

系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试等手段减少运行时异常的发生

系统的DaoServiceController出现都通过throws Exception向上抛出,最后由SpringMVC的前端控制器交由异常处理器HandlerExceptionResolver进行异常处理

异常处理两种方式

  1. 使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver

    SpringMVC已经定义好了该类型转换器,在使用时可以根据项目情况进行相应异常与视图的映射配置

    <!-- 配置简单映射异常处理器 -->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="defaultErrorView" value="error"/> <!-- 默认错误视图 -->
        <property name="exceptionMappings">
            <map>
                <!-- 异常类型、错误视图 -->
            	<entry key="com.exception.MyException" value="error"/>
            	<entry key="java.lang.ClassCastException" value="error"/>
            </map>
        </property>
    </bean>
    
  2. 实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器

    自定义异常处理步骤:

    • 创建异常处理类实现HandlerExceptionResolver
    • 配置异常处理器
    • 编写异常页面
    • 测试异常跳转

AOP

什么是AOP

AOP为Aspect Oriented Programming的缩写,意思是面向切面编程,是通过预编译和运行期动态代理实现程序功能的统一维护的一种技术

AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率

AOP的作用和优势

作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强

优势:减少重复代码,提高开发效率,并且便于维护

AOP的底层实现

实际上,AOP的底层是通过Spring提供的动态代理技术实现的,在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,在去调用目标对象的方法,从而完成功能的增强

AOP的动态代理技术

常用的动态代理技术

  1. JDK代理:基于接口的动态代理技术

    // 目标对象
    final Target target = new Target();
    // 增强对象
    Advice advice = new Advice();
    // 返回值 就是动态生成的代理对象 基于JDK
    Proxy.newProxyInstance(
    	target.getClass().getClassLoader(), // 目标对象类加载器
        target.getClass().getInterfaces(), // 目标对象相同的接口字节码对象数组
        new InvocationHandler(){
            // 调用代理对象的任何方法 实质执行的都是invoke方法
            public Object invoke(Object proxy, Method method, Object[] args){
                advice.before(); // 前置增强(这块是自己写的方法)
                Object invoke = method.invoke(target.args); 
                // (这是反射的那个invoke)执行目标方法
                advice.afterReturning(); // 后置增强(这块是自己写的方法)
                return invoke;
            }
        }
    );
    // 调用代理对象的方法
    proxy.save();
    
  2. cglib代理:基于父类的动态代理技术

    // 目标对象
    final Target target = new Target();
    // 增强对象
    Advice advice = new Advice();
    // 返回值 就是动态生成的代理对象 基于cglib
    // 1.创建增强器
    Enhancer enhancer = new Enhancer();
    // 2.设置父类(目标)
    enhancer.setSuperclass(Target.class);
    // 3.设置回调
    enhancer.setCallback(new MethodInterceptor(){
       public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy){
           advice.before(); // 执行前置
           Object invoke = method.invoke(target, args); // 执行目标
           advice.afterReturning(); // 执行后置
           return invoke;
       } 
    });
    // 4.创建代理对象
    Target proxy = (Target) enhancer.create();
    proxy.save();
    

AOP相关概念

Spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,并通过配置的方式完成指定目标的方法增强

在正式讲解AOP的操作之前,我们必须理解AOP的相关术语,常用的术语如下:

  1. Target(目标对象):代理的目标对象
  2. Proxy(代理):一个类被AOP织入增强后,就产生了一个结果代理类
  3. Joinpoint(连接点):所谓连接点是指那些被拦截到的点,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点
  4. Pointcut(切入点):所谓切入点是指我们要对哪些JoinPoint进行拦截的定义
  5. Advice(通知/增强):所谓通知是指拦截到JoinPoint之后要做的事情就是通知
  6. Aspect(切面):是切入点和通知(引介)的结合
  7. Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程,Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入

AOP开发明确的事项

  1. 需要编写的内容

    编写核心业务代码(目标类的目标方法)

    编写切片类,切片类中有通知(增强功能方法)

    在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合

  2. AOP技术实现的内容

    Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理镀锡,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行

  3. AOP底层使用哪种代理方式

    在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式

知识要点

AOP:面向切面编程

AOP底层实现:基于JDK的动态代理和基于Cglib的动态代理

AOP的重点概念:

  1. Pointcut(切入点):被增强的方法
  2. Advice(通知/增强):封装增强业务逻辑的方法
  3. Aspect(切面):切点+通知
  4. Weaving(织入):将切点与通知结合的过程

开发明确事项:

谁是切点(切点表达式配置)、谁是通知(切面类中的增强方法)、将切点和通知进行织入配置

基于XML的AOP开发

快速入门

  1. 导入AOP相关坐标

    <dependency>
    	<groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.4</version>
    </dependency>
    
  2. 创建目标接口和目标类(内部有切点)

  3. 创建切面类(内部有增强方法)

  4. 将目标类和切面类的对象创建权交给Spring

  5. applicationContext.xml中配置织入关系

    <!-- 目标对象 -->
    <bean id="target" class="com.Target"></bean>
    <!-- 切面对象 -->
    <bean id="myAspect" class="com.MyAspect"></bean>
    <!-- 配置织入:告诉spring框架 哪些方法(切点)需要进行哪些增强 -->
    <aop:config>
        <!-- 声明切面 -->
    	<aop:aspect ref="myAspect">
        	<!-- 切面:切点+通知 -->
            <aop:before method="before" pointcut="execution(public void com.Target.save())"></aop:before>
        </aop:aspect>
    </aop:config>
    
  6. 测试代码

    <dependency>
    	<groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>
    
    @Runwith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class AopTest{
        @Autowired
        private TargetInterface target;
        @Test
        public void test1(){
            target.save();
        }
    }
    

XML配置AOP详解

  1. 切点表达式的写法

    execution([修饰符]返回值类型 包名.类名.方法名(参数))
    
    • 访问修饰符可以忽略
    • 返回值类型、包名、类名、方法名可以使用星号*代表任意
    • 包名与类名之间一个点.代表当前包下的类,两个点..表示当前包及其子包下的类
    • 参数列表可以使用两个点..表示任意个数,任意类型的参数列表
    execution(public void com.aop.Target.method())
    // 特定函数
    execution(void com.aop.Target.*(..))
    // (..)代表任意参数,*代表任意方法,这个代表Target类下的任意方法任意参数
    execution(* com.aop.*.*(..))
    // aop包下的任意类的任意方法任意参数
    execution(* com.aop..*.*(..))
    // aop包及其子包下的任意类的任意方法任意参数
    execution(* *..*.*(..))
    // 都任意
    
  2. 通知的类型

    通知的配置语法:

    <aop:通知类型 method="切面类中方法名" pointcut="切点表达式"></aop:通知类型>
    
    名称 标签 说明
    前置通知 <aop:before> 用于配置前置通知,指定增强的方法在切入点方法之前执行
    后置通知 <aop:after-returning> 用于配置后置通知,指定增强的方法在切入点方法之后执行
    环绕通知 <aop:around> 用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
    异常抛出通知 <aop:throwing> 用于配置异常抛出通知,指定增强的方法在出现异常时执行
    最终通知 <aop:after> 用于配置最终通知,无论增强方式执行是否有异常都会执行
  3. 切点表达式的抽取

    当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用pointcut-ref属性代替pointcut属性来引用抽取后的切点表达式

    <aop:config>
    	<!-- 引用myAspect的Bean为切面对象 -->
        <aop:aspect ref="myAspect">
        	<aop:pointcut id="myPointcut" expression="execution(* com.aop.*.*(..))"/>
            <aop:before method="before" pointcut-ref="myPointcut"></aop:before>
        </aop:aspect>
    </aop:config>
    

知识要点

AOP织入的配置

<aop:config>
	<aop:aspect ref="切面类">
    	<aop:before method="通知方法名称" pointcut="切点表达式"></aop:before>
    </aop:aspect>
</aop:config>

通知的类型:前置通知、后置通知、环绕通知、异常抛出通知、最终通知

切点表达式的写法:execution([修饰符]返回值类型 包名.类名.方法名(参数))

基于注解的AOP开发

快速入门

基于注解的AOP开发步骤:

  1. 创建目标接口和目标类(内部有切点)
  2. 创建切面类(内部有增强方法)
  3. 将目标类和切面类的对象创建权交给Spring
  4. 在切面类中使用注解配置织入关系
  5. 在配置文件中开启组价扫描和AOP的自动代理
  6. 测试
@Component("myAspect")
@Aspect // 标注当前myAspect是一个切面类
public class MyAspect {
    // 配置前置通知
    @Before("execution(*com.anno.*.*(..))")
    public void before(){}
}
<!-- 组件扫描 -->
<context:component-scan base-package="com.anno"/>
<!-- aop自动代理 -->
<aop:aspectj-***/>

注解配置AOP详解

  1. 注解通知的类型

    通知的配置语法:@通知注解("切点表达式")

    名称 注解 说明
    前置通知 @Before 用于配置前置通知,指定增强的方法在切入点方法之前执行
    后置通知 @AfterReturning 用于配置后置通知,指定增强的方法在切入点方法之后执行
    环绕通知 @Around 用于配置环绕通知,指定增强的方法在切入点方法之前和之后都执行
    异常抛出通知 @AfterThrowing 用于配置异常抛出通知,指定增强的方法在出现异常时执行
    最终通知 @After 用于配置最终通知,无论增强方式执行是否有异常都会执行
  2. 切点表达式的抽取

    同xml配置aop一样,我们可以将切点表达式抽取,抽取方式是在切面内定义方法,在该方法上使用@Pointcut注解定义切点表达式,然后在增强注解中进行引用,具体如下:

    @Component("myAspect")
    @Aspect
    public class MyAspect {
        @Before("MyAspect.myPoint()")
        public void before(){}
        @After("MyAspect.pointcut()")
        public void after(){}
        // 定义切点表达式
        @Pointcut("execution(*com.aop.*.*(..))")
        public void myPoint(){}
    }
    

    知识要点

    注解aop开发步骤

    1. 使用@Aspect标注切面类
    2. 使用@通知注解标注通知方法
    3. 在配置文件中配置aop自动代理<aop:aspectj-***/>

编程式事务控制相关对象

PlatformTransactionManager

PlatformTransactionManager接口是Spring的事务管理器,它里面提供了我们常用的操作事务的方法

方法 说明
TransactionStatus getTransaction(TransactionDefination defination) 获取事务的状态信息
void commit(TransactionStatus status) 提交事务
void rollback(TransactionStatus status) 回滚事务

注意:

PlatformTransactionManager是接口类型,不同的Dao层技术则有不同的实现类,例如:Dao层技术是jdbc或mybatis时:org.springframework.jdbc.datasource.DataSourceTransactionManager,Dao层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

TransactionDefinition

TransactionDefinition是事务的定义信息对象,里面有如下方法

方法 说明
int getIsolationLevel() 获得事务的隔离级别
int getPropogationBehavior() 获得事务的传播行为
int getTimeout() 获得超时时间
boolean isReadOnly() 是否只读
  1. 事务的隔离级别

    设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和幻读

    ISOLATION_DEFAULT

    ISOLATION_READ_UNCOMMITTED

    ISOLATION_READ_COMMITTED

    ISOLATION_REPEATABLE_READ

    ISOLATION_SERIALIZABLE

  2. 事务传播行为

    REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)

    SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)

    MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常

    REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起

    NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起

    NEVER:以非事务方式运行,如果当前存在事务,抛出异常

    NESTED:如果当前存在事务,则在嵌套事务内执行,如果当前没有事务,则执行REQUIRED类似的操作

    超时时间:默认值是-1,没有超时限制,如果有,以秒为单位进行设置

    是否只读:建议查询时设置为只读

TransactionStatus

TransactionStatus接口提供的是事务具体的运行状态,方法介绍如下

方法 说明
boolean hasSavepoint() 是否存储回滚点
boolean isCompleted() 事务是否完成
boolean isNewTransaction() 是否是新事务
boolean isRollbackOnly() 事务是否回滚

基于XML的声明式事务控制

Spring的声明式事务顾名思义就是采用声明的方式来处理事务,这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务

声明式事务处理的作用

事务管理不侵入开发的组件,具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可

在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便

注意:Spring声明式事务控制底层就是AOP

声明式事务控制的实现

声明式事务控制明确事项:谁是切点?谁是通知?配置切面?

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

<!-- 通知 事务的增强 -->
<tx:advice id="txAdvice" transaction-manager="">
    <!-- 设置事务的属性信息的 -->
    <tx:attributes>
        <!-- 被增强的方法名 -->
    	<tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
        <tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
        <tx:method name="update*" isolation="...".../>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

<!-- 配置事务的aop织入 -->
<aop:config>
	<aop:advisor advice-ref="txAdvice" pointcut="execution(*com.service.impl.*.*(..))">
    </aop:advisor>
</aop:config>

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

注解配置声明式事务控制解析

  1. 使用@Transactional在需要进行事务控制的类或是方法上修饰,注解可用的属性同xml配置方式,例如隔离级别、传播行为等
  2. 注解使用在类上,那么该类下的所有方法都使用同一套注解参数配置
  3. 使用在方法上,不同的方法可以采用不同的事务参数配置
  4. xml配置文件中要开启事务的注解驱动<tx:annotation-driven/>

知识要点

注解声明式事务控制的配置要点

  1. 平台事务管理器配置(xml方式)
  2. 事务通知的配置(@Transactional注解配置)
  3. 事务注解驱动的配置<tx:annotation-driven/>