凌霄的博客
springmvc入门
springmvc入门

最近开始学习后端相关的知识了,虽然之前做Android开发也是用的java语言,但是移动端和后端的很多理念根本不一样,应该算是重新学习吧。

一. 什么是spring

学习后端绕不开的就是spring了。那什么是spring呢,字面意思就是春天,程序猿的春天嘛。从技术的角度上来讲,使用框架的目的无非就是解耦、简化开发,提升工作效率。而spring确实做到了这些。

spring作为开源的轻量级框架,核心主要分为两个部分:

  1. IOC,控制反转,依赖注入
  2. aop:面向切面编程

下面依次分析这两部分的作用

1. IOC容器

spring被描述得最多的是作为IOC容器,我们用得最多的也是它的IOC,那什么是IOC呢?遇事不决百度一下,百度百科是这样说的:

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

不知道你们晕不晕,反正这一堆专业名字已经把作为初学者的我搞晕了,说人话,其实它的意思就是IOC是一种设计原则,而依赖注入是实现它的方法。有没有一点点恍然大悟的感觉?可以将IOC比作一个接口,而依赖注入是实现了这个接口的类。

那IOC控制反转到底是什么呢?

学习java的时候,我们一直写这样的代码:

class B{}
class A {
    B b;
    public A() {
        b = new B();
    }
}

用一张图来表示上面的关系(图片来自刘望舒的Android进阶之光):

https://blog-1252348761.cos.ap-chengdu.myqcloud.com/spring/ioc1.png

可以看到对象ABCD相互依赖,A初始化B对象的时候,无论是创建B还是使用B,控制权都在A的手上。

而使用了IOC容器之后,就是下面这个样子的:

https://blog-1252348761.cos.ap-chengdu.myqcloud.com/spring/ioc2.png

可以看到,对象的创建销毁由IOC容器来控制,如果A需要对象B,直接从IOC容器中获取。

看出什么门道了吗?现在创建B的控制权不在A的手上了,而是由IOC来控制,A获得B对象的过程,由主动变为了被动,这就是控制反转的由来。

那什么是依赖注入呢?

还是看上面的那段代码,在A中使用了B,可以叫做A依赖于B,也就是说,B是A的依赖,A是需求者,需要B,我们通过注射器(spring)将依赖注射到A中,这个就叫做依赖注入。

class B{}
class A {
    @Autowire
    B b;
    public A() {
        //b = new B();
        b.dosomething();
    }
}

有什么好处?试想一下,如果B的构造方法频繁变动,那么也会引起A的变动,这里只是一个类,如果将来是十个、一百个类依赖于B呢。

2. AOP面向切面编程

概念

所谓的面向切面编程。通俗地讲,它一般被用来解决一些系统交叉业务的织入,比如记录日志、埋点,事务处理之类的。举个栗子,service1的method1方法需要打印日志,servce2的method2方法也需要打印日志,service3的method3方法也需要打印日志,按照平常的编程思想,我们很容易把代码写成这样:

public void method1(){
    打印日志
    //...code
    打印日志
}

public void method2(){
    打印日志
    //...code
    打印日志
}

public void method3(){
    打印日志
    //...code
    打印日志
}

这种直接硬编码的方式相信大家都觉得不太好叭,那看看aop是怎么处理的呢:

---------------------方法调用之前打印日志
public void method(){
    //...code
}
---------------------方法调用之后打印日志

AOP把交叉业务写在方法调用前后,也就是说,我们在方法调用的前后,可以动态的插入自己的代码。

如果说 OOP 是把问题划分到单个模块的话,那么 AOP 就是把涉及到众多模块的某一类问题进行统一管理。AOP的目标是把这些功能集中起来,放到一个统一的地方来控制和管理。

如何实现AOP

JDK中有InvocationHandler 接口和Proxy类,来帮助我们轻松的实现动态代理。

InvocationHandler是一个接口,通过实现这个接口定义一个横切的代码。然后通过反射机制调用目标类的方法,这样就能动态的把非业务代码和业务代码动态的拼接在一起。

proxy则利用InvocationHandler创建代理实例,来间接的调用代理的方法。

public class TestInvocation implements InvocationHandler {

    private Object target;
    public TestInvocation(Object target){
        this.target = target;
    }
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //定义横切代码,在方法执行前后调用
        System.out.println("before do some thing");
        Object invoke = method.invoke(target, args);
        System.out.println("after do some thing");
        return invoke;
    }


    public static void main(String[] args) {
        TestService testService = new TestServiceImpl();
        TestInvocation testInvocation = new TestInvocation(testService);
        //创建一个代理,通过这个代理对象调用方法
        TestService proxy = (TestService)Proxy
                .newProxyInstance(testService.getClass().getClassLoader(),
                        testService.getClass().getInterfaces(), testInvocation);
        proxy.doSomeThing();
    }
}
interface TestService{
    void doSomeThing();
}
class TestServiceImpl implements TestService{
    public void doSomeThing() {
        System.out.println("do some thing");
    }
}

上面是通过JDK动态代理实现的,可以看到它是基于接口实现的,也就是说要使用这种方式实现AOP,必须要用到接口,要是没有接口,能不能实现呢?答案是肯定的,通过CGLib可以实现,它是基于字节码技术,为代理类创建了一个子类来代理它,并在子类中采用方法拦截的技术拦截所有父类方法的调用,所以不能代理被 final 字段修饰的方法。

使用它需要引入额外的jar:

<dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib-nodep</artifactId>
     <version>3.2.5</version>
</dependency>

使用:

public class TestCgLib implements MethodInterceptor {

    public Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("before do some thing");
        Object invoke = methodProxy.invokeSuper(target, objects);
        System.out.println("after do some thing");
        return invoke;
    }

    public static void main(String[] args) {
        TestCgLib testCgLib = new TestCgLib();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TestCgLibService.class);
        enhancer.setCallback(testCgLib);
        TestCgLibService proxy = (TestCgLibService) enhancer.create();
        proxy.doSomeThing();
    }
}
class TestCgLibService {
    public void doSomeThing() {
        System.out.println("do some thing");
    }
}

二. 什么是springmvc

参考链接:springmvc工作原理

mvc的概念相信已经深入人心了,所以这里也就不再讲mvc相关的东西了,直接从springmvc开始!

springMVC是一个MVC的开源框架,可以将它理解为是一个spring的模块,即使用springmvc不需要同spring整合,它们是无缝集成的。

1. 原理浅析

SpringMvc是一种基于Servlet的MVC框架,工作流程如下:

https://pic3.zhimg.com/v2-fcb7afad7d2d7d9eb946c1902e66a412_b.jpg

如图所示:

  1. 客户端发起http请求,服务器通过web.xml找到DispatchServlet;
  2. DispatchServlet通过配置文件查找到一个或者多个HandlerMapping,找到用于处理请求的controller
  3. controller调用service处理业务逻辑
  4. 处理完成之后,返回ModelAndView给DispathServlet
  5. DispatchServlet寻找到一个或者多个ViewResolver(视图解析器),通过前缀+名字+后缀找到指定的视图
  6. DispatchServlet将结果返回给视图(jsp),封装response

整个流程非常的清晰

2.简单使用

看看它是怎么使用的,首先在maven中引入依赖:

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>5.1.4.RELEASE</version>
</dependency>

然后在web.xml中配置DispatcherServlet

<servlet>
    <servlet-name>dispatchServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <!-- 这样配置spring的xml文件可以不用放在/WEB-INF/文件夹下,名字也不需要用applicationContext-->
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-web.xml</param-value>
    </init-param>
    <!--表示容器在启动的时候就加载这个类-->
    <load-on-startup>1</load-on-startup>
</servlet>
<!-- 所有请求都走这个dispatchservlet -->
<servlet-mapping>
   <servlet-name>dispatchServlet</servlet-name>
   <url-pattern>/</url-pattern>
</servlet-mapping>

就如同这个类的字面意思一样,这样配置之后,所有的请求都通过这个servlet进行分发。

spring-web.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">
    <context:component-scan base-package="com.lingxiao"/>
    <!--注解驱动,以使得访问路径与方法的匹配可以通过注解配置-->
    <mvc:annotation-driven />
    <!--静态页面,如html,css,js,images可以访问-->
    <mvc:default-servlet-handler />
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <!-- 视图前缀 -->
        <property name="prefix" value="/WEB-INF/pages/"/>
        <!-- 视图后缀 -->
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>

我们现在写一个controller:

@Controller
public class UserController {
    @RequestMapping(value = "/show_index",method = RequestMethod.GET)
    public ModelAndView index(){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.addObject("name","zhangsan");//需要传递给jsp的参数
        modelAndView.setViewName("index");  //jsp页面的名字
        return modelAndView;
    }
}

然后在浏览器中访问http://localhost:8080/show_index,服务端主要从接收请求到响应主要做了下面几件事:

  1. tomcat收到请求之后,将请求转发给DispatcherServlet
  2. DispatcherServlet根据show_index找到UserController下的index()方法,执行里面的逻辑,主要是封装了一个ModelAndView,并将它作为返回值返回
  3. DispatcherServlet通过ModelAndView里的ViewName + spring.xml中配置的前缀和后缀找到index.jsp文件,并给它填充数据进行渲染
  4. 返回response,浏览器做出响应。

为了使整个流程更加清晰,我将HandlerMapperingHandlerAdapter以及ViewResovler这三个组件

这三个组件可以说是springmvc的核心组件:

  1. DispatcherServlet会委托HandlerMappering,通过URL来找到具体是哪个Controller来处理这次请求
  2. controller执行完成之后,返回ModelAndView,通过HandlerAdapter包装之后(为了支持多种类型的Controller),返回给DispatcherServlet
  3. DispatcherServlet委托ViewResovler进行视图渲染

发表评论

textsms
account_circle
email

springmvc入门
最近开始学习后端相关的知识了,虽然之前做Android开发也是用的java语言,但是移动端和后端的很多理念根本不一样,应该算是重新学习吧。 一. 什么是spring 学习后端绕不开的就是spring…
扫描二维码继续阅读
2019-11-15