实习小记录,做个技术的简单总结。
关于日志文件
intro:
SLF4J不同于其他日志类库,与其它有很大的不同。
SLF4J(Simple logging Facade for Java)不是一个真正的日志实现,而是一个抽象层( abstraction layer),它允许你在后台使用任意一个日志类库。
如果是在编写供内外部都可以使用的API或者通用类库,使用你类库的客户端不必一定使用你选择的日志类库。如果一个项目已经使用了log4j,而你加载了一个类库,比方说 Apache Active MQ——它依赖于于另外一个日志类库logback(Logback是由log4j创始人Ceki Gülcü设计的又一个开源日志组件。logback当前分成三个模块:logback-core,logback- classic和logback-access。),那么你就需要把它也加载进去。但如果Apache Active MQ使用了SLF4J,你可以继续使用你的日志类库而无语忍受加载和维护一个新的日志框架的麻烦。
总的来说,SLF4J使你的代码独立于任意一个特定的日志API,这是一个对于开发API的开发者很好的思想。
main content:日志选择和业务分离
implement:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.0.13</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.0.13</version>
</dependency>
[WebAjaxController.java]
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Controller
public class WebAjaxController extends AbsAjaxController {
private static final Logger LOG = LoggerFactory.getLogger("STAT_LOG");
public void method(){
...
LOG.info("[opt:method,uid:{},from:{},type:{}]", new Object[] {
uid, fromLog, typeLog });
}
...
}
[LogBack.xml]
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="stat_appender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/home/logs/statistics/lepro3.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>/home/logs/mylog.log.%d{yyyy-MM-dd}</fileNamePattern>
</rollingPolicy>
<append>true</append>
<encoder>
<pattern>%d %t %-5level %c{0}.%M:%L -%m%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
</appender>
<logger name="STAT_LOG">
<level value="INFO"/>
<appender-ref ref="stat_appender" />
</logger>
<root level="INFO">
</root>
</configuration>
[web.xml]
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" id="WebApp">
<context-param>
<param-name>logbackConfigLocation</param-name>
<param-value>classpath:logback.xml</param-value>
</context-param>
</web-app>
note:Logback的核心对象:Logger、Appender、Layout
Logback主要建立于Logger、Appender 和 Layout 这三个类之上。
Logger:日志的记录器,把它关联到应用的对应的context上后,主要用于存放日志对象,也可以定义日志类型、级别。Logger对象一般多定义为静态常量。
Appender:用于指定日志输出的目的地,目的地可以是控制台、文件、远程套接字服务器、 MySQL、 PostreSQL、Oracle和其他数据库、 JMS和远程UNIX Syslog守护进程等。
Layout:负责把事件转换成字符串,格式化的日志信息的输出。具体的Layout通配符,可以直接查看帮助文档。
Level 有效级别。Logger可以被分配级别。级别包括:TRACE、DEBUG、INFO、WARN和ERROR,定义于ch.qos.logback.classic.Level类。程序会打印高于或等于所设置级别的日志,设置的日志等级越高,打印出来的日志就越少。如果设置级别为INFO,则优先级高于等于INFO级别(如:INFO、 WARN、ERROR)的日志信息将可以被输出,小于该级别的如DEBUG将不会被输出。为确保所有logger都能够最终继承一个级别,根logger总是有级别,默认情况下,这个级别是DEBUG。
[note] 以上参考
http://www.cnblogs.com/yongze103/archive/2012/05/05/2484753.html,感谢。
关于spring scheme定时任务
note: spring3.0 自带的task
[applicationContext-task.xml]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:task="http://www.springframework.org/schema/task" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.1.xsd">
<task:scheduler id="myScheduler" pool-size="10" />
<task:annotation-driven scheduler="myScheduler" />
</beans>
[web.xml]
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" id="WebApp">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
...
classpath:applicationContext-service.xml
classpath:applicationContext-dao.xml
classpath:applicationContext-task.xml
...
</param-value>
</context-param>
</web-app>
[TipEmailTask.java]
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.concurrent.atomic.AtomicBoolean;
@Component("tipEmailTask")
@Lazy(false)
public class TipEmailTask {
private volatile AtomicBoolean starting = new AtomicBoolean(false);
@Autowired
private UserService userService;
@Scheduled(cron = "0 0 */2 * * ?")
public void shcedule() {
try {
execute();
} catch (Exception e) {
e.printStackTrace();
}
}
private void execute() {
if (starting.compareAndSet(false, true)) {
try {
boolean result = userService.sendTipMail();
} catch (Exception e) {
} finally {
starting.set(false);
}
}
}
note @Scheduled(cron = “0 0 /2 * ?”) 每两小时触发一次,实际是0,2…22点触发。
资料包 cron=({秒} {分} {时} {日期(具体哪天)} {月} {星期})
- 秒:必填项,允许的值范围是0-59,支持的特殊符号包括*/n,表示特定的每n秒可以整除的才会触发任务。
- 分:必填项,允许的值范围是0-59,支持的特殊符号和秒一样,含义类推。
- 时:必填项,允许的值范围是0-23,支持的特殊符号和秒一样,含义类推。
- 日期:必填项,允许的值范围是1-31,支持的特殊符号相比秒多了?,表示与{星期}互斥,即意味着若明确指定{星期}触发,则表示{日期}无意义,以免引起冲突和混乱。
- 月:必填项,允许的值范围是1-12(JAN-DEC),支持的特殊符号与秒一样,含义类推
- 星期:必填项,允许值范围是1~7(SUN-SAT),1代表星期天(一星期的第一天),以此类推,7代表星期六,支持的符号相比秒多了?,表达的含义是与{日期}互斥,即意味着若明确指定{日期}触发,则表示{星期}无意义。
参考文档:https://spring.io/guides/gs/scheduling-tasks/#initial,感谢。
关于xmemcache缓存
[applicationContext-service-lmswitch.xml]
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="memcachedClientBuilder" class="net.rubyeye.xmemcached.XMemcachedClientBuilder" >
<constructor-arg>
<list>
<bean class="java.net.InetSocketAddress">
<constructor-arg value="缓存服务器ip" />
<constructor-arg value="11211" />
</bean>
</list>
</constructor-arg>
<property name="connectionPoolSize" value="5" />
<property name="commandFactory">
<bean class="net.rubyeye.xmemcached.command.TextCommandFactory" />
</property>
<property name="sessionLocator">
<bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator" />
</property>
<property name="transcoder">
<bean class="net.rubyeye.xmemcached.transcoders.SerializingTranscoder" />
</property>
</bean>
<bean id="memcachedClient" factory-bean="memcachedClientBuilder"
factory-method="build" destroy-method ="shutdown" />
</beans>
[web.xml]
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" id="WebApp">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
...
classpath:applicationContext-service-lmswitch.xml
...
</param-value>
</context-param>
</web-app>
[CacheService.java]
import net.rubyeye.xmemcached.Counter;
public interface CacheService {
/**
* 放入缓存
*/
public Object put(String cacheName, String key, Object value);
/**
* 获得缓存
*/
public Object get(String cacheName, String key);
/**
* 删除缓存
*/
public void delete(String cacheName, String key);
/**
* 删除缓存
*/
public void delete(String cacheName);
public Counter getCounter(String counterName, long defaultVal);
}
[MemCacheServiceImpl.java]
import java.util.concurrent.TimeoutException;
import net.rubyeye.xmemcached.Counter;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.exception.MemcachedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.netease.mail.activity.service.complex.CacheService;
@Component("memCacheService")
public class MemCacheServiceImpl implements CacheService {
@Autowired
private MemcachedClient memcachedClient;
@Override
public Object put(String cacheName, String key, Object value) {
long startTime = System.currentTimeMillis();
boolean result = false;
result = memcachedClient.set(getComposedKey(cacheName, key), 0,value);//0代表30天,含义永久;
return null;
}
@Override
public Object get(String cacheName, String key) {
long startTime = System.currentTimeMillis();
Object value = null;
value = memcachedClient.get(getComposedKey(cacheName, key));
return value == null ? null : value.toString();
}
@Override
public void delete(String cacheName, String key) {}
@Override
public void delete(String cacheName) {}
private String getComposedKey(String cacheName, String key) {
StringBuilder sb = new StringBuilder();
sb.append(cacheName).append("_").append(key);
return sb.toString();
}
@Override
public Counter getCounter(String counterName, long defaultVal) {
return memcachedClient.getCounter(counterName, defaultVal);
}
}
note: 以上是缓存处理的service实现,通过@注入,自动扫描。为了表达清楚含义,去掉了trycatch,应用时自己添加。
下面配上缓存的实际应用,为获取获奖者名单的应用方法。
method
private static final Gson gson = new Gson();
@RequestMapping("/ajax/getWinners.do")
@ResponseBody
public void getWinners(HttpServletRequest request,
HttpServletResponse response) throws IOException {
Object winnersObj = memcachedService.get(MemcacheConst.CACHE_NAME,
MemcacheConst.CACHE_WINNERS);
Map<String, Object> result = new HashMap<String, Object>();
if (null == winnersObj) {//不存在从数据库中取,并放入缓存;
List<WinnerVo> winners = winnerService.getWinners();
if (null == winners) {
winners = new ArrayList<WinnerVo>();
}
result.put("winners", winners);
String winnersJson = gson.toJson(winners);
memcachedService.put(MemcacheConst.CACHE_NAME,
MemcacheConst.CACHE_WINNERS, winnersJson);
writeJsonP(request, response,
initAjaxResult(RetCode.SUCCESS.code, result));
return;
} else {//如果存在的话,返回即可;
String winnerJson = (String) winnersObj;
List<WinnerVo> winners = gson.fromJson(winnerJson,new TypeToken<List<WinnerVo>>() {}.getType());
if (null == winners) {
winners = new ArrayList<WinnerVo>();
}
result.put("winners", winners);
writeJsonP(request, response,
initAjaxResult(RetCode.SUCCESS.code, result));
return;
}
}
其他
代码写下来,感觉controller中代码过重,应放入业务层service中。同时if-else过多嵌套。
另外,关于测试的问题,尤其是线上测试,开关注意把控,合理设计。因为这个问题重新编译了两次。
关于发布流程,每一次更新(哪怕一点点更新)必须先在测试环境编译部署,最后再在线上编译部署。
同时,关于nginx的部署、使用、原理还不了解。只是在用人家部署好的,这次使用两台业务服务器一台缓存服务器,所以接下来也需要学习一下nginx,亲自试一试。
以上是这十天阶段的开发感悟。