Spring MVC官方文档学习笔记之Web入门
Posted shame11
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC官方文档学习笔记之Web入门相关的知识,希望对你有一定的参考价值。
注: 该章节主要为原创内容,为后续的Spring MVC内容做一个先行铺垫
1.Servlet的构建使用
(1) 选择Maven -> webapp来构建一个web应用
(2) 构建好后,打开pom.xml文件,一要注意打包方式为war包,二导入servlet依赖,如下
<!-- 打war包 -->
<packaging>war</packaging>
<!-- 导入servlet依赖 -->
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
(3) 替换webapp/WEB-INF/web.xml文件为如下内容,采用Servlet 3.1版本
<?xml version="1.0" encoding="UTF-8"?>
<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">
</web-app>
(4) 在main目录下,新建java目录和resources目录,并在java目录下新建包,最终项目目录结构如下
(5) 编写一个简单的servlet如下
@WebServlet("/example")
public class ExampleServlet extends HttpServlet
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
resp.getWriter().write("example...");
(6) 有了servlet后,我们得让服务器知道哪个请求要交给哪个servlet处理,因此还需要配置web.xml如下
<!-- web.xml中 -->
<web-app ...>
<!-- 配置servlet,给指定的servlet取一个名字 -->
<servlet>
<servlet-name>exampleServlet</servlet-name>
<servlet-class>cn.example.springmvc.boke.servlet.ExampleServlet</servlet-class>
</servlet>
<!-- 配置哪个请求交由哪个servlet来进行处理,这里为了方便使用 / ,即拦截所有的请求都交由exampleServlet来处理 -->
<servlet-mapping>
<servlet-name>exampleServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
(7) 然后,为了能够在网页上访问,我们得把这个项目部署到tomcat服务器中
首先,在URL栏中,添加上项目名称,此处为springmvc
然后,在Deployment中添加我们的项目
最后,注意 Application Context 中的值,应与前面在URL栏中添加的项目名称相同,此处均为springmvc
(8) 最后,启动tomcat服务器,在浏览器上输入 http://localhost:8080/springmvc/example ,如果能看到 example... 字符串,则说明项目配置成功
2.基于web.xml,整合Spring与Servlet
(1) 现在,web应用已经搭建好了,但是我们希望能够在该应用中使用Spring容器,该怎么办呢? 在之前的非web环境中,我们都是在main方法中创建ioc容器(如 new ClassPathXmlApplicationContext()),然后直接使用的,但是现在没有了main方法,该由谁来创建ioc容器呢? 答案就是由我们的web容器,可以在web应用初始化的时候来帮助我们创建,但创建好之后,我们该怎么获取到ioc容器呢? Servlet规定了4大作用域,分别为page域(PageContext),当前页面有效; request域(HttpServletContext),一次请求内有效; session域(HttpSession),一次会话内有效; application域(ServletContext),在当前整个web应用内有效,因此我们可以将创建好的ioc容器直接放到application域中,这样在任何位置,我们都能拿到ioc容器进行使用,具体示例如下
首先导入相关的Spring依赖包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.22.RELEASE</version>
</dependency>
</dependencies>
接着,修改我们的代码,配置一个普通的bean
//创建一个普通的java类
public class ExampleService
public String get()
return "user";
//然后,在resources目录下新建一个springmvc.xml,并将上面的ExampleService注册为一个bean
<beans ....>
<bean class="cn.example.springmvc.boke.service.ExampleService"></bean>
</beans>
接下来,我们就得让web容器来为我们创建ioc容器了,具体由谁来创建呢? Servlet有三大核心组件,即Servlet,用于处理请求;Filter,过滤器,用来拦截或修改请求;Listener,监听器,用于监听某个事件。显然,这里使用Listener最合适,那就由Listener来为我们创建ioc容器
<!-- web.xml中 -->
<!-- 当然,具体的Listener实现类代码是不需要由我们来写的,因为Spring早已内置了一个监听器(ContextLoaderListener),就是用于在基于web.xml的配置中来初始化ioc容器 -->
<web-app ....>
<!-- ContextLoaderListener实现了ServletContextListener,而这个ServletContextListener就是用于监听web应用的生命周期的,当web容器启动或终止web应用的时候,会触发ServletContextEvent事件,而该事件就会由ServletContextListener来处理,因此ContextLoaderListener就会在web应用启动的同时创建ioc容器,加载配置文件,具体可详见源码 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 注意:如果未指定配置文件的路径,那么默认会寻找/WEB-INF/applicationContext.xml配置文件,如果这个配置文件找不到,启动时就会报错
基于web.xml的配置所创建的ioc容器是基于xml配置的ioc容器(XmlWebApplicationContext),它会在容器启动的时候读取加载配置文件 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springmvc.xml</param-value>
</context-param>
<servlet>
<servlet-name>exampleServlet</servlet-name>
<servlet-class>cn.example.springmvc.boke.servlet.ExampleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>exampleServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
现在ioc容器有了,而且被Spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE为key放到了application域中,现在我们可以在任何地方被获取到它,如下所示
@WebServlet("/example")
public class ExampleServlet extends HttpServlet
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
//获取application域中的ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE属性值,即我们的ioc容器
XmlWebApplicationContext ctx = (XmlWebApplicationContext) this.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
//或者也可以使用Spring提供的工具类WebApplicationContextUtils来获取ioc容器,如下
//XmlWebApplicationContext ctx = (XmlWebApplicationContext) WebApplicationContextUtils.getWebApplicationContext(this.getServletContext());
//使用ioc容器,获取其中的bean
ExampleService exampleService = ctx.getBean(ExampleService.class);
resp.getWriter().write(exampleService.get());
//最后,重新启动容器,访问 http://localhost:8080/springmvc/example,会发现页面上出现 user 字符串
当然,向上面这样每次都通过get方法获取,很麻烦,我们可以借助Spring提供的工具类,在Servlet初始化的时候对Servlet进行依赖注入,如下
@WebServlet(urlPatterns = "/example")
public class ExampleServlet extends HttpServlet
//使用@Autowired注解标注需要进行依赖注入的bean
@Autowired
private ExampleService exampleService;
//Servlet初始化方法
@Override
public void init(ServletConfig config) throws ServletException
super.init(config);
//获取application域
ServletContext servletContext = config.getServletContext();
//使用Spring提供的自动注入工具类SpringBeanAutowiringSupport,直接进行依赖注入
SpringBeanAutowiringSupport.processInjectionBasedOnServletContext(this, servletContext);
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
resp.getWriter().println(exampleService.get());
3.基于Servlet扩展接口,整合Spring与Servlet
(1) 在上一节中,我们将ioc的创建配置于web.xml中,但此外我们还可以利用java代码的方式来创建ioc容器,可通过Servlet 3.0提供的ServletContainerInitializer接口,来在web容器启动的时候为第三方组件提供初始化的机会(例如注册Servlet等),如果要使用ServletContainerInitializer接口,那么就必须要在项目或所其依赖的jar包中的/META-INF/services目录下创建一个名称为javax.servlet.ServletContainerInitializer 的文件,而这个文件的具体内容,就是ServletContainerInitializer实现类的全限定类名称,然后,借助java的SPI技术,web容器便会加载这些实现类,通常情况下,ServletContainerInitializer这个接口通常会配合@HandlesTypes注解一起使用,而这个@HandlesTypes注解的作用就是让web容器收集我们项目中所有所指定的类,然后将这些类作为ServletContainerInitializer的onStartup方法参数传入,这样,在web容器启动的时候,我们就可以拿到这些我们所需的类然后创建它们
当然,同上面web.xml中的ContextLoaderListener,Spring也提供了一个ServletContainerInitializer接口的实现类SpringServletContainerInitializer,来创建帮助我们简化ioc容器的创建,首先在spring-mvc jar包中,就定义了一个/META-INF/services/javax.servlet.ServletContainerInitializer文件,然后,在启动时,web容器便会加载这个文件,读取里面的内容,为SpringServletContainerInitializer这个类
由于在SpringServletContainerInitializer上有注解@HandlesTypes标注,而这个注解的值为WebApplicationInitializer,因此,在创建SpringServletContainerInitializer对象前,web容器会收集应用内所有WebApplicationInitializer接口的实现类,并将它们作为参数传递给onStartup方法中的webAppInitializerClasses,这样,在web容器启动时,我们就能初始化我们所指定的对象
总而言之,在应用启动时,web容器会调用ServletContainerInitializer实现类(这里为SpringServletContainerInitializer)中的onStartup方法,而这个onStartup方法中又调用了@HandlesTypes注解所指定的类或接口(此处为WebApplicationInitializer)的实现类中的onStartup方法,因此,我们可以编写一个WebApplicationInitializer的实现类,来创建ioc容器,不过,Spring已经为我们提供了一个实现了WebApplicationInitializer接口的抽象类AbstractContextLoaderInitializer,它里面已经封装好了大部分的逻辑(比如将ioc容器置于application域中等),而我们所需要做的仅仅就是创建一下ioc容器而已,如下
public class IocInit extends AbstractContextLoaderInitializer
@Override
protected WebApplicationContext createRootApplicationContext()
XmlWebApplicationContext ctx = new XmlWebApplicationContext();
ctx.setConfigLocation("classpath:springmvc.xml");
return ctx;
此外,不要忘了注释掉web.xml中关于Spring的相关内容,否则会产生产生两个ioc容器
<web-app ....>
<!-- <!– ContextLoaderListener实现了ServletContextListener,而这个ServletContextListener就是用于监听web应用的生命周期的,当web容器启动或终止web应用的时候,会触发ServletContextEvent事件,而该事件就会由ServletContextListener来处理,因此ContextLoaderListener就会在web应用启动的同时会创建ioc容器,加载配置文件,具体可详见源码 –>-->
<!-- <listener>-->
<!-- <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>-->
<!-- </listener>-->
<!-- <!– 注意:如果未指定配置文件的路径,那么默认会寻找/WEB-INF/applicationContext.xml配置文件,如果这个配置文件找不到,启动时就会报错-->
<!-- 基于web.xml的配置所创建的ioc容器是基于xml配置的ioc容器(XmlWebApplicationContext),它会在容器启动的时候读取加载配置文件 –>-->
<!-- <context-param>-->
<!-- <param-name>contextConfigLocation</param-name>-->
<!-- <param-value>classpath:springmvc.xml</param-value>-->
<!-- </context-param>-->
<servlet>
<servlet-name>exampleServlet</servlet-name>
<servlet-class>cn.example.springmvc.boke.servlet.ExampleServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>exampleServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
最后,重启项目,输入http://localhost:8080/springmvc/example,看见user字符串则说明成功
4.Spring MVC
现在,我们将Servlet与Spring ioc容器整合到了一起,但如果我们需要处理新的请求的话,我们还得继承HttpServlet来编写新的Servlet,并将其配置到web.xml中,非常麻烦,因此,Spring变为我们提供了一个全新的框架 - Spring MVC来帮助我们进行开发
Spring AOP官方文档学习笔记之AOP概述
1.AOP简介
(1) Spring的关键组件之一就是AOP框架,它是对Spring IoC的补充(这意味着如果我们的IOC容器不需要AOP的话就不用引入AOP),此外,AOP亦是对OOP的补充,OOP的关注点在于类,而AOP的关注点在于切面,它可以将分散在不同类不同方法中重复的代码逻辑抽取出来,称之为通知(Advice),然后在运行时通过动态代理技术将“通知”组合进原有对象中,这样就能在实现原有预期效果的情况下达到减少代码冗余的目的
(2) 在Spring中,AOP主要用于两大方面,一是提供了声明式服务(比如声明式事物管理:@Transactional注解),二是让用户实现自定义切面,实现代码解偶,用于作为OOP的补充,一个简单的例子如下
//我们想在ExampleA中的每个方法中记录该方法的开始执行时间和结束执行时间
@Component
public class ExampleA
//在每个方法业务代码执行前和执行后,都有一个System.out.println用于打印执行时间
public void register()
System.out.println(System.currentTimeMillis() + " 开始执行...");
//注册相关的业务逻辑代码...
try
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(System.currentTimeMillis() + " 结束执行...");
public void sendEmail()
System.out.println(System.currentTimeMillis() + " 开始执行...");
//发送邮件相关的业务逻辑代码...
try
Thread.sleep(200);
catch (InterruptedException e)
e.printStackTrace();
System.out.println(System.currentTimeMillis() + " 结束执行...");
//现在,我们期望把"记录该方法的开始执行时间和结束执行时间"这一段代码逻辑提取出来,这样,之后再往ExampleA中添加了新的方法后,我们就不需要再手动添加两个 System.out.println 语句了,而被提取的这一段代码逻辑,被称之为通知(Advice),而通知加上要被增强的业务代码(比如ExampleA中的注册,发送邮件相关的业务逻辑代码)就形成了一个切面
//使用@Aspect注解定义切面类,用于声明定义一个个切面,你可能会疑惑为啥这个注解是由org.aspectj.aspectjrt提供的,那是因为虽然名字叫Spring AOP,给人的感觉好像是Spring单独开发的,但其实是Spring整合了AspectJ AOP框架(用AspectJ AOP的写法和定义方式,底层由Spring封装实现)一同实现的这个AOP功能
@Aspect
@Component
public class Logger
//1.重复的代码逻辑(System.out.println),即通知的提取:对应(3)和(5),用于记录方法的开始执行时间和结束执行时间
//2.指出要被增强的业务代码:假如有两个类ExampleA和ExampleB,我们需要记录ExampleA中每个方法的开始执行时间和结束执行时间,而ExampleB类不用,这就是(1)所发挥的作用
//3.执行要被增强的业务代码:仅仅指出要被增强的业务代码有哪些还不行,我们还需要调用这些业务代码,从而使它真正的被执行,这是(2)和(4)发挥的作用,(2)中的joinPoint称之为切入点,我们可以将它视为要被增强的业务代码(register,sendEmail)的抽象,而(4)joinPoint.proceed() 就代表着业务代码的执行,如同 thread.start() 代表着线程执行一样
//4.将通知和被增强的业务代码整个组合起来,称之为切面,即下面的recordTime方法,它就代表一个切面
@Around("execution(public * cn.example.spring.boke.ExampleA.*(..))") //(1)
public void recordTime(ProceedingJoinPoint joinPoint) throws Throwable //(2)
System.out.println(System.currentTimeMillis() + " 开始执行..."); //(3)
joinPoint.proceed(); //(4)
System.out.println(System.currentTimeMillis() + " 结束执行..."); //(5)
//之后,使用@EnableAspectJAutoProxy开启注解AOP功能
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("cn.example.spring.boke")
public class Config
//然后我们就可以去掉ExampleA的方法中的开始和结束时执行时间打印,因为我们已经把它们抽象提取出来了
@Component
public class ExampleA
public void register()
//注册相关的业务逻辑代码...
try
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
public void sendEmail()
//发送邮件相关的业务逻辑代码...
try
Thread.sleep(200);
catch (InterruptedException e)
e.printStackTrace();
//启动容器,执行register和sendEmail方法,可以看见我们的执行时间日志打印了出来,之后添加进ExampleA类中的新方法,也都会进行时间的打印,而无需我们手动的添加两个System.out.println语句,这便是AOP的强大功能
ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
ctx.getBean(ExampleA.class).register();
ctx.getBean(ExampleA.class).sendEmail();
2.AOP术语
(1) Aspect:切面 = 通知 + 切入点,如我们例子中所示的recordTime方法
(2) Join point:连接点,可以理解为类中的一个一个方法
(3) Advice:通知,即某个切面要在某个切入点上所要采取的行动,通常而言就是各个类各个方法中重复逻辑的抽象,比如上面这个例子中开始ExampleA中的2个方法中都有开始和结束时间打印,它们是重复的逻辑,因此我们可将它提取出来,称之为通知
(4) Pointcut:切入点,会被增强的连接点,比如上面的例子中我们对ExampleA中的方法register和sendEmail进行了增强,那么这些被增强的方法就有了一个新的称谓,即切入点,而其它的类中的方法没有被增强,还是一个个普通的方法,而这些普通的方法称之为连接点
(5) Introduction:引介,它的概念同通知差不多,只不过通知是针对切入点所提供的增强的逻辑,而引介是针对Class类,它可以在不修改原有类的代码的前提下,在运行期为原始类动态添加新的属性/方法
(6) Target object:目标对象,即会被增强的方法所属的对象,如上面例子中的ExampleA对象
(7) AOP proxy:AOP代理对象,是在目标对象上被增强了过后所产生的新对象,Spring采用动态代理技术来实现AOP,底层实现为JDK动态代理或CGLIB动态代理
(8) Weaving:织入,它代表一个动作,即将Advice组合进Target object中,从而产生AOP proxy这么的一个过程
3.AOP通知类型
(1) 根据通知与切入点的执行关系,Spring提供了5种通知类型,如下:
-
Before advice:前置通知,即通知在切入点执行之前执行
-
After returning advice:返回通知,即通知在切入点"正常"执行之后执行
-
After throwing advice:异常通知,即通知在切入点触发异常之后执行
-
After (finally) advice:后置通知,即无论切入点以何种方式执行(正常或异常),通知都会执行
-
Around advice:环绕通知,既可以在切入点之前执行通知,又可以在切入点之后执行,甚至可以不用执行切入点,它是最为强大的通知,我们上面例子中的recordTime就使用的是环绕通知
注意:After returning advice与After throwing advice两者是互斥的,因为如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值,因此这两个通知只会有一个执行
(2) Spring建议能使用具体的通知就去使用具体的通知,比如能用Before advice的情况下就不用去使用Around advice
4.Spring AOP特征
(1) Spring AOP是基于纯java实现的,不需要特殊的编译过程也不需要去控制类加载器的层次结构
(2) Spring AOP目前的切入点类型只能是方法,不能是变量或其它的类型,如果我们想要切入变量,可以使用AspectJ AOP框架,Spring与AspectJ无缝集成
(3) Spring AOP与IOC容器紧密集成,如果我们只是想要单独使用一个AOP框架,那么可以使用AspectJ
(4) Spring AOP支持基于注解的配置,也支持基于xml文件的配置,同IOC一样
(5) Spring AOP默认使用jdk动态代理,因此只要一个类实现了某个接口,那么它就能被代理,但如果我们的某个类没有实现接口,则会采用cglib动态代理来生成代理对象,同时我们也可以强制使用cglib动态代理作为默认选项
以上是关于Spring MVC官方文档学习笔记之Web入门的主要内容,如果未能解决你的问题,请参考以下文章
Spring AOP官方文档学习笔记之基于xml的Spring AOP