WebService就是一种跨编程语言和跨操作系统平台的远程调用技术。具体的实现不是本文探讨重点,在此就不多说了。
先说说我的场景:我们的服务是一种同时对外提供webservice远程调用,以及普通的http请求调用的服务,整个服务是基于springboot框架和cxf建立的。同时,为了简化开发,我定义了一个注解,当需要发布为webservice的类添加注解并填写发布路径后,配置类初始化时就会根据反射原理获取发布的相关信息,完成自动发布。
由于一些操作涉及到事务,所以我很自然的直接使用spring的事务注解,也就是@Transactional来实现。但是,当我添加这行注解之后,重启服务就发现webservice发布时开始报错。
先看下主体代码:
/**
* 测试案例类:这里采用@AutoPublish注解实现自动发布
* 不添加@Transactional时并不会报错
* @author GrainRain
* @date 2020/05/10 14:41
**/
@WebService(serviceName = "TestService",targetNamespace = "http://service.whitetown.com",
endpointInterface = "cn.whitetown.webdemo.service.Testservice")
@AutoPublish("/tess")
@Service
public class TestServiceImpl implements Testservice {
@Override
@Transactional
public String sendMessage() {
/**
* 服务代码
*/
return "success";
}
}
以下是自动发布的配置方法
@Override
public void run(ApplicationArguments applicationArguments) throws Exception {
String[] beanNames = applicationContext.getBeanNamesForAnnotation(AutoPublish.class);
for(String beanName:beanNames){
//这行代码是我在debug时添加的,具体后续说明
System.out.println("bean:"+applicationContext.getType(beanName));
String addr = applicationContext.getType(beanName).getAnnotation(AutoPublish.class).value();
Endpoint endpoint = new EndpointImpl(bus,applicationContext.getBean(beanName));
endpoint.publish(addr);
System.out.println("服务发布成功,地址为:"+addr);
}
}
报错的内容如下:
Caused by: java.lang.NullPointerException: null
at cn.whitetown.webdemo.config.AutoPublishConfig.run(AutoPublishConfig.java:37) ~[classes/:na]
以上报错对应 String addr = applicationContext.getType(beanName).getAnnotation(AutoPublish.class).value(); 这行代码。
因此,为了判断哪里出了问题,我添加了上面注释标注的那行代码,看看到底有没有获取到容器中的bean对象。结果,还真的获取到了:
bean: class cn.whitetown.webdemo.service.impl.TestServiceImpl$$EnhancerBySpringCGLIB$$f3d01031
细心的读者应该找到报错的原因了。因为添加了事务注解,spring在初始化时会为我们创建一个代理类,注册的bean对象也是代理类对象,因而我获取到的bean对象自然是这个代理类对象,而不再是原来自己写的那个类创建的对象了。自然而然的,代理类上并没有我们添加的注解,也就不可能获取得到相应的值。
这也就是出现空指针异常的原因。看来还是对框架底层理解不够透彻导致的,这也体现出读源码,理解底层原理的重要性。
那么,这样的情况下事务如何实现呢。
其实知道了原因那么解决也很简单,必定有那么多种事务实现方式呢。我这里直接使用编程式事务实现,代码如下:
@WebService(serviceName = "TestService",targetNamespace = "http://service.whitetown.com",
endpointInterface = "cn.whitetown.webdemo.service.Testservice")
@AutoPublish("/tess")
@Service
public class TestServiceImpl implements Testservice {
@Autowired
private PlatformTransactionManager platformTransactionManager;
@Override
public String sendMessage() {
DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
//start transaction
TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
/**
* 一个事务内的代码
*/
//commit
platformTransactionManager.commit(transactionStatus);
return "success";
}
}