编程之路

We fear the thing we want most.


  • 首页

  • 关于

  • 归档

  • 标签

DispatcherServlet 源码阅读(1)

发表于 2016-08-24   |  

DispatcherServlet 源码阅读(1)

有时间还是应该多看看源码。

阅读全文 »

使用Spring test测试Controller

发表于 2016-07-24   |  

使用Spring test测试Controller

以前测试一个Contoller是否实现了相应的功能(包括DAO层是否正确工作),我们的做法是mvn clean package然后启动tomcat启动应用,然后打开浏览器(或者使用curl)访问一个URL,然后看webapp的日志输出,据此判断功能是否实现了启。动tomcat好累。或者有时候写个Main方法测试有的功能,太low了。

是时候使用Spring test了。

阅读全文 »

使用MyBatis时的异常处理

发表于 2016-07-23   |  

使用MyBatis时的异常处理

使用mybatis时在Spring的配置文件中给sqlSessionFactory指定好dataSource,typeAliasesPackage,mapperLocations后,接下来写mappers xml及interface文件就好了,在数据层调用的时候很简单的两句:

1
2
SpitterMapper spitterMapper = sqlSessionTemplate.getMapper(SpitterMapper.class);
return spitterMapper.findAllSpitters();

但是这并不够,虽然出了什么错,会在终端告诉我们(比如说操作的数据库表并不存在,SQL语法有问题等等),但是在实际Web环境中,我们往往需要更多的控制,不论是否有异常都需要自己明确的处理,而不是直接出错,所以我们把异常逐渐递交给上层。

所以我们在这里需要自己加入异常处理,并且在异常发生的时候有对应的动作(打印日志,或者回复客户端对应的消息)。像下面这样,在Service层声明我们自己的异常,主动捕获异常,然后把异常(内部包含底层具体的异常)传递下去,而且在日志输出中可以看到实际发生了什么。下面的System.out.println在实际中使用logger。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Service(value = "spitterService")
public class SpitterService {

@Autowired
private SqlSessionTemplate sqlSessionTemplate;

public List<Spitter> findAllSpitters() {
try{
SpitterMapper spitterMapper = sqlSessionTemplate.getMapper(SpitterMapper.class);
return spitterMapper.findAllSpitters();
}catch (Exception e){
System.out.println("----------------------------");
System.out.println(e.getCause());
System.out.println("+++++++++++++++++++++++++++++");
}
return null;
}
}

完整代码

Spring AOP和AspectJ学习实践

发表于 2016-07-23   |  

Spring AOP和AspectJ学习实践

使用Spring AOP

Spring AOP需要的依赖:

1
2
3
4
5
<dependency>
<groupId>${spring.group}</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>

Spring中定义切面的方法,就是在配置文件中声明pointcut,以及pointcut对应的advice(有before,after等),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<aop:config>
<aop:aspect ref="audience"><!--定义一个audience切面-->

<aop:before pointcut=
"execution(* com.vonzhou.springinaction.springidolaop.Performer.perform(..))"
method="takeSeats" />


<aop:before pointcut=
"execution(* com.vonzhou.springinaction.springidolaop.Performer.perform(..))"
method="turnOffCellPhones" />


<aop:after-returning pointcut=
"execution(* com.vonzhou.springinaction.springidolaop.Performer.perform(..))"
method="applaud" /> <!--返回后通知-->


<aop:after-throwing pointcut=
"execution(* com.vonzhou.springinaction.springidolaop.Performer.perform(..))"
method="demandRefund" />


</aop:aspect>
</aop:config>

Pointcut定义的是切点,pointcut表明针对哪些方法需要AOP,然后基于AOP可以定义切点之前,之后,返回值,剖出异常时调用的方法,其实就是代理模式和装饰模式。

完整代码示例

Spring + aspectj

AspectJ比Spring AOP更加强大,是运行时织入(weave)。需要的依赖有:

1
2
3
4
5
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.9</version>
</dependency>

使用aspectj例子如下:

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
@Aspect
public class Audience {
@Pointcut(
"execution(* com.vonzhou.springinaction.springidol.Performer.perform(..))")
public void performance() {
}

@Before("performance()")
public void takeSeats() {
System.out.println("The audience is taking their seats.");
}

@Before("performance()")
public void turnOffCellPhones() {
System.out.println("The audience is turning off their cellphones");
}

@AfterReturning("performance()")
public void applaud() {
System.out.println("CLAP CLAP CLAP CLAP CLAP");
}

@AfterThrowing("performance()")
public void demandRefund() {
System.out.println("Boo! We want our money back!");
}

@Around("performance()")
public void around(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("before around ====");
try {
proceedingJoinPoint.proceed();
System.out.println("after around ===");
} catch (Throwable throwable) {
throwable.printStackTrace();
}

}
}

可以看到Around annotation注解使用的场景是在 Before annotation执行之前(进入方法之前)执行,然后 ProceedingPonintcut.proceed()执行完之后做一些统计处理,方法返回,然后执行After annotation。

完整代码示例

对Spring MVC Controller进行AOP

如何对Spring MVC Controller进行AOP呢?见网上说:对于Spring MVC Controller实行AOP不能一般处理,因为Controller中的方法映射处理,其实都交给了AnnotationMethodHandlerAdapter.handle(),所以要针对其定义pointcut。然而并没有用!(亲测, 如下)而且AnnotationMethodHandlerAdapter现在Deprecated,as of Spring 3.2, in favor of RequestMappingHandlerAdapter。

这样不行:

1
2
3
@Around("execution(* org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(..))")
public Object aa(ProceedingJoinPoint pjp) throws Throwable {
}

所以如果有这样的场景的话,可以使用Spring的HandlerInterceptor,参见。

遇到的问题

1
Caused by: org.xml.sax.SAXParseException; lineNumber: 42; columnNumber: 29; cvc-complex-type.2.4.c: ?????????, ??????? 'aop:aspectj-autoproxy' ????

原因在xml配置文件中没有写完整,少了http://www.springframework.org/schema/aop/spring-aop.xsd。

参考资源

Spring AOP vs AspectJ
AspectJ Runtime

Servlet Filter与HandlerInterceptor的对比

发表于 2016-07-23   |  

Servlet Filter与HandlerInterceptor的对比

对于Servlet Filter,官方文档中说的很好, 并且给出了常见的应用场景。

A filter is an object that performs filtering tasks on either the request to a resource (a servlet or static content), or on the response from a resource, or both.
Filters perform filtering in the doFilter method. Every Filter has access to a FilterConfig object from which it can obtain its initialization parameters, and a reference to the ServletContext which it can use, for example, to load resources needed for filtering tasks.

Filters are configured in the deployment descriptor of a web application.

Examples that have been identified for this design are:

  • Authentication Filters
  • Logging and Auditing Filters
  • Image conversion Filters
  • Data compression Filters
  • Encryption Filters
  • Tokenizing Filters
  • Filters that trigger resource access events
  • XSL/T filters
  • Mime-type chain Filter

接下来用简单的例子进行理解。

示例

实现Filter接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SimpleServletFilter implements Filter{
private static Logger logger = Logger.getLogger(SimpleServletFilter.class);

public void init(FilterConfig filterConfig) throws ServletException {
logger.info("SimpleServletFilter init....");
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String id = request.getParameter("id");
if(id != null && !id.isEmpty() && !id.equals("123")){
chain.doFilter(request,response);
}

// Reply directly here
HttpServletResponse httpResponse = (HttpServletResponse)response;
httpResponse.getWriter().print("Another Page...enjoy");
}

public void destroy() {
logger.info("SimpleServletFilter destroy....");
}
}

配置部署文件, filter所有的请求(?有问题,后面会说)。

1
2
3
4
5
6
7
8
<filter>
<filter-name>simpleFilter</filter-name>
<filter-class>com.vonzhou.learning.filter.SimpleServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>simpleFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

通过运行日志输出,结合前文HandlerInterceptor的对比,可以很清楚的看出filter和interceptor大概的生命周期。filter比interceptor更早出生,更晚死去。

1
2
3
4
5
6
7
8
9
10
11
12
2016-07-23 14:07:18,581  INFO SimpleServletFilter:16 - SimpleServletFilter init....
[2016-07-23 02:07:18,965] Artifact spring-interceptor:war exploded: Artifact is deployed successfully
[2016-07-23 02:07:18,965] Artifact spring-interceptor:war exploded: Deploy took 5,416 milliseconds
2016-07-23 14:07:24,133 INFO LogInterceptor:32 - Will call public java.lang.String com.vonzhou.learning.controller.UserController.serveUser(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Locale,org.springframework.ui.ModelMap)
2016-07-23 14:07:25,164 INFO LogInterceptor:38 - before return view page, i can get model = Some Data
2016-07-23 14:07:25,848 INFO LogInterceptor:48 - Call cost time 1715 mills!
2016-07-23 14:07:28,389 INFO LogInterceptor:32 - Will call public java.lang.String com.vonzhou.learning.controller.UserController.serveUser(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Locale,org.springframework.ui.ModelMap)
2016-07-23 14:07:29,393 INFO LogInterceptor:38 - before return view page, i can get model = Some Data
2016-07-23 14:07:29,395 INFO LogInterceptor:48 - Call cost time 1006 mills!
/usr/local/apache-tomcat-8.0.33/bin/catalina.sh stop
2016-07-23 14:08:12,607 INFO SimpleServletFilter:31 - SimpleServletFilter destroy....
Disconnected from server

完整代码示例

知识点总结

  • filter是比interceptor强大
  • filter操纵request,response的能力更大,可以直接response回应客户端,但是在interceptor的preHandle方法中操纵response不起作用
  • filter的粒度是更靠近前端的request级别,而interceptor处理的粒度测试靠近后端的Controller(或称为handler)的被映射的请求处理方法
  • filter可以双向的,根据request或者response来响应,但是interceptor只能是拦截进入这一端
  • 过程中发现,如果对静态资源配置了<mvc:resources mapping="/resources/**" location="/resources/"/>,那么如果访问的静态资源文件存在的话,filter并不会起作用(web.xml中的filter配置如下),即使把url-pattern指定为<url-pattern>/resources/*</url-pattern>(其实/就匹配了所有的请求)。*说明了mvc:resources配置的静态文件并不会经过Servlet filter, 虽然说filter很强大。
1
2
3
4
5
6
7
8
<filter>
<filter-name>simpleFilter</filter-name>
<filter-class>com.vonzhou.learning.filter.SimpleServletFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>simpleFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
  • 前面我说在interceptor的preHandle方法中操纵response不起作用,其实不够全面, 比如在preHandle方法中设置断点,当response回复消息后,此时shutdown服务器,这时在客户端会收到这里的消息。
  • 另一种情况就更加奇怪,如果在interceptor中的 preHandle 方法中 response write消息之后是 return false,此时返回的消息同时包含了filter中被拦截成功后应该返回的消息(见下面),所以这时就崩溃了,一片混乱,所以只能在使用 handlerinterceptor 的时候不要使用response回复消息了。
1
2
3
4
➜  ~ curl http://localhost:8888/user/service\?id\=11
--- From Interceptor, you are rejected.
+++ From Filter, Another Page...enjoy
➜ ~

参考

(1)The Essentials of Filters
(2) interface HandlerInterceptor

12…5
vonzhou

vonzhou

A Programmer's Notes

21 日志
8 标签
GitHub 知乎
© 2017 vonzhou
由 Hexo 强力驱动
主题 - NexT.Mist