Categories
木有技术

Spring MVC POST FormData乱码问题(不使用web.xml)

在使用jQuery ajax一个FormData的时候(我要同时传文件和数据,所以用了FormData),发现上传的表单数据里,中文都是乱码。查到的结果就是说Servlet在没有读到ContentType的时候,会默认使用ISO-8859-1编码。具体原因可以参考《Spring MVC的Post请求参数中文乱码的原因&处理
Web.xml上添加Filter有个等效的Java写法,我的项目里没有web.xml配置文件:)
其实就是在extends AbstractAnnotationConfigDispatcherServletInitializer的时候override一个方法

@Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter encodingFilter = new CharacterEncodingFilter();
        encodingFilter.setEncoding("UTF-8");
        return new Filter[]{encodingFilter};
    }

然后就好了,很方便

Categories
不学无术

Spring AOP + log4j 实现SpringMVC站点日志输出功能(使用maven依赖)

最近真是不停地学各种东西,现在发现难怪那么多人用java,成熟的包一套一套的。
我们目标是,在一些特定的方法执行的时候(比如执行前,执行完毕)通过日志输出一些东西。这正符合了《Spring in Action》里头说到的“这些功能需要用到应用程序的多个地方,但是我们又不想在每个点调用他们”。为了顺应时代的潮流(其实是我没学xml定义),这里全部使用基于注释(annotation)的方式实现。

Maven依赖

需要引入AOP相关、log4j相关的依赖包
pom.xml

        <!-- AOP -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
        <!-- log4j-->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

目录结构

里面提到的相关文件,目录结构大致如下:

CreativeCrowd
|--src/main
      |--...
      |--java/edu/edu/inlab
          |--config
             |--CreCrowdAppInitializer
             |--RootConfig
             |--WebConfig
          |--service
             |--LoggingService.java
      |--resources
          |--log4j.properties
...

如有需要全·套的,可以直接去我的GitHub项目看看:https://github.com/idailylife/CreativeCrowd

Aspect构建

切面的构建可以声明一个类来并加入注释实现。我做的是一个日志服务,有下面的声明
LoggingService.java

package edu.inlab.service;
import edu.inlab.models.json.MTurkIdValidationRequestBody;
import org.apache.log4j.Logger;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import javax.servlet.http.HttpServletRequest;
@Aspect
public class LoggingService {
    final static Logger logger = Logger.getLogger(LoggingService.class);
    /* User */
    @Pointcut("execution(* edu.inlab.web.UserController.userLogIn(..))")
    public void userLoginPost(){}
    @Before("userLoginPost()")
    public void logUserLoginAttempt(){
        logger.debug("user login attempt.");
    }
    /* Task */
    @Pointcut(value = "execution(* edu.inlab.web.TaskController.checkMturkId(..))" +
    "&& args(request, body,..)", argNames = "request, body")
    public void checkMTurkId(HttpServletRequest request, MTurkIdValidationRequestBody body){}
    @Before(value = "checkMTurkId(request, body)", argNames = "request, body")
    public void logCheckMtAttempt(HttpServletRequest request, MTurkIdValidationRequestBody body){
        logger.debug("Check mturk id, Remote IP= " + getRemoteIP(request) +
                ", RequestBody= " + body);
    }
    String getRemoteIP(HttpServletRequest request){
        String ip = request.getHeader("X-FORWARDED-FOR");
        if(ip == null){
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}

里面的重点是要用@Aspect声明这是一个切面,另外@Pointcut是切点的注释,而@Before这种就是对应的前置通知(当然也可以是@After等等啦)
看一下里面的关于TaskController的操作(/*Task*/下面的),这其实是我控制器里头的一个函数,原函数比较长,函数签名是这样:

public AjaxResponseBody checkMturkId(HttpServletRequest request,
                                         @Valid MTurkIdValidationRequestBody body,
                                         BindingResult bindingResult)

可以看到里面有3个参数,而在切面定义的时候,我写日志只需要它两个参数就可以了,于是就有了下图这样的定义:
SpringAOP
上图这种写法用到了命名切入点,其实还有匿名切入点,这个具体可以看下面给的参考文献,介绍的很详细了。
execution(* edu.inlab.web.TaskController.checkMturkId(..))这种里面有个*号,这表示接受任何形式的返回参数,当然如果要指定也是可以的,记得要放类型的全名(比如com.blabla.SomeClass)。

配置

声明了切面,也别忘了在Spring里面开启AOP的东西。对应我的SpringMVC项目,我再rootConfig里面写了个Bean。
什么是RootConfig?就是在extend AbstractAnnotationConfigDispatcherServletInitializer的时候Override的那个方法嘛,比如:
CreCrowdAppInitializer.java

public class CreCrowdAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{RootConfig.class};
    }
    ...
}

RootConfig.java
开启AOP的关键是在配置文件中声明@EnableAspectJAutoProxy,并且给提供个可以注入的Bean

@Configuration
@ComponentScan(basePackages = "edu.inlab",
        excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)
})
@EnableAspectJAutoProxy
public class RootConfig {
    @Bean
    public LoggingService loggingService(){
        return new LoggingService();
    }
}

Log4j

log4j的使用很方便,而且发现开了log4j以后Hibernate自己也会输出一大堆东西(这个跟hibernate的配置有关)。Log4j在maven里面添加依赖之后,需要声明一个配置文件log4j.properties,放在src目录下。基本的配置可以照着下面画葫芦:
log4j.properties

# Root logger option
log4j.rootLogger=DEBUG, stdout, file
# Redirect log messages to console
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
# Redirect log messages to a log file, support file rolling.
log4j.appender.file=org.apache.log4j.RollingFileAppender
log4j.appender.file.File=D:/Code/Java/CreativeCrowd/log/common.log
log4j.appender.file.MaxFileSize=5MB
log4j.appender.file.MaxBackupIndex=10
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n

然后就是使用了。在之前的代码里其实已经写了log4j的一些东西,首先需要拿到log4j的一个静态对象(这个是静态函数给的)

final static Logger logger = Logger.getLogger(LoggingService.class);

里面的参数是你声明的类。
然后就可以直接使用logger啦,比如

logger.debug(...)

然后运行起来,命令行也会看得到输出(真心推荐IntelliJ IDEA,edu邮箱可以有免费优惠!!!)
log4j
 

参考文献:

Categories
不学无术 木有技术

在SpringMVC 4 中用注释(Annotation)方式使用 Google Kaptcha (Captcha)

原创内容,转载请注明来自http://boweihe.me/?p=2026

近期在努力学习SpringMVC,因为之前对JSP也是一知半解的,干脆拿了本《Spring in Action》(4th edition)啃,发现使用注释的方式比用xml来的有意思一些。网站中要用到验证码,目前能找到的文档都是用xml配置的,感觉有点不爽,决定学我党“摸着石头过河”一次,希望不要naive了..

Maven库

Google自家的库估计是没了,大概是时间太久远了吧。我用的Maven库是这个

<dependency>
            <groupId>com.github.penggle</groupId>
            <artifactId>kaptcha</artifactId>
            <version>2.3.2</version>
</dependency>

Bean配置文件

需要把原有的applicationContext.xml用注释的方式实现,其实就是让Spring找到个可以用的Bean并加载相关配置。
 
我构造了一个类来搞定这个事情,里面的配置参数会从application.properties文件中读取,我仅仅实现了我需要的几个参数,反正如果要加的话就在kaptchaProperties方法里面写就可以,然后Autowire一个Environment用来读取文件配置参数。主要是@Configuration 注释,这个是告诉Spring我是个配置类,这个还是从Hibernate配置中学过来的,哈哈。
声明出来的这个@Bean(name = “captchaProducer”) 就是后面Controller里面要用的了

package edu.inlab.config;
import com.google.code.kaptcha.impl.DefaultKaptcha;
import com.google.code.kaptcha.util.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import java.util.Properties;
/**
 * Created by inlab-dell on 2016/5/17.
 */
@Configuration
@PropertySource(value = {"classpath:application.properties"})
public class KaptchaConfig {
    @Autowired
    private Environment environment;
    private DefaultKaptcha kaptcha;
    @Bean(name = "captchaProducer")
    public DefaultKaptcha getKaptchaProducer(){
        if(null == kaptcha) {
            kaptcha = new DefaultKaptcha();
            kaptcha.setConfig(getKaptchaConfig());
        }
        return kaptcha;
    }
    @Bean
    public Config getKaptchaConfig(){
        return new Config(kaptchaProperties());
    }
    private Properties kaptchaProperties(){
        Properties properties = new Properties();
        properties.put("kaptcha.image.width",
                environment.getRequiredProperty("kaptcha.image.width"));
        properties.put("kaptcha.image.height",
                environment.getRequiredProperty("kaptcha.image.height"));
        properties.put("kaptcha.textproducer.char.string",
                environment.getRequiredProperty("kaptcha.textproducer.char.string"));
        properties.put("kaptcha.textproducer.char.length",
                environment.getRequiredProperty("kaptcha.textproducer.char.length"));
        return properties;
    }
}

对应的application.properties,具体含义可以看参考文献里,解释的很清楚了。

kaptcha.image.width = 200
kaptcha.image.height = 50
kaptcha.textproducer.char.string = ABCDEFGHKLMNPQRSTWXY3456789
kaptcha.textproducer.char.length = 6

CaptchaController 控制器

项目在实习前怕是赶不完来了,我还是少花点时间写博客吧。
控制器的实现Wiki上都有,主要的是要@Autowire之前我们做好的那个Bean,大概是这样。其实就是个基于set方法的注入嘛~

@Controller
@RequestMapping("/captcha")
public class CaptchaController {
    private Producer captchaProducer;
    @Autowired
    public void setCaptchaProducer(Producer captchaProducer) {
        this.captchaProducer = captchaProducer;
    }
    @RequestMapping(method = RequestMethod.GET)
    public ModelAndView handleRequest(HttpServletRequest request,
                  HttpServletResponse response) throws Exception{
        // ...
    }
}

 

参考文献:

  1. 在springmvc项目中使用kaptcha生成验证码
  2. 简单Maven的Web项目之验证码(Kaptcha篇)
  3. Spring mvc框架下使用kaptcha生成验证码
  4. https://code.google.com/archive/p/kaptcha/wikis