SSM框架 ------- springmvc学习笔记

132 阅读15分钟

一、springmvc的简介

1.1 认识spring mvc

springMVC是一种基于Java的实现MVC设计模型的请求驱动模型的轻量级框架web框架,属于springframework的后续产品,已经融合spring web flow中。

springMVC已经成为目前最主流的MVC的框架之一,通过一套注解,让一个简单的java类成为处理请求的控制器,而无须实现任何接口。同时支持restful编程风格的要求。

spring mvc的三层架构的交互

客户端通过http发送请求 --- > tomcat引擎1.接收客户端请求2.封装代表请求的request和响应response 3. 调用请求servlet资源(共有行为和特有行为)共有行为:接收数据、封装实体、指派视图;特有行为:cookie、表单校验等。共有行为是前端控制器

1.2 spring mvc的快速入门

下面通过一个需求先感受spring mvc对前端和后端的数据交互:客户端发起请求,服务端接收请求,执行逻辑并进行视图跳转。

  • 导入spring mvc的坐标
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2.1-b03</version>
</dependency>
<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
<!--导入spring-mvc依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
  • 配置spring mvc核心控制器dispathcerservlet
<!--配置前端控制器-->
<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:spring-mvc.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>
  • 创建controller类和视图页面
@Controller
public class UserController {

    //请求地址http://localhost:8080/user/test
    @RequestMapping("/user")
    public String save(){
        System.out.println("controller save running.......");
        return "success.jsp";
    }
}

success.jsp

<h1>hello_spring_mvc_success! </h1>
  • 使用注解配置controller类中的业务方法和映射地址
@Controller
public class UserController {

    @RequestMapping("/user")
    public String save(){
        System.out.println("controller save running.......");
        return "success.jsp";
    }
}
  • 配置springMVC核心文件spring-mvc.xml
<!--controller组件扫描-->
<context:component-scan base-package="cn.hfnu.controller"/>
  • 客户端发起请求测试图

image.png

1.3 spring mvc的执行流程

image.png

image.png

二、spring mvc的组件解析

2.1 spring mvc的交互核心架构图

image.png

  1. 用户发送http请求至前端控制器dispatcherservlet。 http://localhost:8080/user
  2. dispatcherservlet收到请求后调用handlermapping处理器映射器。 @RequestMapping("/user")
  3. 处理器映射器找到具体的处理器(可以根据xml配置、注解进行查找)生成处理器对象及处理器拦截器一并返回给DispatcherServlet。
  4. dispacherservlet调用handleradapter处理器适配器。
  5. handleradapter经过适配调用具体的处理器(controller,后端控制器)。
  6. controller执行完成返回model and view。 return “success.jsp”
  7. handleradapter将model and view传给viewreslover视图解析器。
  8. viewreslover解析后返回具体view,渲染视图。

2.2 spring mvc的注解解析

@RequestMapping 作用:用于建立请求URL和处理请求方法之间的对应关系。//请求地址 http://localhost:8080/user/test

@RequestMapping位置: 类上,请求url的第一级访问目录。此处不写,相当于应用的根目录。 方法上,请求url的第二级访问目录,与类上的使用@RequestMapping标注的一级目录一起组成访问虚拟路径。

@RequestMapping属性:

value:用于指定请求url。它和path属性作用是一样的。

method:用于指定请求方式。

params:用于指定限制请求参数的条件。支持简单的表达式,要求请求参数的key和value必须和配置的一模一样。

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

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

@Controller
@RequestMapping(value = "/user")
public class UserController {

    //请求地址http://localhost:8080/user/test
    @RequestMapping(value = "/test", method= RequestMethod.GET, params = {"username", "pwd"})
    public String save() throws Exception{
        System.out.println("controller save running.......");
        return "/success.jsp";
    }
}

效果图

image.png

mvc的命名空间

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

mvc的约束地址

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

mvc的组件扫描

<!—方式一:controller组件扫描-->
<context:component-scan base-package="cn.hfnu.controller"/>

<!--方式二:controller组件扫描-->
<context:component-scan base-package="cn.hfnu.controller">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

2.3 spring mvc的资源配置器

<!--配置资源视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- /jsp/success.jsp -->
    <property name="prefix" value="/jsp/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>

三、spring mvc的数据响应

3.1 数据响应方式

3.1.1 页面跳转

返回字符串形式:直接返回字符串:此种方式将返回字符串与视图解析器的前后缀拼接后跳转。 转发资源地址:/WEB-INF/views/index.jsp 返回带有前缀的字符串: 转发:forward:/WEB-INF/views/index.jsp 重定向:redirect:/index.jsp

  1. 直接返回字符串:将返回的字符串和视图解析器的前后缀拼接进行页面跳转
@RequestMapping(value = "/test", method= RequestMethod.GET, params = {"username", "pwd"})
public String save() throws Exception{
    System.out.println("controller save running.......");
    return "success";
}
<!--配置资源视图解析器-->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <!-- /jsp/success.jsp -->
    <property name="prefix" value="/jsp/"></property>
    <property name="suffix" value=".jsp"></property>
</bean>
  1. 通过model and view 对象返回
@RequestMapping(value = "/test2")
public ModelAndView save2() throws Exception{
    ModelAndView modelAndView = new ModelAndView();
    modelAndView.setViewName("index");
    return modelAndView;
}
@RequestMapping(value = "/test3")
public ModelAndView save3(ModelAndView modelAndView) throws Exception{
    List<String> list = new ArrayList<>();
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
    Student student = new Student(1001, "小明", "男", "1999-01-01");

    //设置模型数据
    modelAndView.addObject("username","zhangsan");
    modelAndView.addObject("age","18");
    modelAndView.addObject("List",list);
    modelAndView.addObject("Student", "student");

    //设置视图名称
    modelAndView.setViewName("success");
    return modelAndView;
}
@RequestMapping(value = "/test4")
public String save4(Model model) throws Exception{
    model.addAttribute("username", "lisi");
    model.addAttribute("pwd", "123456");
    return "success";
}
@RequestMapping(value = "/test5")
public String save5(HttpServletRequest request) throws Exception{
    request.setAttribute("username", "刘华");
    request.setAttribute("age", "20");
    return "success";
}

3.1.2 回写数据

  1. 直接返回字符串
@RequestMapping(value = "/test6")
public void save6(HttpServletResponse response) throws Exception{
    response.getWriter().print("hello liuhua...");
}
@RequestMapping(value = "/test7")
@ResponseBody
public String save7() throws Exception{
    return "hello liuhua...";
}
  1. 直接返回json字符串
@RequestMapping(value = "/test8")
@ResponseBody
public String save8() throws Exception{
    return "{\"username\":\"zhangsan\",\"age\":\"18\"}";
}
@RequestMapping(value = "/test9")
@ResponseBody
public String save9() throws Exception{
    Student student = new Student(1001, "曹操", "18", "男");
    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(student);

    return json;
}
  1. 返回对象或集合
<!--json格式转换工具-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.9.0</version>
</dependency>
<!--配置处理器映射器并转换json字符串-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="messageConverters">
        <list>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" />
        </list>
    </property>
</bean>
<!--mvc的注解驱动-->
<mvc:annotation-driven/>
@RequestMapping(value = "/test10")
@ResponseBody
public Student save10() throws Exception{
    Student student = new Student(1001, "曹操", "18", "1");
    return student;
}

四、spring mvc获得参数

4.1 获得请求参数

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

服务器端接收参数后需要封装数据。

4.2 获得基本类型参数

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

@RequestMapping(value = "/test11")
@ResponseBody
public void save11(String username, int age) throws Exception{
    System.out.println("=======username======"+username);
    System.out.println("=======age========="+age);
}

image.png

4.3 获得pojo类型参数

controller中的业务方法的pojo参数的属性名和请求参数的name一致,参数值会自动映射匹配。

@RequestMapping(value = "/test12")
@ResponseBody
public void save12(Student student) throws Exception{
    System.out.println("=======student======="+student);
}

image.png

4.4 获得数组类型参数

controller中的业务方法数组名称与请求参数的name一致,参数值自动映射配置。

@RequestMapping(value = "/test13")
@ResponseBody
public void save13(String[] str) throws Exception{
    System.out.println("=======数组======="+ Arrays.asList(str));
}

image.png

4.5 获得集合类型参数

获得集合参数时,要将集合参数包装到pojo才可以。

@RequestMapping(value = "/test14")
@ResponseBody
public void save14(Vo vo) throws Exception{
    System.out.println("=======vo========="+vo);
}

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

image.png

前端ajax接收参数mapper错误

静态资源的访问权限:

<script src="js/jquery-3.3.1.js"></script>

<script>
    var studentList = new Array();
    studentList.push(id=1001, username="xiaoming", age=18, gender=1);
    studentList.push(id=1002, username="xiaowang", age=18, gender=1);

    $.ajax({
        type: 'post',
        url: '${pageContext.request.contextPath}/user/test15',
        data: JSON.stringify(studentList),
        contentType: 'application/json;charset=utf-8'
    });
</script>
@RequestMapping(value = "/test15")
@ResponseBody
public void save15(@RequestBody List<Student> studentList) throws Exception{
    System.out.println("========studentList=====>"+studentList);
}
<!--开放静态资源的访问权限-->
<!--<mvc:resources mapping="/js/**" location="/js/"/>
<mvc:resources mapping="/img/**" location="/img/"/>-->

<mvc:default-servlet-handler />

image.png

4.6 请求数据乱码问题

<!--解决中文乱码问题-->
<!--配置全局过滤器filter-->
<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>

4.7 参数绑定注解@requestParam

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

@RequestMapping(value = "/test16")
@ResponseBody
public void save1(@RequestParam(value = "name") String username) throws Exception{
    System.out.println("========username=====>"+username);
}
  • 注解参数@RequestParam还有以下参数可以可以使用:

value:与请求参数名称

required:此在指定的请求参数是否必须包括,默认是true,提交时如果此参数则报错。

defaultValue:当没有指定请求参数时,则使用指定的默认值赋值

@RequestParam(value = "name", required = false, defaultValue = "hello") String username

4.8 获得Restful风格的参数

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

Restful风格的请求是使用url+请求方式 表示一次请求目的,http协议的四种操作方式:

get;用于获取资源

post:用于新建资源

put:用于更新资源

delete:用于删除资源

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

//http://localhost:8080/user/test17/xxx
@RequestMapping(value = "/test17/{username}")
@ResponseBody
public void save17(@PathVariable(value = "username") String username) throws Exception{
    System.out.println("========username=====>"+username);
}

4.9 自定义类型转换器

spring mvc默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int类型进行参数设置。

自定义类型的转换器的步骤

1)定义转换器实现convert接口

2)在配置文件中声明转换器

3)在annotation-driven中引用转换器

@Override
public Date convert(String strDate) {
    //将日期字符串转换成日期对象
    SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    Date date = null;
    try {
        date = format.parse(strDate);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return date;
}
<!--配置自定义格式转换器-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="cn.hfnu.converter.DateConverter"></bean>
        </list>
    </property>
</bean>
<!--mvc的注解驱动-->
<mvc:annotation-driven conversion-service="conversionService"/>

4.10 获得servlet的相关API

spring mvc支持使用原始servlet API对象作为控制器方法的参数进行注入,常用的对象:httpservletrequest / httpservletresponse / httpsession

4.11 获得请求头

@RequestHeader 使用@RequestHeader可以获得请求头信息,相当于web阶段request.getHeader(name) @RequestHeader注解的属性如下: value: 请求头名称 required: 是否必须携带此请求头 @CookieValue注解的属性: value: 指定cookie的名称 required: 是否必须携带此cookie

4.12 文件上传

  1. 文件上传三要素

表单项type=”file”

文件表单的提交方式post

表单的enctype属性是多部分表单形式,enctype= “multipart/form-data”

  1. 文件上传原理

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

<%--单文件上传项--%>
<form action="/user/test22" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username"><br>
    文件:<input type="file" name="upload"><br>
    <input type="submit" value="提交">
</form>

4.12.1 单文件上传

思路分析:

  1. 导入filerupload和io坐标

  2. 配置文件上传解析器

  3. 编写文件上传代码

<!--导入文件上传依赖-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>
<!--配置文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--上传文件总大小-->
    <property name="maxUploadSize" value="5242800"></property>
    <!--单个文件的大小-->
    <property name="maxUploadSizePerFile" value="5242800"/>
    <!--上传文件的编码类型-->
    <property name="defaultEncoding" value="UTF-8"/>
</bean>
@RequestMapping(value = "/test22")
@ResponseBody
public void save22(String username, MultipartFile upload) throws Exception{
    System.out.println("========username=====>"+username);
    System.out.println("==========upload======>"+upload);
    //获取文件名称
    String originalFilename = upload.getOriginalFilename();
    //保存文件 实际项目中要上传到服务器上
    upload.transferTo(new File("D:\\edwarder\\upload\\"+originalFilename));
}

4.12.2 多文件上传

多文件上传,只需要将页面修改为多个文件上传项,将方法参数multipartfile类型修改为multipartfile[]

<%--多文件上传项--%>
<form action="/user/test23" method="post" enctype="multipart/form-data">
    用户名:<input type="text" name="username"><br>
    文件1:<input type="file" name="upload"><br>
    文件2:<input type="file" name="upload"><br>
    文件3:<input type="file" name="upload"><br>
    <input type="submit" value="提交">
</form>
@RequestMapping(value = "/test23")
@ResponseBody
public void save23(String username, MultipartFile[] upload) throws Exception{
    System.out.println("========username=====>"+username);
    System.out.println("==========upload======>"+upload);
    for (MultipartFile multipartFile : upload) {
        String originalFilename = multipartFile.getOriginalFilename();
        multipartFile.transferTo(new File("D:\\edwarder\\upload\\"+originalFilename));
    }
}

效果图:

image.png

5 spring mvc拦截器

5.1 拦截器(interceptor)的作用

spring mvc的拦截器类似于servlet开发中的过滤器filter。用于对处理器进行预处理和后处理。 将拦截器按一定的顺序联结成一条链,称为拦截器链(Interceptor Chain )。在访问被拦截的方法或字段,拦截器中的拦截器就会按其之前定义的顺序被调用。拦截器是AOP思想体现

5.2 拦截器和过滤器的区别

使用范围:是servlet规范的一部分,任何web工程都可以使用;拦截器是spring mvc框架自己的,只能是spring mvc框架才能使用。 拦截范围:filter在url-pattern中配置/* 之后,可以对所有要访问的资源拦截;interceptor在<mvc:mapping path=”” />中配置了/** 之后,也可以多所有资源进行拦截,可以通过<mvc:exclude-mapping path=”” />标签排除不需要拦截的资源。

5.3 拦截器是快速入门

配置拦截器:spring-mvc.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           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">

    <!--mvc注解驱动-->
    <mvc:annotation-driven />
    <!--配置内部资源视图解析器-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>
    <!--开启静态资源的访问权限-->
    <mvc:default-servlet-handler/>
    <!--组件扫描,扫描controller层-->
    <context:component-scan base-package="cn.hfnu.controller"></context:component-scan>
    <!--配置自定义的拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--对哪些资源执行拦截操作-->
            <mvc:mapping path="/**"/>
            <bean class="cn.hfnu.interceptor.MyInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

</beans>

创建拦截器类实现HandlerInterceptor接口:

public class MyInterceptor implements HandlerInterceptor {

    //在目标方法执行之前 : 执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("=======preHandle=========");
        return false;
    }

    //在目标方法执行之后, 视图对象执行之前 : 执行
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("==========postHandle=========");
    }

    //在所有执行流程完毕后  : 执行
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("===========afterCompletion===========");
    }
}

controller层:

@Controller
public class TargetController {

    @RequestMapping(value = "/target")
    public ModelAndView show(ModelAndView modelAndView){
        System.out.println("=========目标资源被执行=======");
        modelAndView.addObject("name", "hello, heshi");
        modelAndView.setViewName("index");
        return modelAndView;
    }

}

效果图:

image.png

image.png

//在目标方法执行之前 : 执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    System.out.println("=======preHandle=========");
    String param = request.getParameter("param");
    if ("xiaomeng".equals(param)){
        return true;
    }else {
        request.getRequestDispatcher("/error.jsp").forward(request, response);
        return false;  //返回false不放行; 返回true放行
    }
}

效果图:

image.png

//在目标方法执行之后, 视图对象执行之前 : 执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    modelAndView.addObject("name", "锦绣大道");
    System.out.println("==========postHandle=========");
}

效果图:

image.png

多个拦截器:

<!--配置自定义的拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--对哪些资源执行拦截操作-->
        <mvc:mapping path="/**"/>
        <bean class="cn.hfnu.interceptor.MyInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <!--对哪些资源执行拦截操作-->
        <mvc:mapping path="/**"/>
        <bean class="cn.hfnu.interceptor.MyInterceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>

效果图:

image.png

5.4 拦截器方法说明

image.png

5.5 案例:用户登录权限控制

需求:用户没有登录的情况下,不能对后台菜单进行访问操作,点击菜单跳转到登录页面,只有用户登录成功才能进行后台功能的操作。

public class PrivilegeInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断用户是否登录,本质:session中是否有user
        HttpSession session = request.getSession();
        User user = (User) session.getAttribute("user");
        if (user == null){
            //没有登录去登录
            response.sendRedirect(request.getContextPath()+"/login.jsp");
            return false;
        }
        //放行
        return true;
    }
<!--配置自定义拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="cn.hfnu.interceptor.PrivilegeInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

效果图

image.png

image.png

6 spring 的AOP简介

6.1 什么是AOP

AOP(Aspect Oriented Programming),意思是面向切面编程,通过预编译的方式和运行期动态代理实现程序功能的统一维护的一种技术。松耦合 AOP是OOP的延续,是软件开发的一个热点,也是spring框架的另一个重要内容,是函数式编程的一种衍生范型。利用AOP对业务逻辑的各个部分进行隔离(解耦合),从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。A B

6.2 AOP的作用及优势

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

save(user) update(user) delete(user) 目标方法

日志控制 功能增强

运行时:内存 ---> 日志控制save update delete

配置文件.xml

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

6.3 AOP底层实现

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

6.4 AOP动态代理技术

常用的动态代理技术:

JDK代理:基于接口的动态代理技术。通用性

cglib代理:基于父类的动态代理技术。

6.5 JDK动态代理

目标对象 == > 目标接口 == > 代理对象(内存中运行期)

public interface TargetInterface {

    public void save();
public class Target implements TargetInterface {
    @Override
    public void save() {
        System.out.println("=====save running===========");
    }
public class Advice {

    public void before(){
        System.out.println("======前置增强=========");
    }

    public void after(){
        System.out.println("======后置增强=========");
    }

测试:

//目标对象
Target target = new Target();

//增强对象
Advice advice = new Advice();

@Test
public void JDKProxyTest(){

    //返回值 : 动态代理生成的代理对象
    TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
            target.getClass().getClassLoader(),  //目标对象类加载器
            target.getClass().getInterfaces(),  //目标对象相同的接口字节码对象数组
            new InvocationHandler() {
                //调用代理对象的任何方法实质执行的是invoke方法
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    advice.before();//前置增强
                    Object invoke = method.invoke(target, args);
                    advice.after();//后置增强
                    return invoke;
                }
            }
    );
    //调用代理对象的方法
    proxy.save();

}

效果图:

image.png

6.6 cglib动态代理

目标对象 == > 代理对象(内存 运行时)

image.png

@Test
public void test(){
    //目标对象
    Target target = new Target();

    //增强对象
    Advice advice = new Advice();

    //返回值 基于cglib的动态生成代理对象
    //创建增强器
    Enhancer enhancer = new Enhancer();
    //设置父类
    enhancer.setSuperclass(Target.class);
    //设置回调
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            //执行前置增强
            advice.before();
            //执行目标资源
            Object invoke = method.invoke(target, args);
            //执行后置增强
            advice.after();
            return invoke;
        }
    });
    //创建代理对象
    Target proxy = (Target) enhancer.create();

    proxy.save();
}

效果图:

image.png

6.7 AOP相关概念

spring的AOP实现底层就是对上面的动态代理的代码进行了封装,封装后我们只需要对需要关注的部分进行代码编写,通过配置方式完成指定目标的方法增强。
Target(目标对象):代理的目标对象。
Proxy(代理):一个类被AOP织入增强后产生一个结果代理类
Joinpoint(连接点):所谓连接点是指那些被拦截到的点,spring中这些点指的是方法,spring只支持方法类型的连接点。  可以被增强的方法  
Pointcut(切入点):对哪些Joinpoint进行拦截定义。  真正配置被增强的方法  
Advice(通知/增强):拦截到Joinpoint之后所要做的事情就是通知。  对目标方法增强的逻辑  
Aspect(切面):是切入点和通知(引介)的结合。  将目标方法加增强;
Weaving(织入):把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,AspectJ采用编译器织入和类装载期织入。  将切点和增强结合的过程  

6.8 AOP开发明确的事项

编写的内容:
1. 编写核心业务代码(目标类的目标方法)
2. 编写切面类,切面类中有通知(增强功能方法)
3. 在配置文件中,配置织入关系,将哪些通知和连接点相结合
实现的内容:
spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
**执行配置好的切点方法,一旦执行spring就监控被增强方法创建被代理对象,调用代理对象的同名方法。调用代理对象的目标方法时spring进行功能方法的织入。**

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

7 基于XML的AOP开发

7.1 快速入门

1. 导入aop坐标:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.7</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

2. 创建目标接口和目标类

public class Target implements TargetInterface {
    @Override
    public void save() {
        System.out.println("=====save running===========");
    }
}
public interface TargetInterface {

    public void save();
}

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

public class MyAspect {
    
    public void before(){
        System.out.println("===========前置增强=========");
    }
}
  1. 将目标类和切面类对象创建权交给spring

  2. 在applicationContext.xml中配置织入关系。在applicationContext.xml中告诉spring容器哪些方法需要被哪些增强。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--配置目标对象-->
    <bean id="target" class="cn.hfnu.aop.Target"></bean>

    <!--配置切面对象-->
    <bean id="myAspect" class="cn.hfnu.aop.MyAspect"></bean>

    <!--配置织入-->
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <aop:before method="before" pointcut="execution(public void cn.hfnu.aop.Target.save())"/>
        </aop:aspect>
    </aop:config>

</beans>

6. 测试代码

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {

    @Autowired
    private TargetInterface targetInterface;

    @Test
    public void test1(){
        targetInterface.save();
    }

效果图:

image.png

8 spring的事务控制