SpringMVC框架开发要点

Spring_framework

之前项目里用spring mvc都没有系统记录过,这次总结在一起以便日后查看。本文使用的springmvc版本为3.2.16,mybatis版本为3.3.1。官方文档 | 打印版 | 示例工程

1、SpringMVC基本配置

在j2ee项目里使用maven添加springmvc依赖,pom.xml里添加如下内容:

<dependencies>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-web</artifactId>
 <version>3.2.16.RELEASE</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-webmvc</artifactId>
 <version>3.2.16.RELEASE</version>
 </dependency>
</dependencies>

在web.xml里添加DispatcherServlet:

<listener>
 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
 <servlet-name>myapp</servlet-name>
 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<servlet-mapping>
 <servlet-name>myapp</servlet-name>
 <url-pattern>/</url-pattern>
</servlet-mapping>

在WEB-INF下添加applicationContext.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
</beans>

在WEB-INF下添加myapp-servlet.xml文件("myapp"这个名字要与web.xml里定义的servlet名称匹配):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
 xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
 http://www.springframework.org/schema/beans/spring-beans-3.2.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-3.2.xsd">

 <!-- 启动注解驱动的Spring MVC功能,注册请求url和注解POJO类方法的映射 -->
 <mvc:annotation-driven />

 <!-- 启动包扫描功能,以便注册带有@Controller、@Service、@repository、@Component等注解的类成为spring的bean -->
 <context:component-scan base-package="cn.myapp.*" />

 <!-- 静态资源文件,避免被DispatcherServlet拦截 -->
 <mvc:resources location="/css/" mapping="/css/**"/>
 <mvc:resources location="/scripts/" mapping="/scripts/**"/>
 <mvc:resources location="/images/" mapping="/images/**"/>

 <!-- 对模型视图名称的解析,在请求时模型视图名称添加前后缀 -->
 <bean
 class="org.springframework.web.servlet.view.InternalResourceViewResolver"
 p:prefix="/" p:suffix=".jsp" />
</beans>

典型的SpringMVC工程包括Repository、Service、Controller和View等多层结构,其中Repository层负责封装数据库操作,Service层负责业务逻辑,Controller层负责页面跳转输出,View层是前端展现。

写一个Controller验证一下,例如:

@Controller
public class TestController {

 /**
 * 简单url
 * @return
 */
 @RequestMapping(value = "/welcome", method = RequestMethod.GET)
 public String myMethod1() {
 return "/login";//forward to /login.jsp
 }

}

在浏览器里访问:http://localhost:8080/myapp/welcome,看是否转向到/login.jsp(如果还没有这个文件会报404错误

用@RequestParam注解可以获取到request里的parameter值,用ModelAndView对象可以将数据传递给页面(相当于request.setAttribute()),例如可以在Controller里这样写:

@RequestMapping(value = "/web/users")
public ModelAndView listUsers(@RequestParam(value = "p", required = false, defaultValue = "0") Integer page) {
 Map<String, Object> model = new HashMap<String, Object>();
 model.put("discounts", userService.listUsers(page));
 return new ModelAndView("users", model);
}

技巧1:可以使用Spring提供的ModelMap对象简化上面的代码(参考What's the difference between ModelAndView and ModelMap?):

@RequestMapping(value = "/web/users")
public String listUsers(@RequestParam(value = "p", required = false, defaultValue = "0") Integer page, ModelMap model) {
 model.put("discounts", userService.listUsers(page));//将数据添加到传入的model参数
 return "users";//只需直接返回view的名字
}

技巧2:有些页面不需要经过Controller(例如register界面经常是这种情况),但又希望用户不要直接在浏览器地址栏里看到.jsp页面,可以直接在myapp-servlet.xml里使用下面的方式指定自动跳转,以免自己定义很多空的Controller:

<!-- 访问/register时直接跳转到/register.jsp -->
<bean name="/register" class="org.springframework.web.servlet.mvc.UrlFilenameViewController"/>

技巧3:要让Controller返回二进制流,可直接使用response对象。具体可参考Downloading a file from spring controllers

技巧4:如果提交表单后发现中文乱码,请在web.xml里配置Spring提供的CharacterEncodingFilter,设置为utf-8。具体介绍文章网上很多,例如这篇

2、使用JNDI数据源连接MySQL

参考Spring DataSource JNDI with Tomcat Example。首先在pom.xml里添加dependencies:

<!-- MySQL Driver -->
<dependency>
 <groupId>mysql</groupId>
 <artifactId>mysql-connector-java</artifactId>
 <version>5.1.21</version>
</dependency>

在myapp-servlet.xml里添加数据源bean,这个数据源(java:comp/env/jdbc/mydb)的具体属性如url、user、password是事先在容器(如tomcat的<Context>)里定义好的:

<!-- DataSource bean-->
<bean id="dbDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
 <property name="jndiName" value="java:comp/env/jdbc/mydb"/>
</bean>

在需要使用此数据源的Controller类里(注意:@Controller本身也是@Bean;如果容器里只定义了一个DataSource,则@Qualifier注解可以省略):

 @Autowired
 @Qualifier("dbDataSource")
 private DataSource dataSource;

使用JdbcTemplate简化数据库操作:

还是先在pom.xml里添加依赖项:

<!-- Spring JDBC Support --> 
<dependency> 
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>3.2.16.RELEASE</version>
</dependency>

在myapp-servlet.xml里添加jdbcTemplate bean,其dataSource属性引用已经定义过的那个dbDataSource:

<!-- JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
 abstract="false" lazy-init="false" autowire="default">
 <property name="dataSource">
 <ref bean="dbDataSource" />
 </property>
</bean>

在代码里:

@Autowired
@Qualifier("jdbcTemplate")
private JdbcTemplate jdbcT;

SqlRowSet rs = jdbcT.queryForRowSet("select id,name from mytable");
while (rs.next()) {
 System.out.println(rs.getInt(1) + ", " + rs.getString(2));
}

3、集成MyBatis-3

在pom.xml里添加MyBatis依赖(最新稳定版本号在这里看)和mybatis-spring组件:

<dependency>
 <groupid>org.mybatis</groupid>
 <artifactid>mybatis</artifactid>
 <version>3.3.1</version>
</dependency>

<dependency>
 <groupId>org.mybatis</groupId>
 <artifactId>mybatis-spring</artifactId>
 <version>1.2.5</version>
</dependency>

在src目录下新建mybatis-config.xml文件,:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-/mybatis.org//DTD Config 3.0//EN" 
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 <settings>
 <setting name="lazyLoadingEnabled" value="false" />
 </settings>
 <typeAliases>
 <typeAlias alias="user" type="cn.myapp.model.User" />
 </typeAliases>
</configuration>

每个mybatis项目里都需要定义一组Mapper,它们是用来包装查询语句和字段映射的核心文件。每个Mapper可以是单一xml文件、单一java接口(配合annotation),也可以是两者结合。

  • 单一接口方式:
package cn.myapp.mapper;

public interface UserMapper {
 @Select("SELECT * FROM users WHERE id = #{userId}")
 User getUser(@Param("userId") int userId);
}

在myapp-servlet.xml里定义成bean:

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
 <property name="mapperInterface" value="cn.myapp.mapper.UserMapper" />
 <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
  • 单一xml文件方式(其中resultType属性值可以是类的全名,例如cn.myapp.User,或者java.util.HashMap;也可以是在mybatis-config.xml里定义过的alias名):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.myapp.mapper.UserMapper">
 <select id="getUser" resultType="cn.myapp.User">
 SELECT * FROM user WHERE id = #{id}
 </select>

</mapper>

定义为bean的方法同上。

代码里这样调用,略复杂且引号里的名称容易写错:

SqlSession session = sqlSessionFactory.openSession();
User user = session.getUser("cn.myapp.mapper.UserMapper.getUser", 101);
  • xml与java接口结合的方式(xml文件内容同上),让两个文件放在同一目录下(“If the UserMapper has a corresponding MyBatis XML mapper file in the same classpath location as the mapper interface, it will be parsed automatically by the MapperFactoryBean. ” 参考)。java接口如下:
package cn.myapp.mapper;

public interface UserMapper {
 User getUser(int id);
}

定义为bean的方法同上。代码里这样调用:

@Autowired
@Qualifier("userMapper")
private UserMapper userMapper;

{
  User user = userMapper.getUser(101);
}

以上三种方式,个人推荐最后一种,这样只要查看.xml文件就能看到所有查询语句和映射关系(resultmap,稍后介绍),而接口只负责提供更简单的调用方式。

注意1:对String类型的参数,接口里参数定义的@Param注解不可少,否则提示There is no getter for property named 'xx' in 'class java.lang.String错误。

注意2:Mapper文件里的参数(如#{id})的使用方法可见官方文档,如果用${id}表示不要对参数做任何转换(例如加单引号)。

注意3:如果想让返回结果是map类型,可以这样定义:resultType="java.util.HashMap",但要求sql执行结果最多只能有一条数据(即selectOne);map里的entry是类似这样的:<"id", 5>,<"name","张三">,而不是<5, "张三">, <6, "李四">,后者的实现还比较复杂,需要用result handler实现。

为了使用Mapper,需要在myapp-servlet.xml里定义一个SessionFactory bean和mapper bean(dataSource的定义在文章前面已经有了):

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
 <property name="configLocation" value="classpath:mybatis-config.xml" />
 <property name="dataSource" ref="dbDataSource" />
</bean>

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
 <property name="mapperInterface" value="cn.myapp.mapper.UserMapper" />
 <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

在需要查询user的地方这样使用:

int userId = ...;
User user = userMapper.getUser(userId);

使用Dynamic SQL支持加参数查询:

例如希望在mapper里定义一个查询参数groupId,如果传入的groupId值大于零则返回属于指定用户组的用户列表。此时应在mapper里这样定义SQL语句:

<select id="listDiscounts" resultType="cn.myapp.User">
 SELECT * FROM user WHERE 1=1
 <if test="groupId > 0">
 and group_id = #{groupId}
 </if>
</select>

相应的java接口(注意@Param对于Dynamic SQL的情况不能省略,否则会报错There is no getter for property named 'xxx' in 'class java.lang.Integer 参考):

package cn.myapp.mapper;

public interface UserMapper {
 User listUsers(@Param("groupId") int groupId);
}

获取自增字段值

在mapper的<insert>里使用useGeneratedKeys和keyProperty即可将自增字段的值赋值到指定属性上,例如:

<insert id="insert" useGeneratedKeys="true" keyProperty="id">
...
</insert>

MyBatis物理分页方案

目前使用Mybatis-PageHelper这个开源项目,需要配置一个mybatis的plugin,代码里增加一行PageHelper.startPage()代码,返回的列表用PageInfo对象包装即可。例如在service类里这样使用:

PageHelper.startPage(curPage, 10);
List<User> users = userDao.listUsers();
PageInfo<User> pageInfo = new PageInfo<User>(users);
return pageInfo;

这个方案有一个缺点就是页码固定从1开始,而不是从0开始,应该允许开发者设置就好了。

事务

在myapp-servlet.xml里添加TransactionManager的定义,并且启用基于annotation的事务支持扫描,下面的代码直接取自官方文档

<?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: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/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">

  <!-- this is the service object that we want to make transactional -->
  <bean id="fooService" class="x.y.service.DefaultFooService"/>

  <!-- enable the configuration of transactional behavior based on annotations -->
  <tx:annotation-driven transaction-manager="txManager"/>

  <!-- a PlatformTransactionManager is still required -->
  <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <!-- (this dependency is defined somewhere else) -->
  <property name="dataSource" ref="dataSource"/>
  </bean>

  <!-- other <bean/> definitions here -->

</beans>

为需要事务支持的bean添加@Transactional注解,例如:

// the service class that we want to make transactional
@Service
@Transactional
public class DefaultFooService implements FooService {

  Foo getFoo(String fooName);

  Foo getFoo(String fooName, String barName);

  void insertFoo(Foo foo);

  void updateFoo(Foo foo);
}

@Transactional可以用来修饰接口、类、接口中的方法和类中的public方法,官方文档建议只用于类或类中的public方法。

4、实现Restful URL

Restful基本约定:

  • POST:新增一个对象
  • GET:获取一个对象,例如GET /user/1
  • DELETE:删除一个对象
  • PUT:更新一个对象

在Controller里这样定义,就可以实现 http://www.myapp.com/profile/1 这样的url风格:

/**
 * 带参数的url
 * @param request
 * @param response
 * @param user url里的参数
 * @param modelMap
 * @return
 * @throws Exception
 */
 @RequestMapping(value = "/profile/{user}", method = RequestMethod.GET)
 public ModelAndView myMethod2(HttpServletRequest request, HttpServletResponse response,
 @PathVariable("user") String user, ModelMap modelMap) throws Exception {
 System.out.println("User id is: " + user);
 ...
 modelMap.put("profile", ...);
 return new ModelAndView("/profile", modelMap);
 }

浏览器里<form>的method只支持GET和POST,为了能以PUT、DELETE方法提交请求,需要在jsp里用标签来写:

<form:form action="${ctx}/profile/${user.userId}" method="put">
...
</form:form>

上面的代码运行时会转换为类似下面这个样子:

<form id="user" action="/myapp/profile/2" method="post">
<input type="hidden" name="_method" value="put"/>
...
</form>

所以我们还需要在myapp-servlet.xml里配置一个filter自动处理“_method”这个参数:

<filter>
 <filter-name>HiddenHttpMethodFilter</filter-name>
 <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

接受JSON格式的输入(需要先引入json-lib),在Controller里这样写:

import net.sf.json.JSONObject;
@RequestMapping(value = "/api/search", method = RequestMethod.POST)
public Object search(@RequestBody JSONObject jReq) {
 int page = jReq.optInt("page");
 ...
}

注意,请求时需要指定请求的content-type为"application/json"(jquery处理方法如下),否则会报“415 Unsupported Media Type”的HTTP错误:

/**
 * 防止"http 415 Unsupported Media Type"
 */
$.ajaxSetup({ 
 contentType : 'application/json' 
 });

直接输出JSON到response

使用@ResponseBody注解Controller里的方法,即可直接输出文本,相当于response.getWriter().print(),例如:

@RequestMapping(value = "/hello", method = RequestMethod.GET)
@ResponseBody
public String hello() {
 return "{'id':1}";
}

注意:如果@ResponseBody输出有乱码,可以在@RequestMapping里添加produces="text/html;charset=utf-8"或produces="application/json;charset=utf-8"来解决。参考链接

使用jackson自动将对象转换为json:

先pom.xml里添加依赖(注意:-asl后缀表示apache license,-lgpl后缀表示LGPL license,参考):

<!-- Jackson -->
<dependency>
 <groupId>org.codehaus.jackson</groupId>
 <artifactId>jackson-core-asl</artifactId>
 <version>1.9.11</version>
</dependency>

<dependency>
 <groupId>org.codehaus.jackson</groupId>
 <artifactId>jackson-mapper-asl</artifactId>
 <version>1.9.11</version>
</dependency>

在myapp-servlet.xml里配置jackson自动转换:

<!-- Jackson -->
<bean id="mappingJacksonHttpMessageConverter" class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter" />

注意:在Spring 4.x里这个类的名字改为了org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

在Controller里直接返回对象:

@RequestMapping(value = "/hello", method = RequestMethod.GET)
@ResponseBody
public Object hello() {
 return new User();
}

5、单元测试

首先是官方文档链接。为对springmvc的项目进行单元测试,需在项目里引入junit和spring-test依赖项:

<dependency>
 <groupId>junit</groupId>
 <artifactId>junit</artifactId>
 <version>4.9</version>
 <scope>test</scope>
</dependency>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-test</artifactId>
 <version>3.2.16.RELEASE</version>
 <scope>test</scope>
</dependency>

测试Service

下面是一个单元测试的例子:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = { "file:WebContent/WEB-INF/applicationContext.xml", "file:WebContent/WEB-INF/myapp-servlet.xml" })

//使用test profile
@ActiveProfiles("test")

//自动回滚,避免污染数据库
@Transactional
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class MyServiceTest {

 @Test
 public void testInsert(){
 ...
 }
}

上面的代码中,使用@ActiveProfiles是有必要的,因为在开发环境/生产环境中我们一般在web容器中通过jndi定义数据源,但测试环境里因为没有启动web容器所以这个jndi是不存在的。为此,要在myapp-servlet.xml里定义dev和test两个profile,如下所示:

 <beans profile="dev">
 <bean id="dbDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
 <property name="jndiName" value="java:comp/env/jdbc/mydb" />
 </bean>
 </beans>

 <beans profile="test">
 <bean id="dbDataSource"
 class="org.springframework.jdbc.datasource.DriverManagerDataSource"
 p:driverClassName="com.mysql.jdbc.Driver"
 p:url="jdbc:mysql://localhost:3306/mydb?characterEncoding=utf8"
 p:username="root" p:password="mypassword" />
 </beans>

为方便起见,在web.xml里告诉Spring缺省使用dev这个profile,以免在非测试代码的每个类里都指定profile:

<context-param>
 <param-name>spring.profiles.default</param-name>
 <param-value>dev</param-value>
</context-param>

这样测试环境就可以连接到dev数据库进行测试了。

测试Controller/REST接口

使用Spring Test提供的MockMVC对象,参考官方文档之Server-Side Tests部分。以下是一个单元测试的例子:

package com.myapp;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = { "file:WebContent/WEB-INF/applicationContext.xml", "file:WebContent/WEB-INF/myapp-servlet.xml" })

//使用test profile
@ActiveProfiles("test")

//自动回滚,避免污染数据库
@Transactional
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class UserTest {

 @Autowired
 WebApplicationContext wac;

 private MockMvc mockMvc;

 @Before
 public void setup() {
 mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
 }

 @Test
 public void testWelcomeMybatis() {
 try {
 mockMvc.perform(get("/user/25")).andExpect(status().isOk())
 .andExpect(content().contentType("application/json;charset=UTF-8")).andDo(print())
 .andExpect(jsonPath("$.name").value("Tom"));
 } catch (Exception e) {
 e.printStackTrace();
 }
 }

 @After
 public void teardown() {

 }
}

需要注意几点:

1、需要添加一些静态import才能使用status().isOK()这样的fluent语法;

2、在@WebAppConfiguration注解的测试类里,可以直接@Autowired注解WebApplicationContext实例;

3、为支持jsonpath需要在pom.xml里添加下面的依赖项。当前最新版本是2.2.0,但经测试与spring-test 3.2.16不兼容(jsonpath 1.0.0也不行),compile方法提示NoSuchMethodError,需要使用0.8.1版或0.9.0版(参考链接):

<dependency>
 <groupId>com.jayway.jsonpath</groupId>
 <artifactId>json-path</artifactId>
 <version>0.8.1</version>
 <scope>test</scope>
</dependency>

6、使用AOP

利用AOP可以比较方便的实现日志输出、性能监控等业务逻辑。这里以注解方式为例介绍一下如何使用。

首先在pom.xml里添加dependencies:

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-aop</artifactId>
 <version>3.2.16.RELEASE</version>
</dependency>

<dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjrt</artifactId>
 <version>1.6.11</version>
</dependency>

<dependency>
 <groupId>org.aspectj</groupId>
 <artifactId>aspectjweaver</artifactId>
 <version>1.6.11</version> 
</dependency>

然后写一个Aspect类,要同时用@Component和@Aspect注解,前者定义此类为一个Bean,后者定义此类为一个Aspect:

package cn.myapp;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.After;

@Component
@Aspect
public class LoggerAspect {

 @Before("execution(* cn.myapp..*.*(..))")
 public void logBefore(JoinPoint joinPoint) {
 System.out.println("logBefore() is running!");
 }

 @After("execution(* cn.myapp..*.*(..))")
 public void logAfter(JoinPoint joinPoint) {
 System.out.println("logAfter() is running!");
 }
}

在myapp-servlet.xml里添加autoproxy支持:

<aop:aspectj-autoproxy />

现在,执行任意cn.myapp包下的任意java类里的任意方法,都会在控制台里打印出logBefore和logAfter信息。

参考链接:

以上java代码里的@Before、@After被称作“Advice”,完整的语法可参考eclipse.org上的这个链接

Spring AOP + AspectJ annotation example

7、其他

统一异常处理

在SpringMVC里使用@ControllerAdvice注解和@ExeptionHandler注解可以实现统一的异常处理。当在Controller里throw new MyException("...")后,会自动将此异常按指定方式输出,例如输出为json:

@ControllerAdvice
public class MyExceptionController {

 @ExceptionHandler(MyException.class)
 @ResponseBody
 public Map<String, Object> handleException(MyException exception) {
 Map<String, Object> map = new HashMap<>();
 map.put("success", false);
 map.put("msg", exception.getMessage());
 return map;
 }

}

输出二进制数据

以下示例代码展示了如何将用户头像图片输出到response:

@RequestMapping(value = "/user/avatar/{userId}", method = RequestMethod.GET, produces = "image/jpeg")
public void avatar(@PathVariable("userId") int userId, HttpServletResponse response) {

 //从文件或数据库获取图片数据
 byte[] byteArray = ...;

 response.getOutputStream().write(byteArray);

}

待续