一:Service层接口设计
准备工作:新建三个包:service包、exception包、dto包,分别用来存放业务接口、自定义异常类、dto类。
1:定义接口
package org.myseckill.service; import java.util.List; import org.myseckill.dto.Exposer; import org.myseckill.dto.SeckillExecution; import org.myseckill.entity.Seckill; import org.myseckill.exception.RepeatKillException; import org.myseckill.exception.SeckillClosedException; import org.myseckill.exception.SeckillException; public interface SeckillService { //查询所有 List<Seckill> getSeckillList(); //根据ID查询 Seckill getById(long seckillId); //暴露秒杀网页地址 Exposer exportSeckillUrl(long seckillId); //md5用于与内部md5做比较,防止用于篡改url进行秒杀 SeckillExecution executeSeckill(long seckillId,long userPhone,String md5) throws SeckillException,RepeatKillException,SeckillClosedException; }
2:定义接口中用到的相关dto:
package org.myseckill.dto; //封装暴露信息 public class Exposer { //是否开启秒杀 private boolean exposed; private String md5; private long seckillId; private long now; private long start; private long end; public Exposer(boolean exposed, String md5, long seckillId) { super(); this.exposed = exposed; this.md5 = md5; this.seckillId = seckillId; } public Exposer(long now, long start, long end) { super(); this.now = now; this.start = start; this.end = end; } public Exposer(boolean exposed, long seckillId) { super(); this.exposed = exposed; this.seckillId = seckillId; } public boolean isExposed() { return exposed; } public void setExposed(boolean exposed) { this.exposed = exposed; } public String getMd5() { return md5; } public void setMd5(String md5) { this.md5 = md5; } public long getSeckillId() { return seckillId; } public void setSeckillId(long seckillId) { this.seckillId = seckillId; } public long getNow() { return now; } public void setNow(long now) { this.now = now; } public long getStart() { return start; } public void setStart(long start) { this.start = start; } public long getEnd() { return end; } public void setEnd(long end) { this.end = end; } }
package org.myseckill.dto; import org.myseckill.entity.SuccessKilled; //封装秒杀后信息 public class SeckillExecution { private long seckillId; private int state; private String stateInfo; private SuccessKilled successKilled; public long getSeckillId() { return seckillId; } public void setSeckillId(long seckillId) { this.seckillId = seckillId; } public int getState() { return state; } public void setState(int state) { this.state = state; } public String getStateInfo() { return stateInfo; } public void setStateInfo(String stateInfo) { this.stateInfo = stateInfo; } public SuccessKilled getSuccessKilled() { return successKilled; } public void setSuccessKilled(SuccessKilled successKilled) { this.successKilled = successKilled; } //秒杀成功的构造函数 public SeckillExecution(long seckillId, int state, String stateInfo, SuccessKilled successKilled) { super(); this.seckillId = seckillId; this.state = state; this.stateInfo = stateInfo; this.successKilled = successKilled; } //秒杀失败的构造函数 public SeckillExecution(long seckillId, int state, String stateInfo) { super(); this.seckillId = seckillId; this.state = state; this.stateInfo = stateInfo; } }
3:定义接口中用到的自定义异常
package org.myseckill.exception; //秒杀相关通用异常 public class SeckillException extends RuntimeException { public SeckillException() { super(); // TODO Auto-generated constructor stub } public SeckillException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public SeckillException(String message) { super(message); // TODO Auto-generated constructor stub } public SeckillException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } }
package org.myseckill.exception; //重复秒杀异常 public class RepeatKillException extends SeckillException { public RepeatKillException(String message, Throwable cause) { super(message, cause); } public RepeatKillException(String message) { super(message); } public RepeatKillException(Throwable cause) { super(cause); } }
package org.myseckill.exception; //秒杀已关闭异常 public class SeckillClosedException extends SeckillException { public SeckillClosedException() { super(); } public SeckillClosedException(String message, Throwable cause) { super(message, cause); } public SeckillClosedException(String message) { super(message); } public SeckillClosedException(Throwable cause) { super(cause); } }
二:Service层接口实现
1:在Service包下,新建一个Impl包,用于存放接口的实现类。
2:定义接口实现类SeckillServiceImpl
package org.myseckill.service.Impl; import java.util.Date; import java.util.List; import org.myseckill.dao.SeckillDao; import org.myseckill.dao.SuccessKilledDao; import org.myseckill.dto.Exposer; import org.myseckill.dto.SeckillExecution; import org.myseckill.entity.Seckill; import org.myseckill.entity.SuccessKilled; import org.myseckill.exception.RepeatKillException; import org.myseckill.exception.SeckillClosedException; import org.myseckill.exception.SeckillException; import org.myseckill.service.SeckillService; import org.myseckill.enums.SeckillStateEnum; import org.myseckill.exception.RepeatKillException; import org.myseckill.exception.SeckillClosedException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.DigestUtils; public class SeckillServiceImpl implements SeckillService { //使用slf4j日志 private Logger logger=LoggerFactory.getLogger(this.getClass()); private SeckillDao seckillDao; private SuccessKilledDao successKilledDao; //用于加密的混淆字符串,随机串 private final String slat="asfdfadsf45qa@$E#iudkgj15=sdf=daf5"; //本例只用了4跳记录,实际项目中可以再定义一个selectall的SQL语句查询所有 public List<Seckill> getSeckillList() { return seckillDao.queryAll(0, 4); } @Override public Seckill getById(long seckillId) { return seckillDao.queryById(seckillId); } @Override public Exposer exportSeckillUrl(long seckillId) { Seckill seckill=seckillDao.queryById(seckillId); if(seckill==null){//没有这个产品的秒杀记录,不进行暴露 return new Exposer(false, seckillId); } Date now=new Date(); Date start=seckill.getStartTime(); Date end=seckill.getEndTime(); //若时间非法,不秒杀 if(now.getTime()<start.getTime() || now.getTime()>end.getTime()){ return new Exposer(false, seckillId, now.getTime(), start.getTime(), end.getTime()); } //否则,进行秒杀网址暴露 String md5=getMD5(seckillId); return new Exposer(true, md5, seckillId); } //用md5加密 private String getMD5(long seckillId){ String base=seckillId+"/"+slat; String md5=DigestUtils.md5DigestAsHex(base.getBytes()); return md5; } @Override public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillException, SeckillException { if(md5==null||!md5.equals(getMD5(seckillId))){ throw new SeckillException("seckill data rewrite"); } //执行秒杀逻辑:减库存+记录购买行为 try { //记录购买行为 int insertCount = successKilledDao.insertSuccessKilled(seckillId, userPhone); if(insertCount <= 0 ){ //重复秒杀 throw new RepeatKillException("seckill repeated"); }else{ //减库存,热点商品竞争(高并发点) int updateCount = seckillDao.reduceNumber(seckillId, new Date()); if(updateCount<=0){ //没有更新到记录,秒杀结束,rollback throw new SeckillClosedException("seckill is closed"); }else{ //秒杀成功,commit SuccessKilled successKilled = successKilledDao.queryByIdWithSeckill(seckillId, userPhone); return new SeckillExecution(seckillId,SeckillStateEnum.SUCCESS,successKilled); } } } catch(SeckillClosedException e1){ throw e1; } catch(RepeatKillException e2){ throw e2; }catch (Exception e) { logger.error(e.getMessage(),e); //所有异常转化为运行期异常 throw new SeckillException("seckill inner error:"+e.getMessage()); } } }
3:把状态与状态信息常量,封装成枚举常量
在org.myseckill包下新建enums包,包中新建一个枚举类SeckillStateEnum:
package org.myseckill.enums; //在实际开发中,全局常用的常量们用枚举常量存储 public enum SeckillStateEnum { //定义一系列枚举常量 SUCCESS(1,"秒杀成功"), END(0,"秒杀结束"), REPEAT_KILL(-1,"重复秒杀"), INNER_ERROR(-2,"系统异常"), DATA_REWRITE(-3,"数据篡改"); private int state; private String stateInfo; SeckillStateEnum(int state, String stateInfo) { this.state = state; this.stateInfo = stateInfo; } public int getState() { return state; } public void setState(int state) { this.state = state; } public String getStateInfo() { return stateInfo; } public void setStateInfo(String stateInfo) { this.stateInfo = stateInfo; } public static SeckillStateEnum stateOf(int index) { //迭代枚举常量,返回state值等于index的常量 for (SeckillStateEnum stateEnum : values()) { if (stateEnum.getState() == index) { return stateEnum; } } return null; } }
4:修改SeckillExecution类的构造函数:
//秒杀成功的构造函数 public SeckillExecution(long seckillId, SeckillStateEnum success,SuccessKilled successKilled) { super(); this.seckillId = seckillId; this.state = success.getState(); this.stateInfo = success.getStateInfo(); this.successKilled = successKilled; } //秒杀失败的构造函数 public SeckillExecution(long seckillId, SeckillStateEnum success) { super(); this.seckillId = seckillId; this.state = success.getState(); this.stateInfo = success.getStateInfo(); }
三:Spring托管Service层的类
SpringIOC中,注入主要分为两种场景:
1:第三方类库提供的类,如:datasource等。使用XML中ref来注入;
2:自己定义的类,则在代码中,使用注解来注入;
1:在resource.spring包下,新建一个spring-service.xml,用于负责托管service层bean以及进行事务管理。
2:托管bean:
如果 Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用上述注解对分层中的类进行注释。
@Service用于标注业务层组件
@Controller用于标注控制层组件(如struts中的action)
@Repository用于标注数据访问组件,即DAO组件
@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
component-scan标签默认情况下自动扫描指定路径下的包(含所有子包),将带有@Component、@Repository、@Service、@Controller标签的类自动注册到spring容器。
托管bean分两步:
首先在配置文件开启包扫描:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 包扫描,找到包下使用注解来依赖注入的属性,进行托管 --> <context:component-scan base-package="org.myseckill.service"/> </beans>
然后,在bean类加注解:为类加组件注解,类中需要注入属性的加注入注解:
@Service public class SeckillServiceImpl implements SeckillService { @Autowired private SeckillDao seckillDao; @Autowired private SuccessKilledDao successKilledDao; ...... }
3:声明式事务管理
事务管理主要有两种:
1:XML配置,使用tx:advice标签配置aop进行事务管理。好处是一次配置处处生效(可以自己配置切入点),坏处是,阅读代码时不知道哪个方法是被事务管理的,需要频繁查阅xml。
2:@Transactional注解:使用该注解注释的方法,表示把该方法交给spring进行事务管理。推荐使用这个方法,一是简便,二是在阅读代码时清晰地看到该方法被事务托管。
事务托管:
首先,在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:context="http://www.springframework.org/schema/context" 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/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd"> <!-- 扫描service包下所有的类型 --> <context:component-scan base-package="org.myseckill.service"></context:component-scan> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入数据库连接池 --> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置基于注解的声明式事务 默认使用注解来管理事务行为--> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
然后,在bean中,需要事务托管的方法前面,加@Transactional注解:
@Transactional
public SeckillExecution executeSeckill(long seckillId, long userPhone, String md5) throws SeckillException, RepeatKillExeception, SeckillException{}