看了看大牛们的各种补偿式事务框架,也就是tcc框架.然后自己尝试写下,实现了很简单功能,还有很多需要像大牛学习的地方...
1.入口方法:
@Aspect
@Component
public class Aopconfig implements ApplicationContextAware {
@Autowired
MyService myService;
private ApplicationContext applicationContext;
@Pointcut(value = "@annotation(com.example.testaop.config.Gao)")
public void gao() {
}
@Around(value = "gao()")
public Object test(ProceedingJoinPoint joinPoint) throws Throwable {
Object[] before = myService.before(joinPoint);
Object proceed = null;
String flag = null;
Object[] args = joinPoint.getArgs();
try {
proceed = joinPoint.proceed();
myService.todo(joinPoint, (String)before[0],(Class)before[2],args);
} catch (Exception e) {
myService.todo(joinPoint, (String)before[1],(Class)before[2],args);
throw new Throwable(e);
}
return proceed;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationUtil.getInstance().setApplication(applicationContext);
}
}
每个程序都要有他的入口,这里也是,我这里是使用spring的aop,然后切面是判断执行方法是否含有我的自定义注解;这里实现ApplicationContextAware 接口,获取到spring上下文环境,后面可以通过他来注入或提取bean;
2.bean的提取工具方法:
public class ApplicationUtil {
private final static ApplicationUtil app = new ApplicationUtil();
private ApplicationContext applicationContext;
public ApplicationContext getApplicationContext() {
return applicationContext;
}
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
public static ApplicationUtil getInstance() {
return app;
}
public ApplicationUtil setApplication(ApplicationContext applicationContext) {
app.setApplicationContext(applicationContext);
return app;
}
public Object getBean(Class aClass) {
return this.applicationContext.getBean(aClass);
}
public Object getBeanByInterface(Class aClass) {
AutowireCapableBeanFactory autowireCapableBeanFactory = this.applicationContext.getAutowireCapableBeanFactory();
return autowireCapableBeanFactory.getBean(aClass);
}
}
3.自定义注解:(作用域是在方法上面)
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = ElementType.METHOD)
public @interface Gao {
String confirm();
String cancel();
Class interface_();
}
4.简单定义了一个接口,两个方法一个是执行反射方法,一个是获取拦截方法的参数列表:
public interface MyService {
public void todo(ProceedingJoinPoint joinPoint, String flag,Class a,Object[] args)throws Throwable;
public Object[] before(ProceedingJoinPoint joinPoint);
}
5.实现类:
@Service
public class MyServiceImpl implements MyService {
@Override
public void todo(ProceedingJoinPoint joinPoint, String flag, Class aclass,Object[]args) throws Throwable {
if (aclass.isInterface()) {
Object beanByInterface = ApplicationUtil.getInstance().getBeanByInterface(aclass);
Method[] methods = aclass.getMethods();
for (Method m : methods) {
if (flag.equals(m.getName())) {
m.invoke(beanByInterface, args);
}
}
} else {
Method[] methods = aclass.getMethods();
for (Method m : methods) {
if (flag.equals(m.getName())) {
m.invoke(ApplicationUtil.getInstance().getBean(aclass), args);
}
}
}
}
@Override
public Object[] before(ProceedingJoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Gao gao = method.getAnnotation(Gao.class);
String succ = gao.confirm();
String fail = gao.cancel();
Class aClass = gao.interface_();
return new Object[]{succ, fail, aClass};
}
}
方法中判断注解传入的参数是一个类还是一个接口;如果是接口的话,直接注入一个实现.这时如果程序中接口有多个实现方法的话就需要@Primary注解.否则会出错(其实可以在注解上多一个实现类名字来精确的提取bean)
在测试的时候出现过反射参数位数错误问题.原因就是注解所在的方法参数数量与实现类的参数数量不相同;所以需要参数类型和数量等完全一样;
____________________________________________________________________________________________
到这里其实就结束了-.-哈哈,只是简单的实现了先执行try方法,如果正常执行confirm程序,错误执行cancel程序.其实中间还有很复杂的各种逻辑.比如confirm和cancel中也要实现新的事务,还有记录操作状态,本地补偿等等....
下面说下我集成到程序中:(springcloud项目)
1.首先将上面的代码打成jar包.
2.将jar包复制到项目中,然后引入maven(这里使用本地引入jar包)
com.gao.www
gao
system
${project.basedir}/src/main/resources/lib/aop-test.jar
3.解决扫描不到jar包中注解等问题:
因为springboot的@SpringBootApplication注解其实已经包含了@ComponentScan注解,不过它默认扫描的路径是自身项目的所有包;所以这里要将第三方的jar包路径加进来就可以了
@SpringBootApplication(scanBasePackages = {"com.example.testaop","com.example.client22"})
4.下面就是测试的方法:
@FeignClient("client1")
public interface MyFeignClient {
@GetMapping("/try")
public Integer tryy(@RequestParam("id") String id,@RequestParam("amount") double amount);
@GetMapping("/confirm")
public Integer confirm(@RequestParam("id") String id,@RequestParam("amount") double amount);
@GetMapping("/cancel")
public Integer cancel(@RequestParam("id") String id,@RequestParam("amount") double amount);
}
@Service
public class MysServiceImpl implements MysService {
@Autowired
JdbcTemplate jdbcTemplate;
@Resource
MyFeignClient myFeignClient;
@Override
public Integer confirm(String id, double amount, Integer count) {
int update = jdbcTemplate.update("update test2 set amount= amount + ?,frozen = frozen - ? where id = ?", amount, amount, id);
myFeignClient.confirm(id, amount);
return update;
}
@Override
public Integer cancel(String id, double amount, Integer count) {
int update = jdbcTemplate.update("update test2 set frozen = frozen - ? where id = ?", amount, id);
myFeignClient.cancel(id, amount);
return update;
}
}
@RestController
public class Web {
@Autowired
JdbcTemplate jdbcTemplate;
@Resource
MyFeignClient myFeignClient;
@Transactional
@Gao(cancel = "cancel", confirm = "confirm", interface_ = MysService.class)
@GetMapping("/try")
public Integer t(String id, double amount, @RequestParam(defaultValue = "1") Integer count) {
int update = jdbcTemplate.update("update test2 set frozen = ? where id = ?", amount, id);
Integer tryy = myFeignClient.tryy(id, amount);
int i = 100 / count;
return update + tryy;
}
}