Categories
不学无术

Hibernate Community Documentation 之 Chapter 5. Locking 锁(翻译)

私人翻译,如有错误请大胆指出,我相信不会有人转载:)
原文见https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html

所谓“锁”是指用来在关系型数据库中防止在数据读取(read)到数据使用(used)之间其内容发生变化的操作。
锁的实现分为两种,分别是乐观(Optimistic)与悲观(Pessimestic)。

锁策略

乐观锁

乐观锁假设多组事务(transaction)能够互不干涉地并发完成,因此这些事务执行时不需要将他们影响到的数据加锁。在事务提交(commit)前,每个事务都会校验以确保它的数据没有被别人改动过。如果校验的结果是已有修改冲突(conflicting modifications),整个事务就会回滚(rollback)。

悲观锁

悲观锁假设事务之间会相互冲突,因此要求在数据读取时上锁,直至数据使用完毕后才释放锁。
 
Hibernate为以上两种锁策略都提供了支持。

5.1 乐观锁 Optimistic

如果你的应用使用了长事务或是由许多数据库事务组成的会话(conversation),你可以通过存储版本(version)数据使得当一个实体(entity)被两个会话更新时,最后一个提交更改的会话会被告知出现冲突,并且此时不会影响到前一个会话进行的更改。该方法保证了一定的隔离性,扩展性好且特别适用于常读取、少写入(Read-Often Write-Sometimes)的场景。

5.1.1 专用版本号 Dedicated version number

实现乐观锁的版本号机制是通过@Version 注释来实现的

@Entity
public class Flight implements Serializable {
...
    @Version
    @Column(name="OPTLOCK")
    public Integer getVersion() { ... }
}

此处,Version属性被映射到OPTLOCK列,实体管理器(the entity manager)通过这个注释来检测有冲突的更新,借此防止数据更新被最后一个写入提交操作给覆盖(last-commit-wins)。
Version列可以是任意类型的,只要你定义并且实现一个自定义的UserVersionType即可。
Hibernate禁止你的应用手动变更版本号(version number)。如需手动增加版本号,请查看下列相关文档:
LockModeType.OPTIMISTIC_FORCE_INCREMENT 或 LockModeType.PESSIMISTIC_FORCE_INCREMENTcheck。

如果版本号是由数据库通过注入triggert自动生成的,需使用注释

@org.hibernate.annotations.Generated(GenerationTime.ALWAYS)
<version
        column="version_column"
        name="propertyName"
        type="typename"
        access="field|property|ClassName"
        unsaved-value="null|negative|undefined"
        generated="never|always"
        insert="true|false"
        node="element-name|@attribute-name|element/@attribute|."
/>
column 存储版本号的列名. 可选,默认是属性名.
name 需要持久化的类名
type 版本号存储类型. 可选,默认是 integer.
access Hibernate访问属性时使用的方式. 可选, 默认是 property.
unsaved-value Indicates that an instance is newly instantiated and thus unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value,undefined, indicates that the identifier property value should be used. Optional.
generated 表明这是由数据库生成的版本号. 可选,默认是never.
insert 是否在SQL insert 语句中加入版本号. 默认是 true, 但如果你的版本号列有默认值0的话,可以设置成false.

5.1.2 时间戳 Timestamp

与版本号相比,时间戳在实现锁时稍显不可靠一些,但如果应用想要借此实现一些其他功能,还是有用的。如果@Version 注释应用在Date或Calendar对象上时,会自动采用时间戳实现。

@Entity
public class Flight implements Serializable {
...
    @Version
    public Date getLastUpdate() { ... }
}

如果你用注释@org.hibernate.annotations.Source 特别指定,Hibernate可以从数据库或者JVM中撷取时间戳的值:可以是org.hibernate.annotations.SourceType.DB或者是org.hibernate.annotations.SourceType.VM,如果不特别指定,默认值是使用数据库的时间戳。
如果指定了@org.hibernate.annotations.Generated(GenerationTime.ALWAYS) 注释,时间戳的值可以借由数据库直接生成,而不是Hibernate。

<timestamp
        column="timestamp_column"
        name="propertyName"
        access="field|property|ClassName"
        unsaved-value="null|undefined"
        source="vm|db"
        generated="never|always"
        node="element-name|@attribute-name|element/@attribute|."
/>
column 存放时间戳的列名。可选,默认值为属性名。
name The name of a JavaBeans style property of Java type Date or Timestamp of the persistent class.
access Hibernate用于访问属性的策略。可选,默认为property。
unsaved-value A version property which indicates than instance is newly instantiated, and unsaved. This distinguishes it from detached instances that were saved or loaded in a previous session. The default value of undefined indicates that Hibernate uses the identifier property value.
source Whether Hibernate retrieves the timestamp from the database or the current JVM. Database-based timestamps incur an overhead because Hibernate needs to query the database each time to determine the incremental next value. However, database-derived timestamps are safer to use in a clustered environment. Not all database dialects are known to support the retrieval of the database’s current timestamp. Others may also be unsafe for locking, because of lack of precision.
generated 是否由数据库内部自动生成时间戳的值。可选,默认值是never。

5.2 悲观锁 Pessimistic

通常情况下,你只需要指明一个JDBC的隔离等级(lsolation level),把锁的问题交给数据库处理就行了。如果你确实需要获得独占锁(exclusive locks)或是在事务开始时重新得到(re-obtain)锁,Hibernate会提供给你需要的方法。

Hibernate总是使用数据库的锁机制,从不在内存中给对象上锁

5.2.1 LockMode类

LockMode类指定了Hibernate能取得的各种锁级别。

LockMode.WRITE 在Hibernate执行update或insert行时自动获取
LockMode.UPGRADE 在用户显式地使用 SELECT ... FOR UPDATE 语句时(仅当数据库支持该语法)
LockMode.UPGRADE_NOWAIT 在用户对Oracle数据库显式地使用SELECT ... FOR UPDATE NOWAIT 语句时.
LockMode.READ 在Hibernate以Repeatable Read或是Serializable隔离级别读取时自动获取,亦可由用户显式地获取
LockMode.NONE 不启用锁。在事物结束后,所有的对象将会切换到这一状态。通过调用update()或saveOrUpdate()方法与会话(session)关联的对象亦以这种锁模式启动。

 
 
附注:有关锁的其他参考文章

Categories
木有技术

LEOPOLD FC660m Mac OS X Karabiner键位修改文件(private.xml)

很惭愧,就做了一点微小的工作,谢谢大家。
——你懂得

先后尝试minila和fc660m,感觉还是喜欢大空格和大右Shift,而且白色更好看哈哈哈!
660m对OS X的兼容性不如minila,不过既然有了Karabiner,基本就没问题了。
主要修改了空格键右侧的两个按键,正确对应到command和option了,
右上角的Insert减默认是映射到了Help按键,我本来是想改成Insert的,结果似乎改不好(不知道有没有高手说下为啥),只能作罢改成~`这个按钮了(就是原本在1字左边那个),这样Chrome底下切换窗口也方便了。
屏幕快照 2016-06-04 下午4.23.04
文件下载:http://pan.baidu.com/s/1mhFr3FM

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
不学无术

未填之坑:Java包、类的加载机制(实作)

我是一个坑。

Categories
不学无术

Java包、类的加载机制(理论)

本人Java菜鸡一枚,如果下面的内容有误,请各位拼命留言,我秒秒钟改!

做项目的同时努力补全知识点或许是当务之急?

问题

此面试问题暴露了我对Java内部原理的无知,哈哈,赶紧学。题外话:换做面C++的话,我估计面试官会用一个多继承里头的问题来考。

有a.jar和b.jar,里面都有某个类MyClass且他们的包名完全相同,但是有不同的foo()方法,在一个项目中同时引入这两个jar会出现什么奇妙的事情呢?

这个问题的答案是,要根据类加载器的加载顺序来决定我们构建MyClass对象的时候究竟会先找到哪个jar里头的文件,a.jar/b.jar哪个运气好拔得头筹了,剩下的就被忽视了。
如果我们构建了不同的类加载器对象的话,可以通过自定义的类加载器来同时加载这两个长得看起来一模一样的类,并且可以各自调用其方法属性什么的。
更详尽的回答可以看这里:http://stackoverflow.com/questions/6879652/possible-to-use-two-java-classes-with-same-name-and-same-package

包(Package)、编译单元

C/C++里面用#include来表示引用的类(文件),并且用过C++的盆友肯定知道namespace 这个关键字可以用来隔离名字相同东西,最常见的莫过于using namespace std; 了。以上这些与所谓“访问权限控制”有关(详见《Java编程思想》第6章)。
而Java以及诸如Python这种语言采用包(package)作为库单元,一个包内包含一组类,比如ArrayList在Java标准库中被放置在java.util包(名字空间)下。包的作用有点像地址,在寻找一个类的时候就像快递分拣一样,根据包的名字能找到最终的目标,一个不恰当的例子就是,package 浙江.杭州.西湖 底下可能会有 同仁医院 这个类,而 package 福建.福州.鼓楼(这个例子不恰当,因为按照Java的原意,包名应该是“从后往前”写的,如cn.com.ibm) 底下也可以有 同仁医院 这个类,在Java代码中使用类的内容的时候,Java虚拟机就得根据包的名称来确定你要引用的到底是哪个医院类了。
而所谓编译单元,大多是指Java源代码文件。一个编译单元的后缀就是.java,文件里只能有一个public的类且该类的名称与文件名相同,否则就会导致编译错误。Java解释器的运行过程大致是:找出环境变量CLASSPATH(包含一个或多个目录)用作查找.class文件的根目录。从根目录开始,解释器获取包的名称并将每个句点替换为反斜杠\,比如 浙江.杭州.西湖 就变成了目录 浙江\杭州\西湖(根据操作系统可能略有不同,变为正斜杠/也是有可能的)。随后解释器就在这么个目录中查找对应你给的类名称的.class文件,比如上面的 同仁医院.class

编译执行流程

估计大家都知道,Java中的类可以被动态加载到Java虚拟机中执行。下面这张图(推荐https://www.processon.com/,在线画简单流程图的好东西)可以大致说明:
Java Classloader
Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成java.lang.Class 类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance() 方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
基本上所有的类加载器都是 java.lang.ClassLoader 类的一个实例。上图可以看出一点,就是通过不同的类加载器,可以加载出不同的Class类的实例。

java.lang.ClassLoader

之前说过类加载器是干啥使的,除了加载类以外,其实诸如图像文件什么的资源也是它加载的,不过这里暂且不谈。
java.lang.ClassLoader其实是一个抽象类,有几个比较重要的方法:

  • getParent() 用于返回该类加载器的父类加载器;这个方法与经常能看到的“双亲委派”这个关键词有关:)
  • loadClass(String name) 加载名称为name的类,返回java.lang.Class实例。
    该方法有三步:
    1.调用findLoadedClass方法看看是不是已经加载过这个类了;
    2.如果没有,在父类加载器上调用loadClass方法。如果父类加载器是null(啊..到底了…),则用虚拟机的内置类加载器(下面讲到的引导类加载器);
    3.还是不行,调用findClass方法查找类
  • findClass(String name) 查找名称为name的类,返回java.lang.Class实例
  • findLoadedClass(String name) 查找一个名为name的已经加载过的类,返回java.lang.Class实例
  • defineClass(String name, byte[] b, int off, int len) 把字节数组b中的内容转换为Java类,返回java.lang.Class实例,这是个final方法
  • resolveClass(Class<?> c) 链接指定的Java类

注意:里面的参数name都是二进制名称。
系统提供的类加载器主要有三类:

  • Bootstrap class loader: 引导类加载器,用原生代码写的,用途就是bootstrap Java的核心库。
  • Extensions class loader: 扩展类加载器,用来加载扩展库。
  • System class loader: 系统类加载器,根据Java应用类的路径(CLASSPATH)加载Java类,一般我们写的东西就是它来加载的。

除了写好的类加载器外,我们可以自己继承java.lang.ClassLoader来实现自己的类加载器,满足一些奇奇怪怪的需求,比如问题1…

getParent()——找“爹”方法

在参考文献中是这么写的“对于开发人员编写的类加载器来说,其父类加载器是加载此类加载器 Java 类的类加载器”,是不是很拗口?比如我们自己继承了个ClassLoader类的话(假设他叫MyClassLoader),一般会在这个方法里面返回System class loader,为啥呢?因为我们自己写的MyClassLoader也是个类(先有鸡还是先有蛋,哈哈),也需要一个加载器把MyClassLoader加载了,我们才能用它去加载别的东西。
有了这个方法,就可以在加载类的时候有一个向上追溯的找类过程,这个感觉就有点像JavaScript里面的XX链(原型链、作用域链……哦,扯远了)。类加载器之间可以形成树形结构,比如下面这样子(图版权归IBM所有):

类加载器的树状结构

一路都可以getParent()上去,有些JDK可能输出不到最初的那个引导类加载器(AppClassLoader的getParent()返回了null)。

类加载器的代理模式——”我”是”我”?

Java虚拟机怎么判断两个类相同呢?通过包名+类名?不是的,还得看加载这个类的类加载器是不是一样。本文的第一张图可以看出,相同的字节码,被不同的类加载器加载之后,得到的Class类实例是不同的。
不同的类加载器使得包、类名称完全相同的两个类可以并存在Java虚拟机中,且不同加载器加载的类之间是不兼容的。代理模式的设计动机是保证Java核心库的类型安全。我们知道java.lang.Object类是”万物的基础“,如过这个东西能够用自己的类加载器来先加载的话,那就可能存在多个版本的java.lang.Object类并且他们还互不兼容,这就懵逼了。

加载类的过程

首先明确一点,由于类的加载是自顶向下尝试的,真正完成类加载工作的加载器和启动加载过程的加载器有可能不是同一个。
defineClass方法的作用是”真正的“完成类的加载过程(你看它的传入参数,字节数组什么的,多么朴实),执行这个方法的加载器被称作定义加载器;启动类的加载过程是调用loadClass方法实现的(它只要给个名字就行),执行这个方法的加载器叫做初始加载器,它要找不到类的话,就抛出个ClassNotFoundException异常。成功加载一个类后,会把得到的Class类实例缓存起来,下次请求时就不会重复加载了,即对于一个类(全名表示)来说,loadClass方法不会在一个classLoader里头重复调用。
 
额,如果有空的话,下一篇会实作一个自定义的ClassLoader。

参考文献

  • 彻底搞懂Java Classloader: http://weli.iteye.com/blog/1682625
  • 深入探讨Java类加载器:https://www.ibm.com/developerworks/cn/java/j-lo-classloader/
  • 《Java编程思想(第四版)》
Categories
生活琐碎

全站HTTPS启用

由于众所周知的原因,本站点已全站启用HTTPS协议。

Categories
木有技术

Apache服务器配置WoSign(沃通)SSL证书

参考文献
使用沃通签了一份SSL证书,其实配置的过程挺简单的,看参考文献基本能搞定。需要注意的是,本教程只开启了HTTPS站点的访问,并不强制跳转HTTP到HTTPS
我的服务器是Ubuntu+Apache,按照apt-get安装的apache2应该在这个/etc/apache2 目录下
可以看到目录大概是这个结构:

total 80
-rw-r--r-- 1 root root 7114 Dec 31 05:57 apache2.conf
drwxr-xr-x 2 root root 4096 Dec 31 05:31 conf-available
drwxr-xr-x 2 root root 4096 Dec 31 05:31 conf-enabled
-rw-r--r-- 1 root root 1782 May 20 2015 envvars
-rw-r--r-- 1 root root 31063 May 20 2015 magic
drwxr-xr-x 2 root root 12288 Apr 28 07:03 mods-available
drwxr-xr-x 2 root root 4096 May 24 11:17 mods-enabled
-rw-r--r-- 1 root root 320 May 20 2015 ports.conf
drwxr-xr-x 2 root root 4096 Dec 31 05:56 sites-available
drwxr-xr-x 2 root root 4096 May 24 11:27 sites-enabled

其中sites-available目录下应该有个默认的https配置的,我这边就有个default-ssl.conf ,然后参考本文最开始提供的参考文献链接,编辑这个文件,做这么几步:

  1. 配置公钥:修改字段SSLCertificateFile conf/ssl/test.wosign.com.crt ,SSLCertificateFile字段后面的文件地址是你存储的公钥文件(以我的站为例,wosign发给我的压缩包里有for Apache.zip,压缩包里面就有个 _boweihe.me.crt);
  2. 配置私钥:修改字段SSLCertificateKeyFile conf/ssl/test.wosign.com.key 同理,自行修改私钥文件地址(私钥文件后缀是.key)
  3. 配置服务器证书链:找到以#SSLCertificateChainFile 开头的一行,删除行开头的#号注释,并修改字段对应值为你的证书链文件(1_root_bundle.crt)地址,如SSLCertificateChainFile conf/ssl/1_root_bundle.crt

然后需要启用这个HTTPS站点(配置文件的其他内容修改可以参考你自己本身已经启用的http站的,可能要注意的是webroot要保持一致什么的),启用的命令很简单

a2ensite default-ssl

后面的default-ssl是你刚才修改的default-ssl.conf文件的名字,如果你起了另外的名字,那相应修改。命令执行成功后,会提示

To activate the new configuration, you need to run:
  service apache2 reload

照着做就行了,重新加载下apache服务:

service apache2 reload

至此你的站就可以用https协议访问了,如图
https
可能遇到的问题是,站点里部分资源是非HTTPS协议的,这时候图表就不能变绿了。比如我使用了七牛云的静态文件缓存,还得去人工提交一下申请,开通HTTPS静态资源访问的协议。

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

 

Categories
不学无术

LeetCode 119. Pascal's Triangle II

题目

Given an index k, return the kth row of the Pascal’s triangle.
For example, given k = 3,
Return [1,3,3,1].
Note:
Could you optimize your algorithm to use only O(k) extra space?

思路

交替使用两个数组即可。

代码

class Solution {
public:
    vector<int> getRow(int rowIndex) {
        rowIndex++;
        if(rowIndex < 1){
            return vector<int>();
        }
        vector<int> temp0(rowIndex, 0);
        vector<int> temp1(rowIndex, 0);
        vector<int>* pCurrRow = &temp0;
        vector<int>* pLastRow = &temp1;
        for(int i=0; i<rowIndex; i++){
            (*pCurrRow)[0] = 1;
            for(int col=1; col<=i; col++){
                (*pCurrRow)[col] = (*pLastRow)[col-1] + (*pLastRow)[col];
            }
            vector<int>* pTemp = pLastRow;
            pLastRow = pCurrRow;
            pCurrRow = pTemp;
        }
        return (*pLastRow);
    }
};

 

Categories
不学无术

LeetCode 38. Count and Say

题目

The count-and-say sequence is the sequence of integers beginning as follows:
1, 11, 21, 1211, 111221, ...
1 is read off as "one 1" or 11.
11 is read off as "two 1s" or 21.
21 is read off as "one 2, then one 1" or 1211.
Given an integer n, generate the nth sequence.
Note: The sequence of integers will be represented as a string.

思路

好几题没刷leetcode手痒痒,又没太多时间,就选了个Easy的题啦…这个题目确实挺简单的,统计一下前面是不是重复就行了。在句末加一个特殊符号可以避免多写几行代码的样子~

代码

class Solution {
public:
    string countAndSay(int n) {
        string nStr = "1$";
        string nextStr = "";
        while(n > 1){
            char lastChr = '$';
            int lastCount = 0;
            for(int i=0; i<nStr.size(); i++){
                if(nStr[i] == lastChr){
                    lastCount++;
                } else {
                    if(lastCount > 0){
                        string tempStr;
                        while(lastCount > 0){
                            tempStr += (lastCount%10 +'0');
                            lastCount /= 10;
                        }
                        reverse(tempStr.begin(), tempStr.end());
                        nextStr += tempStr;
                        nextStr += lastChr;
                    }
                    lastCount = 1;
                    lastChr = nStr[i];
                }
            }
            nStr = nextStr + "$";
            nextStr = "";
            n--;
        }
        return nStr.substr(0, nStr.size()-1);
    }
};