springmvc

SpringMVC

回顾servlet
Servlet 处理请求和响应
JSP 可以嵌套到html中,最终转换成servlet
Filter 过滤URL进行拦截
Listener 监听事件 例如:ServletContext、HttpSession的创建和销毁

如何使用SpringMVC

1. 依赖
org.springframework:spring-context:5.2.0.RELEASE
org.springframework:spring-webmvc:5.2.0.RELEASE
org.springframework:spring-jdbc:5.2.0.RELEASE
javax.annotation:javax.annotation-api:1.3.2
io.pebbletemplates:pebble-spring5:3.1.2
ch.qos.logback:logback-core:1.2.3
ch.qos.logback:logback-classic:1.2.3
com.zaxxer:HikariCP:3.4.2
org.hsqldb:hsqldb:2.5.0
以及provided依赖:
org.apache.tomcat.embed:tomcat-embed-core:9.0.26
org.apache.tomcat.embed:tomcat-embed-jasper:9.0.26

2. 目录
spring-web-mvc
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── itranswarp
        │           └── learnjava
        │               ├── AppConfig.java
        │               ├── DatabaseInitializer.java
        │               ├── entity
        │               │   └── User.java
        │               ├── service
        │               │   └── UserService.java
        │               └── web
        │                   └── UserController.java
        ├── resources
        │   ├── jdbc.properties
        │   └── logback.xml
        └── webapp
            ├── WEB-INF
            │   ├── templates
            │   │   ├── _base.html
            │   │   ├── index.html
            │   │   ├── profile.html
            │   │   ├── register.html
            │   │   └── signin.html
            │   └── web.xml
            └── static
                ├── css
                │   └── bootstrap.css
                └── js
                    └── jquery.js

3. 
src/main/webapp是标准web目录,WEB-INF存放web.xml,编译的class,第三方jar,
以及不允许浏览器直接访问的View模版,static目录存放所有静态文件
4. 编写配置
@Configuration
@ComponentScan
@EnableWebMvc // 启用Spring MVC
@EnableTransactionManagement
@PropertySource("classpath:/jdbc.properties")
public class AppConfig {
    ...
    @Bean
    WebMvcConfigurer createWebMvcConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addResourceHandlers(ResourceHandlerRegistry registry) {
                registry.addResourceHandler("/static/**").addResourceLocations("/static/");
            }
        };
    }
    // 配置魔板引擎
    @Bean
    ViewResolver createViewResolver(@Autowired ServletContext servletContext) {
        PebbleEngine engine = new PebbleEngine.Builder().autoEscaping(true)
                .cacheActive(false)
                .loader(new ServletLoader(servletContext))
                .extension(new SpringExtension())
                .build();
        PebbleViewResolver viewResolver = new PebbleViewResolver();
        viewResolver.setPrefix("/WEB-INF/templates/");
        viewResolver.setSuffix("");
        viewResolver.setPebbleEngine(engine);
        return viewResolver;
    }
}

如何启动web应用程序
通过Listener启动,
通过Servlet启动,
使用XML配置,
使用注解配置


第一步,我们在web.xml中配置Spring MVC提供的DispatcherServlet:

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.itranswarp.learnjava.AppConfig</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

初始化参数contextClass指定使用注解配置的AnnotationConfigWebApplicationContext,
配置文件的位置参数contextConfigLocation指向AppConfig的完整类名,最后,把这个Servlet映射到/*,即处理所有URL。

上述配置可以看作一个样板配置,有了这个配置,
Servlet容器会首先初始化Spring MVC的DispatcherServlet,
在DispatcherServlet启动时,
它根据配置AppConfig创建了一个类型是WebApplicationContext的IoC容器,
完成所有Bean的初始化,并将容器绑到ServletContext上。

因为DispatcherServlet持有IoC容器,
能从IoC容器中获取所有@Controller的Bean,
因此,DispatcherServlet接收到所有HTTP请求后,
根据Controller方法配置的路径,
就可以正确地把请求转发到指定方法,并根据返回的ModelAndView决定如何渲染页面。

最后,我们在AppConfig中通过main()方法启动嵌入式Tomcat:

public static void main(String[] args) throws Exception {
    Tomcat tomcat = new Tomcat();
    tomcat.setPort(Integer.getInteger("port", 8080));
    tomcat.getConnector();
    Context ctx = tomcat.addWebapp("", new File("src/main/webapp").getAbsolutePath());
    WebResourceRoot resources = new StandardRoot(ctx);
    resources.addPreResources(
            new DirResourceSet(resources, "/WEB-INF/classes", new File("target/classes").getAbsolutePath(), "/"));
    ctx.setResources(resources);
    tomcat.start();
    tomcat.getServer().await();
}
上述Web应用程序就是我们使用Spring MVC时的一个最小启动功能集。由于使用了JDBC和数据库,用户的注册、登录信息会被持久化

使用Spring MVC时,整个Web应用程序按如下顺序启动:

启动Tomcat服务器;
Tomcat读取web.xml并初始化DispatcherServlet;
DispatcherServlet创建IoC容器并自动注册到ServletContext中。
启动后,浏览器发出的HTTP请求全部由DispatcherServlet接收,并根据配置转发到指定Controller的指定方法处理。

使用Spring写Rest接口

rest就是接受json 返回json的接口
1. 所以必须引入json的操作
对应的Maven工程需要加入Jackson这个依赖:com.fasterxml.jackson.core:jackson-databind:2.11.0
2. controller
@PostMapping(value = "/rest",
             consumes = "application/json;charset=UTF-8",
             produces = "application/json;charset=UTF-8")
@ResponseBody
public String rest(@RequestBody User user) {
    return "{\"restSupport\":true}";
}
这个@RequestBody User user
输入的JSON则根据注解@RequestBody直接被Spring反序列化为User这个JavaBean。
这个是把json进行反序列化  这个是spring把传入的json进行反序列化

简写
@RestController
@RequestMapping("/api")
public class ApiController {
    @Autowired
    UserService userService;

    @GetMapping("/users")
    public List<User> users() {
        return userService.getUsers();
    }

    @GetMapping("/users/{id}")
    public User user(@PathVariable("id") long id) {
        return userService.getUserById(id);
    }

    @PostMapping("/signin")
    public Map<String, Object> signin(@RequestBody SignInRequest signinRequest) {
        try {
            User user = userService.signin(signinRequest.email, signinRequest.password);
            return Map.of("user", user);
        } catch (Exception e) {
            return Map.of("error", "SIGNIN_FAILED", "message", e.getMessage());
        }
    }

    public static class SignInRequest {
        public String email;
        public String password;
    }
}

每个方法都是一个API接口,输入和输出只要能被Jackson序列化或反序列化为JSON就没有问题

jackson
{"user":{"id":1,"email":"bob@example.com","password":"bob123","name":"Bob",...
像这样的用户 我们肯定是想让password能保存 不让password显示出去返回
这时候就要在对象的字段passwor上加上jackson的注解
public class User {
    ...

    @JsonProperty(access = Access.WRITE_ONLY)
    public String getPassword() {
        return password;
    }

    ...
}
同样的,可以使用@JsonProperty(access = Access.READ_ONLY)允许输出
 @JsonIgnore 输入 输出都忽略

Filter 这是servlet中的一个概念
SpringMVC中如何使用呢

Servlet默认不是以UTF-8 编码的 所以需要
把编码设置一下
<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>
以上的设置比较简单 
注意到CharacterEncodingFilter其实和Spring的IoC容器没有任何关系,两者均互不知晓对方的存在,因此,配置这种Filter十分简单

如果我们写的一个类里边用到了IOC里边的bean怎么办呢?
这个配置文件可是直接给Servelet加载的 访问不到Spring的IOC
这也没关系,Spring提供了一个代理DelegatingFilterProxy
所以配置就要这样写了

<web-app>
    <filter>
        <filter-name>aaaFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <!-- 指定Bean的名字 --> 如果这个名字authFilter 跟 aaaFilter 一样就不需要再进行配置了
    <init-param>
        <param-name>targetBeanName</param-name>
        <param-value>authFilter</param-value>
    </init-param>

    <filter-mapping>
        <filter-name>aaaFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

这样我们自己写的一个类就可以用了 这个类里可以用Spring的组件了
这个原理是什么呢?

Servlet容器从web.xml中读取配置,
实例化DelegatingFilterProxy,注意命名是authFilter;
Spring容器通过扫描@Component实例化AuthFilter。
当DelegatingFilterProxy生效后,
它会自动查找注册在ServletContext上的Spring容器,
再试图从容器中查找名为authFilter的Bean,
也就是我们用@Component声明的AuthFilter。

DelegatingFilterProxy将请求代理给AuthFilter,

一句通俗的话就是 我在Servlet里写的类如果要想跟Spring扯上关系就得用DelegatingFilterProxy进行代理

@Component
public class AuthFilter implements Filter {
    @Autowired
    UserService userService;

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        // 获取Authorization头:
        String authHeader = req.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Basic ")) {
            // 从Header中提取email和password:
            String email = prefixFrom(authHeader);
            String password = suffixFrom(authHeader);
            // 登录:
            User user = userService.signin(email, password);
            // 放入Session:
            req.getSession().setAttribute(UserController.KEY_USER, user);
        }
        // 继续处理请求:
        chain.doFilter(request, response);
    }
}

         │   ▲
         ▼   │
       ┌───────┐
       │Filter1│
       └───────┘
         │   ▲
         ▼   │
       ┌───────┐
┌ ─ ─ ─│Filter2│─ ─ ─ ─ ─ ─ ─ ─ ┐
       └───────┘
│        │   ▲                  │
         ▼   │
│ ┌─────────────────┐           │
  │DispatcherServlet│<───┐
│ └─────────────────┘    │      │
   │              ┌────────────┐
│  │              │ModelAndView││
   │              └────────────┘
│  │                     ▲      │
   │    ┌───────────┐    │
│  ├───>│Controller1│────┤      │
   │    └───────────┘    │
│  │                     │      │
   │    ┌───────────┐    │
│  └───>│Controller2│────┘      │
        └───────────┘
└ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘

虚线框是Filter的拦截范围, 可见filter对底层业务没有了解。把所有的都过滤掉了
Filter组件实际上并不知道后续内部处理是通过Spring MVC提供的DispatcherServlet还是其他Servlet组件,
因为Filter是Servlet规范定义的标准组件,它可以应用在任何基于Servlet的程序中

如果要想对具体的业务进行过滤该怎么办呢?使用Interceptor

      │   ▲
       ▼   │
     ┌───────┐
     │Filter1│
     └───────┘
       │   ▲
       ▼   │
     ┌───────┐
     │Filter2│
     └───────┘
       │   ▲
       ▼   │
┌─────────────────┐
│DispatcherServlet│<───┐
└─────────────────┘    │
 │              ┌────────────┐
 │              │ModelAndView│
 │              └────────────┘
 │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ▲
 │    ┌───────────┐    │
 ├─┼─>│Controller1│──┼─┤
 │    └───────────┘    │
 │ │                 │ │
 │    ┌───────────┐    │
 └─┼─>│Controller2│──┼─┘
      └───────────┘
   └ ─ ─ ─ ─ ─ ─ ─ ─ ┘

这个虚线是对Controller进行的拦截

CROS

rest开发 前后端分离 前端的同源策略会引发跨域问题
解决办法:
1.使用@CrossOrigin
//@CrossOrigin(origins = {"http://a.com", "https://www.b.com"})
@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api")
public class ApiController {
    ...
}

2. 使用CorsRegistry
@Bean
WebMvcConfigurer createWebMvcConfigurer() {
    return new WebMvcConfigurer() {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/api/**")
                    .allowedOrigins("http://local.liaoxuefeng.com:8080")
                    .allowedMethods("GET", "POST")
                    .maxAge(3600);
            // 可以继续添加其他URL规则:
            // registry.addMapping("/rest/v2/**")...
        }
    };
}

3. 使用CorsFilter
第三种方法是使用Spring提供的CorsFilter,
我们在集成Filter中详细介绍了将Spring容器内置的Bean暴露为Servlet容器的Filter的方法,
由于这种配置方式需要修改web.xml,也比较繁琐,所以推荐使用第二种方式。

异步处理
这块是理解实现高并发的基础

Servlet模型中,每一个用户每一个请求过来都会由后台启动的一个线程处理,处理之后,反会给用户.整个过程都是由同一个线程完成的

这样做的优点是:事务处理 肯定得是一个线程的
这样做的缺点是: 如果一个进程的耗时特别长,那么这个进程就不能及时得到释放,再有用户请求过来,就会起动新的线程。线程数达到一定数目服务器就挂了
优化就是在不改变优点的前提下改善缺点:

如果把长时间处理的请求改为异步处理,
那么线程池的利用率就会大大提高。
Servlet从3.0规范开始添加了异步支持,
允许对一个请求进行异步处理

Servlet是如何支持线程的
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    <display-name>Archetype Created Web Application</display-name>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
        </init-param>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.itranswarp.learnjava.AppConfig</param-value>
        </init-param>
        <load-on-startup>0</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>

    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

实现异步有三种方法:返回一个Callable  返回一个DeferredResult对象  使用Filter
推荐使用DeferredResult 因为操作比较灵活

Servlet 3.0规范添加的异步支持是针对同步模型打了一个“补丁”,
虽然可以异步处理请求,
但高并发异步请求时,
它的处理效率并不高,
因为这种异步模型并没有用到真正的“原生”异步。
Java标准库提供了封装操作系统的异步IO包java.nio,
是真正的多路复用IO模型,
可以用少量线程支持大量并发。
使用NIO编程复杂度比同步IO高很多,
因此我们很少直接使用NIO。

实现长连接
websocket

第一步
首先,我们需要在pom.xml中加入以下依赖:
org.apache.tomcat.embed:tomcat-embed-websocket:9.0.26
org.springframework:spring-websocket:5.2.0.RELEASE
第二步
在config配置里增加bean
@Bean
WebSocketConfigurer createWebSocketConfigurer(
        @Autowired ChatHandler chatHandler,
        @Autowired ChatHandshakeInterceptor chatInterceptor)
{
    return new WebSocketConfigurer() {
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            // 把URL与指定的WebSocketHandler关联,可关联多个:
            registry.addHandler(chatHandler, "/chat").addInterceptors(chatInterceptor);
        }
    };
}
第三步骤
Spring提供了TextWebSocketHandler和BinaryWebSocketHandler分别处理文本消息和二进制消息
@Component
public class ChatHandler extends TextWebSocketHandler {
    // 保存所有Client的WebSocket会话实例:
    private Map<String, WebSocketSession> clients = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 新会话根据ID放入Map:
        clients.put(session.getId(), session);
        session.getAttributes().put("name", "Guest1");
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        clients.remove(session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String s = message.getPayload();
        String r = ... // 根据输入消息构造待发送消息
        broadcastMessage(r); // 推送给所有用户
    }
}
推送给指定用户
@Component
public class ChatHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
    public ChatHandshakeInterceptor() {
        // 指定从HttpSession复制属性到WebSocketSession:
        super(List.of(UserController.KEY_USER));
    }
}

在Servlet中使用WebSocket需要3.1及以上版本;
通过spring-websocket可以简化WebSocket的开发
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰明子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值