第三阶段(day08)springmvc2

本文详述了Spring MVC的实践应用,包括RESTful API的设计原则,如URL与请求方法的对应,以及静态资源处理。介绍了异常处理的局部与全局策略,包括自定义异常、枚举错误码和使用@RestControllerAdvice进行全局统一处理。还涉及了文件上传下载的实现,以及拦截器的使用,如登录拦截器的配置。最后讨论了跨域问题,提供了基于过滤器的跨域配置方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 复习:

引入jar包

springmvc.xml

web.xml

实体类AxiosResult:

@JsonInclude(JsonInclude.Include.NON_NULL)
public class AxiosResult {
    private Integer code;
    private String msg;
    private Object data;

    public AxiosResult(E e, Object data) {
        this(e);
        this.data = data;
    }

    public AxiosResult(Object data) {
        this(E.SUC);
        this.data = data;
    }

    public AxiosResult(E e) {
        this.code = e.getCode();
        this.msg = e.getMsg();
    }

    public static AxiosResult suc(){
        return new AxiosResult(E.SUC);
    }

    public static AxiosResult suc(Object data){
        return new AxiosResult(E.SUC,data);
    }

    public static AxiosResult error(){
        return new AxiosResult(E.ERROR);
    }
    public static AxiosResult error(E e){
        return new AxiosResult(e);
    }
    public static AxiosResult error(Object data){
        return new AxiosResult(E.ERROR,data);
    }
    public static AxiosResult error(E e,Object data){
        return new AxiosResult(e,data);
    }
    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

响应码和响应信息放到枚举类:

public enum E {
    SUC(20000,"suc"),
    ERROR(50000,"ERROR"),
    NOT_EXISTS(50001,"数据不存在"),
    PWD_ERROR(50002,"密码错误"),
    FILE_MAX_SIZE(50003,"上传文件过大" ),
    NO_LOGIN(50004,"用户未登录" );
    private Integer code;
    private String msg;

    E(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

控制层也可以叫handler(处理器,即springmvc的处理器对象)

获取用户列表,增加用户

springmvc执行流程:

①.Tomcat启动,实例化DispatcherServlet。加载springmvc.xml,创建容器。创建容器时做包扫描,将

RestController注册到容器。

同时引入springmvc的注解识别,会对RequestMapping和GetMapping做url映射,将映射结果存到处理器映射对象。

②.用户请求进来,进DispatcherServlet,解析url,根据url找对应的方法,执行它。执行需要参数,根据方法的参数结构(带不带RequestBody注解),拿到数据。

③.执行方法得到的结果,再通过json转换器,把对象转成json字符串,放在响应体里。

1.RESTURL(重要)

是一种url的写法风格,与之前url写法区别如下:

rest风格的url,没有动词

传统url参数请求方法rest风格参数
/user/getById?id=12GET|GET/user/12参数属于uri的一部分
/user/add请求体jsonPOST|POST/user请求体json
/user/update请求体jsonPOST|PUT/user请求体json
/user/deleteByID?id=12GET|DELETE/user/12参数属于uri的一部

注意点1:请求方法与url只要有一个(url排除路径变量的)不同,就是不同的处理器方法.

 (url一样,请求方法不同。get做查询,post做添加,put做更新,delete做删除) 

注意点2:参数不再?追加到url后面,而是属于uri的一部分.

(/user/12,第二层url是参数)

改成rest风格的url

 

 括号里是url路径变量,取该参数,用@PathVariable("路径变量名")

 若路径变量名和形参名一致,直接写个注解即可。 

若再来个根据名字取用户

 他们的请求方法和url长度都一样,就无法区分。所谓的请求方法与url只要有一个不同,是url排除路径变量的不同,此时都是/user,此时需要再加一层。

路径变量可以有多个

注意点3:只有参数个数是固定的情况才使用rest风格的url,参数不能为""

(条件查询不能用该风格)

(若不传手机号,就是空路径,不支持)

 (这种情况还是用原来的)

只有get请求和delete请求用路径变量。

put请求,默认无法获取请求体的k=v参数.本质来说tomcat就不支持put,delete请求.要用put,delete请求要求前端后端选择axios,springmvc都要支持put,delete. axios在进行put,delte请求时,底层仍然是发送post,method:put|delete,springmvc在解析method,对put,delete请求的请求再进行分发.

2.静态资源处理

在前端未分离开发模式下才需要静态资源处理.一旦前端代码独立项目部署,那么不需要静态资源处理.

静态资源:html,css,js,jpb,png.音视频,文件

新建main.css

body{
    background-color: burlywood;
}

新建main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/css/main.css">
</head>
<body>
    main.html
</body>
</html>

在index.jsp中:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
 index.jsp
  </body>
</html>

访问:

但另两个访问不到。

 为何html等访问不到?

 当访问html,css,js等文件时,这些请求也进入DispatcherServlet,解析uri,查找HandlerMethod方法.找不到则404.

jsp为何可以?

访问jsp文件,在tomcat下有默认的JspServlet来处理jsp请求

Tomcat:
DefaultServlet此servlet映射路径/  没有springmvc环境时,访问tomcat下的静态资源都是default来处理.有了springmvc的DisptcherServlet以后,覆盖了defaultServlet

JspServlet此servlet映射路径 *.jsp  

解决方案:

在web.xml

 法二:(建议使用)

在springmvc.xml

 

 法三:

在springmvc.xml

相应的

 一般写成和文件夹一样

3.异常处理(重要)

异常处理的目的1:服务端的异常的内存级别的错误信息,不返回给前端,正常返回前端错误码,文字信息.

异常处理的目的2:上线运行后,需要记录程序运行过程中产生的exception异常堆栈数据.

自定义的切面,写异常通知方法.织入到所有的控制层对象中.当控制层抛出异常后,进入异常通知方法进行处理.

在springmvc中已经有封装好的异常通知.

1.局部异常处理

缺点:只对某一个处理器类中的方法产生的异常生效.

优点:能够对不同的异常转到不同的方法进行处理.

@RestController 
@RequestMapping("user")
public class UserHandler {
   
    
    @GetMapping("{userid}")
    public AxiosResult getById(@PathVariable("userid") Integer id){
        int a =1/0;
        SysUser u =new SysUser();
        u.setId(id);
        u.setUname("aaa");
        return AxiosResult.suc(u);
    }

    @GetMapping("list")
    public AxiosResult userlist(SysUser u,Integer pageNum,Integer pageSize) throws FileNotFoundException {
        FileInputStream fileInputStream = new FileInputStream("D:/aaa.txt");

        if(pageNum==null)pageNum=1;
        if(pageSize==null)pageSize=10;

        List<SysUser> users = new ArrayList<>();
        users.add(new SysUser("阿萨德","123123","fythnu"));
        users.add(new SysUser("德克士","222222","dks"));
        return AxiosResult.suc(users);
    }

    @GetMapping("uname/{uname}/{uwechat}")
    public AxiosResult getByUname(@PathVariable String uname,@PathVariable String uwechat){
        String str = null;
        System.out.println(str.substring(1));
        SysUser u =new SysUser();
        u.setId(1);
        u.setUname(uname);
        return AxiosResult.suc(u);
    }

    @ExceptionHandler(ArithmeticException.class)
    public AxiosResult doArithmeticException(ArithmeticException e){
        System.out.println(e.getMessage());
        return AxiosResult.error(e.getMessage());
    }


    @ExceptionHandler(FileNotFoundException.class)
    public AxiosResult doFileNotFoundException(FileNotFoundException e){
        System.out.println(e.getMessage());
        return AxiosResult.error(e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public AxiosResult doException(Exception e){
        System.out.println(e.getMessage());
        return AxiosResult.error(e.getMessage());
    }

    
}

   

最后一个是空指针异常,进入到Exception.

2.全局异常处理

优点:对所有处理器类中的方法异常进行处理.

缺点:无法对不同异常分开处理.

新建MvcExceptionHandler类

@Component
public class MvcExceptionHandler implements HandlerExceptionResolver {  //处理异常处理器
    /**
     *
     * @param req :请求对象
     * @param resp :响应对象
     * @param o : HandlerMethod处理器方法对象
     * @param e :异常对象
     * @return 转错误视图,用在同步开发模式下
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest req, HttpServletResponse resp, Object o, Exception e) {
        if(o instanceof HandlerMethod){           //如果o是HandlerMethod类型
            HandlerMethod m = (HandlerMethod)o;    //强转
            Object bean = m.getBean();             //是哪个类里的
            Method method = m.getMethod();         //是哪个方法
            //这个bean里的这个method方法产生异常
        }

        //如果需要对不同的异常返回给客户端不同的错误信息的话,需要重复的if-elseif判断,无上限的elseif.(也可原生方式处理异步)
        AxiosResult a  =  AxiosResult.error();
        PrintWriter writer = null;
        try {
            writer = resp.getWriter();
            writer.print(JSON.toJSONString(a));
            writer.close();
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }

        return null;
    }
}

3.全局统一异常处理(重要)----用这个

//@ControllerAdvice    控制器增强注解,当控制层出现异常时,进入ControllerAdvice注解的对象
//@ResponseBody
@RestControllerAdvice   //前两个注解的复合
public class GlobExceptionHandler {
    @ExceptionHandler(ArithmeticException.class)
    public AxiosResult doArithmeticException(ArithmeticException e){
        System.out.println(e.getMessage());
        return AxiosResult.error(E.NOT_EXISTS,e.getMessage());
    }

    @ExceptionHandler(FileNotFoundException.class)
    public AxiosResult doFileNotFoundException(FileNotFoundException e){
        System.out.println(e.getMessage());
        return AxiosResult.error(e.getMessage());
    }

    @ExceptionHandler(MvcException.class)
    public AxiosResult doMvcException(MvcException e){
        return AxiosResult.error(e.getE());
    }

    @ExceptionHandler(MultipartException.class)
    public AxiosResult doMultipartException(MultipartException e){
        return AxiosResult.error(E.FILE_MAX_SIZE);
    }

    @ExceptionHandler(Exception.class)
    public AxiosResult doException(Exception e){
        System.out.println(e.getMessage());
        return AxiosResult.error(e.getMessage());
    }
}

(自定义异常也在这里处理)

使用@RestControllerAdvice来对控制层对象进行统一异常处理,所有的异常类型都在此注解对应的类中进行处理,添加不同的方法,使用@ExceptionHandler注解来限定每个方法处理器的异常类型.

新建IUserService:

public interface IUserService {
    SysUser getByUname(String uname);
}

新建:

@Service
public class UserServiceImpl implements IUserService {
    @Override
    public SysUser getByUname(String uname) {
        if("admin".equals(uname)){
            return new SysUser(uname,"111111","adminwechat","root");
        }
        return null;
    }
}

在UserHandler类中增加用户名密码登录

    @Autowired
    private IUserService us;

    @PostMapping("login")
    public AxiosResult doLogin(@RequestBody SysUser u, HttpSession session) {
        //用户名密码登录
        String uname = u.getUname();
        //1.根据用户名去查找用户对象.
        SysUser byUname = us.getByUname(uname);
        if(byUname==null)
            throw new MvcException(E.NOT_EXISTS);
         //return AxiosResult.error(E.NOT_EXISTS);
        //2.比较密码
        if(!u.getUpwd().equals(byUname.getUpwd()))
            throw new MvcException(E.PWD_ERROR);
        session.setAttribute("LOGIN_USER",byUname);
        return AxiosResult.suc();
    }

新建:

public class MvcException extends RuntimeException {
    private E e;

    public MvcException(E e) {
        this.e = e;
    }

    public E getE() {
        return e;
    }

    public void setE(E e) {
        this.e = e;
    }
}

(通过自定义异常来接收业务异常信息.)

总结:

@RestControllerAdvice+@ExceptionHandler+自定义RuntimeException+异常枚举对象

4.文件上传下载

springmvc对commons-fileupload组件进行了二次封装.使用更加简单.

添加commons-fileupload;commons-io包;

配置CommonsMultipartResolver对象到springmvc容器对象.此对象检测req请求是否content-type=multipart/form-data;内部要解析req对象,得到文件数据,把文件数据封装MuipartFile对象

在spring.mvc

<!--id必须是multipartResolver,因为springmvc内部从容器中获取文件解析器对象是按照固定id获取.-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="defaultEncoding" value="UTF-8"></property><!--支持中文文件名-->
        <property name="maxInMemorySize" value="3000000"></property><!--临时文件域-->
        <property name="maxUploadSize" value="10000000"></property><!--上传文件最大大小限制,如果不指定则不限制大小-->
        <property name="uploadTempDir" value="/upload/tmp"></property><!--临时文件存储目录-->
    </bean>

新建

 新建文件控制层:

@RestController
public class FileHandler {
    @Value("${mvc.root}")
    private String root;

    //Map<String,MultipartFile>  :key:文件表单参数名, value:文件对象(文件名,大小,文件流)
    @PostMapping("upload")
    public AxiosResult doUpload(MultipartFile myfile,MultipartFile jl) throws IOException {
        String originalFilename = myfile.getOriginalFilename();
        long size = myfile.getSize();
        //文件内容字节数组,小文件使用getBytes方法.一次性把InputStream流的数据读取到bytes数组
//        byte[] bytes = myfile.getBytes();
//        IOUtils.write(bytes,out);
        //大文件使用流来慢慢的读
        InputStream in = myfile.getInputStream();//文件内容,文件流
        //把文件存到指定的磁盘目录

        SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");
        String time=df.format(new Date());

        File file = new File(root+"/"+time);
        if(!file.exists())file.mkdirs();

        String filepath = time+"/"+originalFilename;
        File target = new File(root,filepath);
        FileOutputStream out = new FileOutputStream(target);
        BufferedOutputStream bout = new BufferedOutputStream(out);
        byte[] bytes= new byte[2048];
        int r;
        while((r=in.read(bytes))!=-1){
            bout.write(bytes,0,r);
        }
        bout.flush();
        bout.close();

        //返回客户端什么数据?
        Map<String,Object> map  = new HashMap<>();
        map.put("real_name",originalFilename);
        map.put("file_size",size);
        map.put("file_path",filepath);
        map.put("upload_time",System.currentTimeMillis());
        return AxiosResult.suc(map);
    }
}

(可传多个文件,在形参加即可,比如第二个文件是简历)

 文件下载:

main.xml中:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/css/main.css">
</head>
<body>
    main.html
    <a href="/download?filePath=20220609/cpu模型.png&realName=cpu模型.png">下载</a>
</body>
</html>

@RestController
public class FileHandler {
    ...

    //文件下载,返回值类型必须是ResponseEntity,因为文件下载必须指定content_disposition响应头
    @GetMapping("download")
    public ResponseEntity doDownload(String filePath,String realName) throws Exception {
        File f = new File(root,filePath);
        if(!f.exists())
            throw new MvcException(E.NOT_EXISTS);

        byte[] bytes = IOUtils.toByteArray(new FileInputStream(f));
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition","attachment;filename="+ URLEncoder.encode(realName, "utf-8"));
        return new ResponseEntity(bytes,headers, HttpStatus.OK);
    }
}

(想要下载文件时有进度条的效果,要设置响应头,响应头里的设置是百度出来的。

Content-Disposition通知浏览器,对发给他的响应体里的数据怎么处理。若不指定,则浏览器默认把数据展现在页面。attachment,通知浏览器对内容以附件的形式存储。filename,附件的名字指定一下。文件名应指定编码格式,防止乱码)

5.拦截器

interceptor拦截器,是控制层框架中的一个概念,类似于原生servlet中的filter过滤器的作用.

登录拦截器:

@Component
public class LoginInterceptor implements HandlerInterceptor {   //HandlerInterceptor 处理器拦截器
    //前拦截
    @Override        //一般啊只用该方法,不重写后两个方法
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        Object login_user = session.getAttribute("LOGIN_USER");
        if(login_user==null) throw new MvcException(E.NO_LOGIN);
        return true;
    }

    //返回
//    @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");
//    }
}

springmvc.xml配置拦截器

<!--配置拦截器-->
    <mvc:interceptors>
        <mvc:interceptor>
            <!--拦截url-->
            <mvc:mapping path="/**"/>
            <!--忽略url-->
            <mvc:exclude-mapping path="/user/login"/>
            <mvc:exclude-mapping path="/static/**"/>
            <mvc:exclude-mapping path="/pages/**"/>
            <ref bean="loginInterceptor"></ref>
        </mvc:interceptor>
    </mvc:interceptors>

用户发送请求,HandlerMapping解析url,查找该url对应的拦截器栈(集合)。即判断url在不在拦截范围或者忽略范围。

 进入该拦截器,执行前拦截

前拦截返回true,再去执行对应的登录

拦截器可以配置多个

执行都是在dispatcherServlet类里:

public class UserHandler {
    @Autowired
    private IUserService us;

    @PostMapping("login")
    public AxiosResult doLogin(@RequestBody SysUser u, HttpSession session) {
        //用户名密码登录
        String uname = u.getUname();
        //1.根据用户名去查找用户对象.
        SysUser byUname = us.getByUname(uname);
        if(byUname==null)
            throw new MvcException(E.NOT_EXISTS);
         //return AxiosResult.error(E.NOT_EXISTS);
        //2.比较密码
        if(!u.getUpwd().equals(byUname.getUpwd()))
            throw new MvcException(E.PWD_ERROR);
        session.setAttribute("LOGIN_USER",byUname);
        return AxiosResult.suc();
    }

  

    @PostMapping
    public AxiosResult addUser(@RequestBody SysUser u,HttpSession session){
        Object login_user = session.getAttribute("LOGIN_USER");
        System.out.println(login_user);
        System.out.println("addUser:"+u);
        return AxiosResult.suc();
    }

}
@Component
public class LoginInterceptor implements HandlerInterceptor {   //HandlerInterceptor 处理器拦截器
    //前拦截
    @Override        //一般只用该方法,不重写后两个方法
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HttpSession session = request.getSession();
        Object login_user = session.getAttribute("LOGIN_USER");
        if(login_user==null) throw new MvcException(E.NO_LOGIN);
        return true;
    }

6.跨域处理

什么情况会造成跨域?

当我们前端独立部署服务器的时,有自己的ip,port,在前端项目访问页面视图,页面视图中发起向服务端的请求,服务端也是独立部署,有自己的ip,port,服务端的ip与port与前端服务器的ip,port必定有不同.

浏览器不允许在页面中发起与当前路径不同的请求(协议,ip,port).如果发起源地址不同的请求,就会造成浏览器跨域异常.

解决跨域异常的方案:

服务端在收到客户端请求时,首先检测客户端ip,port,method,请求头是否在服务端配置白名单中.如果都允许,就处理请求,并在响应头放入跨域相关响应头.

springmvc解决方案:

1.在处理器类上添加CrossOrigin注解,没用(只对这一个处理器类有效)

@CrossOrigin(origins = "http://localhost:8088",methods = {RequestMethod.GET,RequestMethod.POST},allowedHeaders = "*")

(只有本地8088端口下的get和post请求能进该方法。允许的请求头,*表示全部支持)

2.在springmvc.xml配置,全局配置跨域,没用

(springmvc进行跨域是基于跨域拦截器来执行的,而且此拦截器是最后一个执行.与自定义拦截器一起使用时,有问题)

  <mvc:cors>
        <mvc:mapping path="/**"
                     allowed-origins="http://localhost:8088"
                     allowed-headers="*"
                     allowed-methods="*"
                     allow-credentials="true"
        />
    </mvc:cors>

(mvc:mapping path="/**"   所有的url都进行跨域

allowed-origins 白名单客户端     allow-credential 允许客户端请求携带cookie)

cookie客户端存储信息的地方

3.基于过滤器进行跨域配置

(过滤器在整个servlet走之前走的,请求先进过滤器,再进DispatcherServlet)

第一步:向容器中注册CorsFilter对象

在springmvc.xml中:

<bean id="corsFilter" class="org.springframework.web.filter.CorsFilter">
        <constructor-arg name="configSource">
            <bean class="org.springframework.web.cors.UrlBasedCorsConfigurationSource">
                <property name="corsConfigurations">
                    <map>
                        <entry key="/**">
                            <bean class="org.springframework.web.cors.CorsConfiguration">
                                <property name="allowCredentials" value="true"/>
                                <property name="allowedMethods">
                                    <list>
                                        <value>GET</value>
                                        <value>POST</value>
                                        <value>PUT</value>
                                        <value>DELETE</value>
                                        <!--预检请求-->
                                        <value>OPTIONS</value>
                                    </list>
                                </property>
                                <property name="allowedHeaders" value="*"/>
                                <property name="allowedOrigins" value="http://localhost:8088"/>
                            </bean>
                        </entry>
                    </map>
                </property>
            </bean>
        </constructor-arg>
    </bean>

(预检请求:当前后端分离时,浏览器请求分为简单请求与复杂请求. )

简单请求:

Get请求

content-type为urlEncoded,multipart/form-data的post请求

复杂请求:

post请求,contnet-type:applicaitn/json

put

delete请求

浏览器对复杂请求,先发送一次OPTIONS预检请求,此次预检请求不带请求参数,不带cookie;如果预检请求返回200,才发起第二次真正的请求.

第二步:在web.xml配置过滤器代理对象  

<filter>
        <filter-name>delegatingFilterProxy</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>corsFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>delegatingFilterProxy</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值