0%

NJU-服务端开发笔记

Abstract

服务端开发课程第4讲:Web开发框架和上下文配置的XML方式与Java配置类方式

Spring MVC的流程图

  1. 用户请求DispathcerServlet。
  2. DispatcherServlet接受到请求,将根据请求信息交给处理器映射器。
  3. 处理器映射器(HandlerMapping)根据用户的url请求查找匹配该url的Handler,并返回一个执行链。
  4. DispacherServlet再根据执行链请求处理器适配器(HandlerAdapter)。
  5. 处理器适配器调用相应的handle进行处理。
  6. 对应的handler处理完成后返回ModelAndVIew给处理器适配器。
  7. 处理器适配器将接受的ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet请求视图解析器来解析视图。
  9. 视图解析器处理完后返回View对象给DispacherServlet。
  10. 最后前端控制器对View进行视图渲染(即将模型数据填充至视图中)。

项目的包图

Web.xml

首先是Web容器启动的上下文配置

它提示 在 classpath 的根路径(resource)下面有一个对应的文件 (applicationContext.xml)。

1
2
3
4
5
6
7
8
9
10
11
<!-- ①从类路径下加载Spring配置文件,classpath关键字特指类路径下加载 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!-- 负责启动Spring容器的监听器,他将引用①处的上下文参数获得Spring配置文件的地址 -->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>

多个配置文件可用逗号或空格分隔;Web容器监听器在Web容器启动时自动运行。

对应的上下文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
<context:component-scan base-package="com.example.dao"/>
<context:component-scan base-package="com.example.service"/>

<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/exampledb"
p:username="root"
p:password="root" /><!-- 请修改成你的数据库口令 -->

<!-- 配置Jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource" />

<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource" />

<!-- 通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod"
expression=" execution(* com.example.service..*(..))" />
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
</beans>

此外,需要将log4J.propertis日志配置文件放置在类路径下,以便日志引擎自动生效。

1
2
3
4
log4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%d %5p [%t] (%F:%L) - %m%n

接着我们看一下Spring MVC配置的相关信息。Spring MVC像Struts一样,也通过一个Servlet来截获URL请求,然后再进行相关的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- Spring MVC的主控Servlet -->
<servlet>
<servlet-name>viewspace</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<!-- * Spring MVC处理的URL -->
<servlet-mapping>
<servlet-name>viewspace</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>

它会根据 Servlet 的名字(viewspace)加上 servlet 的后缀(viewspace-servlet),在/WEB-INF目录下找到对应的 servlet 的 xml 文件(契约式规定)。

*处对这个Servlet的URL路径进行定义,在这里让所有以.html为后缀的URL都能被viewspace Servlet截获,进而转由Spring MVC框架进行处理(注意:对于那些无需任何动态处理的静态网页,则可以使用.htm后缀进行区分,以避免被框架截获)。

我们来看一下viewspace-servlet.xml文件的内容

servlet

它的主要内容包括:

1
2
3
4
5
6
7
8
9
<!-- 扫描web包,应用Spring的注解 -->
<context:component-scan base-package="com.example.web"/>

<!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>

会去对应的文件里寻找 Controller。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Controller
@RequestMapping(value = "/admin")
public class LoginController {

private UserService userService;

@Autowired
public LoginController(UserService userService) {
this.userService = userService;
}

@RequestMapping(value = "/login.html")
public String loginPage() {

return "login";
}

@RequestMapping(value = "/loginCheck.html")
public ModelAndView loginCheck(HttpServletRequest request, LoginInfo loginInfo) {
boolean isValidUser =
userService.hasMatchUser(loginInfo.getUserName(),
loginInfo.getPassword());
if (!isValidUser) {
return new ModelAndView("login", "error", "用户名或密码错误。");
} else {
User user = userService.findUserByUserName(loginInfo
.getUserName());
user.setLastIp(request.getLocalAddr());
user.setLastVisit(new Date());
userService.saveLog(user);
request.getSession().setAttribute("user", user);
return new ModelAndView("main");
}
}
}

另一个就是 视图解析器,解析对应的视图文件名。

1
2
3
4
5
6
<!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:viewClass="org.springframework.web.servlet.view.JstlView"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp"/>

应用程序上下文配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">

<!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
<context:component-scan base-package="com.example.dao"/>
<context:component-scan base-package="com.example.service"/>

<!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/exampledb"
p:username="root"
p:password="root" /><!-- 请修改成你的数据库口令 -->

<!-- 配置Jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource" />

<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource" />

<!-- 通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod"
expression=" execution(* com.example.service..*(..))" />
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>
</beans>

Dao 层代码

1
<context:component-scan base-package="com.example.dao"/>

业务层代码

1
<context:component-scan base-package="com.example.service"/>

配置数据源

1
2
3
4
5
6
7
   <!-- 配置数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/exampledb"
p:username="root"
p:password="root" /><!-- 请修改成你的数据库口令 -->

访问数据库的 jdbc 模板

1
2
3
<!-- 配置Jdbc模板  -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource" />

事务处理器

1
2
3
4
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource" />

事务增强处理器 - 切面定义

1
2
3
4
5
6
7
8
9
10
11
<!-- 通过AOP配置提供事务增强,让service包下所有Bean的所有方法拥有事务 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="serviceMethod"
expression=" execution(* com.example.service..*(..))" />
<aop:advisor pointcut-ref="serviceMethod" advice-ref="txAdvice" />
</aop:config>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" />
</tx:attributes>
</tx:advice>

以上组装出了一个 可运行的 Web 应用程序。

Java 配置类

初始化类。一个 Web 初始化器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class AddressBookWebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}

@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}

@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}

}

在配置中继承了AbstractAnnotationConfigDispatcherServletInitializer 这个抽象类,这个类在父类AbstractContextLoaderInitializer中注册了ContextLoadListener监听器,它会监听项目的启动,在项目启动时调用容器初始化方法完成Spring容器的初始化

将配置信息通过 @Override继承复写的方式告诉它。

例如,如果你需要引入 Servlet 的配置信息。

1
2
3
4
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[]{WebConfig.class};
}

通知 该 Web 其它的 Bean。

1
2
3
4
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[]{RootConfig.class};
}

Config

实例化 Component 路径 为 example.web

@EnableWebMvc 通知需要 Mvc 的框架。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
@EnableWebMvc
@ComponentScan("example.web")
public class WebConfig extends WebMvcConfigurerAdapter {

@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// TODO Auto-generated method stub
super.addResourceHandlers(registry);
}

}

视图解析器

1
2
3
4
5
6
7
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}

RootConfig

导入了其它类作为配置的补充( DataConfig)。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
@Import(DataConfig.class)
@ComponentScan(basePackages = {"example"},
excludeFilters = {
@Filter(type = FilterType.CUSTOM, value = WebPackage.class)
})
public class RootConfig {
public static class WebPackage extends RegexPatternTypeFilter {
public WebPackage() {
super(Pattern.compile("example\\.web"));
}
}
}

定义组件搜索路径。除了 web 以外的所有路径下寻找是否有需要实例化的 Bean 。(排除行为)

1
2
3
4
@ComponentScan(basePackages = {"example"},
excludeFilters = {
@Filter(type = FilterType.CUSTOM, value = WebPackage.class)
})

DataConfig

Dao 层所需要的的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class DataConfig {

@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("schema.sql")
.build();
}

@Bean
public JdbcOperations jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}

}

运行Web应用

有两种方式

  1. 将项目打包成.war文件,放入到本机tomcat/webapps/目录下,然后启动tomcat

  2. pom.xml文件中配置Web应用服务器插件(Jetty)(注意!Jetty版本要支持Servlet 3才能由Java Config类配置启动)(此处是8.1.15.v20140411

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <plugin>
    <groupId>org.mortbay.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>8.1.15.v20140411</version>
    <configuration>
    <scanIntervalSeconds>3</scanIntervalSeconds>
    <webAppSourceDirectory>src/main/webapp</webAppSourceDirectory>
    <webApp>
    <contextPath>/section4</contextPath>
    </webApp>
    <connectors>
    <connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
    <port>8088</port>
    <maxIdleTime>60000</maxIdleTime>
    </connector>
    </connectors>
    <stopKey>shutdown</stopKey>
    <stopPort>9998</stopPort>
    </configuration>
    </plugin>