类和方法注解
@ResponseBody
在代码里只需返回java对象,@ResponseBody注解会自动转为json字符串,同时设置content-type为application/json。
@RestController和@Controller的区别
@Controller
:修饰class,用来创建处理http请求的对象
@RestController
:Spring4之后加入的注解,原来在@Controller
中返回json需要@ResponseBody
来配合,如果直接用@RestController
替代@Controller
就不需要再配置@ResponseBody
,默认返回json格式。我们可以通过postman查看返回消息的content-type,发现加了@ResponseBody
后,确实从text/plain变为application/json 。
因此:
- 如果只是使用@RestController注解Controller,则Controller中的方法无法返回html页面,配置的视图解析器InternalResourceViewResolver不起作用,返回的内容就是return 里的内容。例如:本来应该到success.jsp页面的,则其显示success.
- 如果需要返回到指定页面,则需要用 @Controller配合视图解析器InternalResourceViewResolver才行。
- 如果需要返回JSON,XML或自定义mediaType内容到页面,则需要在对应的方法上加上@ResponseBody注解。
@Service+@Controller搭配使用
@Service 常用来标注一个业务模块,该模块以单件形式存在。
@Controller MVC里的控制器。
常用的一个实践是:rest请求在Controller里进行,具体业务则在service里实现,Controller负责将rest请求分发到service接口。下面是一个例子:
@Controller
@Path("/lee/v1/secu/auth")
public class RestAuthService extends AbstractROAService
{
...
@Autowired
private SecurityModuleItf smi_;
@GET
public String doGet(@QueryParam("oper") String oper, @QueryParam("roleNames") String roleNames, @QueryParam("roleIds") String roleIds)
{
try
{
if(!Strings.isNullOrEmpty(roleNames))
{
List<String> roleNameLst = Util.fromJson(roleNames, List.class);
return Util.genResponseJson(smi_.authByRoleNames(oper, roleNameLst), null, null) ;
}
...
}
catch(Exception e)
{
...
}
}
}
public interface SecurityModuleItf
{
...
boolean authByRoleNames(String oper, List<String> roleNames);
}
@Service
public class SecurityModule implements SecurityModuleItf
{
...
}
@RequestMapping注解
参数如下:
value: 指定请求的实际url地址, 比如 /action/info之类。
method: 指定请求的method类型, GET、POST、PUT、DELETE等
consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回
params: 指定request中必须包含某些参数值时,才让该方法处理
headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求
一个样例:
@RequestMapping(value = "/test", method = RequestMethod.GET, headers="Referer=http://xxx.com/")
public void testHeaders(@PathVariable String ownerId, @PathVariable String petId) {
// implementation omitted
}
注意
:也可以用GetMapping等简化的注解。
init和fini注解
init:@PostConstruct
fini:@PreDestroy
参数和成员注解
@PathVariable
@PathVaribale 获取url路径中的参数,例如获取
http://localhost:8080/hello/{id}
中的id,可以这样写:
@RequestMapping(value="/hello/{id}",method= RequestMethod.GET)
public String sayHello(@PathVariable("id") Integer id){
return "id:"+id;
}
@RequestParam
@RequestParam 获取url请求参数的值,例如获取
localhost:8080/hello?id=98
中的id值,可以这样写:
@RequestMapping(value="/hello",method= RequestMethod.GET)
public String sayHello(@RequestParam("id") Integer id){
return "id:"+id;
}
注意
:url里数组的写法:
/lee/comp_mgmt/v1/lib_desc?lib=x1&lib=x2
亦即:同名的key构成一个数组。
代码里这样写:
@GetMapping(value="/lib_desc")
public String showLibs(@RequestParam("lib") List<String> libNames) {
Map<String,String> res = compMgmtService.showLibs(libNames);
return SeDeUtil_Java.toJson(res);
}
@RequestBody
获得消息体中的json对象。下面例子使用@RequestBody从消息体中分离出SaleInput对象列表:
@PostMapping(value="/send_evt")
public String sendEvent(HttpServletRequest request, @RequestBody List<SaleInput> objs)
{
for (SaleInput o: objs)
{
evtFrmSvc.acceptEvent(o);
}
return "ok";
}
@Value注解
有$和#两种注解方式,$主要是配置文件里值的获取,格式为:
${ property : default_value }
#是bean属性的获取,格式为:
#{ obj.property }
如要在未给值时设定默认值,可以这样写:
#{ obj.property ?: default_value }
#{ obj.property ? obj.property : default_value }
上述两种写法的效果是一样的。
需要注意的是,如果是获取一个方法的值时,需要在前面增加@,比如
#{ @obj.getProperty() }
覆盖框架的spring bean定义
要覆盖的bean:
@Slf4j
public class HackDataSourceAliasMapper extends DataSourceAliasMapper {
public void init() {
try {
log.info("HackDataSourceAliasMapper.init()");
super.init();
}
catch (Exception e) {
log.warn("perish DataSourceAliasMapper failure");
}
}
}
实现BeanDefinitionRegistryPostProcessor接口,把老的bean移除,替换成新的bean:
@Component
@Slf4j
public class MyBeanRegPostProc implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
String beanName = "dataSourceAliasMapper";
log.info("begin to hack bean:{} success", beanName);
beanDefinitionRegistry.removeBeanDefinition(beanName);
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.rootBeanDefinition(HackDataSourceAliasMapper.class);
beanDefinitionBuilder.setInitMethodName("init");
beanDefinitionRegistry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
log.info("hack bean:{} success", beanName);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
log.info("postProcessBeanFactory: empty");
}
}
spring bean后处理的顺序
BeanDefinitionRegistryPostProcessor接口的postProcessBeanDefinitionRegistry方法先于
BeanDefinitionRegistryPostProcessor接口的postProcessBeanFactory方法先于
BeanFactoryPostProcessor接口的postProcessBeanFactory方法
与BeanDefinitionRegistryPostProcessor不同,BeanFactoryPostProcessor甚至可以修改bean实例!
调整bean的加载顺序
使用@DependsOnDatabaseInitialization可在dataSource初始化后立刻做一些事情(springboot2.5引入):
某些众所周知类型的bean(如JdbcOperations)将被排序,以便在数据库初始化之后对它们进行初始化。如果有一个直接使用DataSource的bean,请使用@DependsOnDatabaseInitialization注释它的类或@Bean方法,以确保它也在数据库初始化之后被初始化。
其它情况,若想调整两个bean间的关系,可用@DependsOn;当然,用autowired依赖也是可以的,且更自然。