一、为什么选择spring boot:极速开发的秘密
作为.net转java的开发者,您会发现spring boot与asp.net core在理念上惊人的相似,但spring boot的“约定优于配置”哲学将其发挥到极致。spring boot可以:
- 在30秒内启动一个可运行的rest api
- 通过自动配置减少80%的配置工作
- 提供生产就绪的功能(监控、健康检查、指标)
- 拥有全球最大的java生态支持
二、极速启动:三步创建第一个rest接口
2.1 项目初始化
使用spring initializr(start.spring.io)或ide内置工具创建项目,只需选择:
- spring web:restful支持
- spring data jpa:数据库操作(可选)
- validation:数据验证(可选)
2.2 基础代码示例
@restcontroller
@requestmapping("/api/v1/users")
public class usercontroller {
@getmapping("/{id}")
public responseentity<user> getuserbyid(@pathvariable long id) {
user user = userservice.findbyid(id);
return responseentity.ok(user);
}
@postmapping
@responsestatus(httpstatus.created)
public user createuser(@valid @requestbody userdto userdto) {
return userservice.create(userdto);
}
@putmapping("/{id}")
public user updateuser(@pathvariable long id,
@valid @requestbody userdto userdto) {
return userservice.update(id, userdto);
}
@deletemapping("/{id}")
@responsestatus(httpstatus.no_content)
public void deleteuser(@pathvariable long id) {
userservice.delete(id);
}
}
三、spring boot restful核心详解
3.1 控制器层最佳实践
3.1.1 restful资源设计原则
// 好的restful设计示例
@restcontroller
@requestmapping("/api/v1/orders")
public class ordercontroller {
// get /api/v1/orders - 获取所有订单
@getmapping
public list<order> getallorders(
@requestparam(defaultvalue = "0") int page,
@requestparam(defaultvalue = "20") int size) {
return orderservice.getorders(page, size);
}
// get /api/v1/orders/{id} - 获取特定订单
@getmapping("/{id}")
public order getorderbyid(@pathvariable long id) {
return orderservice.getbyid(id);
}
// get /api/v1/orders/{id}/items - 获取订单项(子资源)
@getmapping("/{id}/items")
public list<orderitem> getorderitems(@pathvariable long id) {
return orderservice.getorderitems(id);
}
// post /api/v1/orders - 创建订单
@postmapping
public responseentity<order> createorder(@valid @requestbody orderdto dto) {
order created = orderservice.create(dto);
uri location = servleturicomponentsbuilder
.fromcurrentrequest()
.path("/{id}")
.buildandexpand(created.getid())
.touri();
return responseentity.created(location).body(created);
}
}
3.1.2 高级请求处理技巧
// 1. 多条件查询参数处理
@getmapping("/search")
public list<user> searchusers(
@requestparam(required = false) string name,
@requestparam(required = false) string email,
@requestparam(required = false) @datetimeformat(iso = datetimeformat.iso.date) localdate startdate,
@requestparam(required = false) @datetimeformat(iso = datetimeformat.iso.date) localdate enddate,
@requestparam(defaultvalue = "createdat") string sortby,
@requestparam(defaultvalue = "desc") string direction) {
specification<user> spec = userspecifications.search(name, email, startdate, enddate);
sort sort = direction.equalsignorecase("desc")
? sort.by(sortby).descending()
: sort.by(sortby).ascending();
return userservice.search(spec, sort);
}
// 2. 文件上传处理
@postmapping("/upload")
public responseentity<string> uploadfile(
@requestparam("file") multipartfile file,
@requestparam("category") string category) {
if (file.isempty()) {
throw new badrequestexception("文件不能为空");
}
string filepath = filestorageservice.store(file, category);
return responseentity.ok("文件上传成功: " + filepath);
}
// 3. 请求/响应体压缩
@postmapping("/compress")
public responseentity<byte[]> handlecompresseddata(
@requestbody byte[] compresseddata) throws ioexception {
byte[] decompressed = compressionutils.decompress(compresseddata);
// 处理数据...
byte[] responsedata = "处理成功".getbytes();
byte[] compressedresponse = compressionutils.compress(responsedata);
return responseentity.ok()
.header("content-encoding", "gzip")
.body(compressedresponse);
}
3.2 服务层设计与实现
3.2.1 服务层架构模式
// 1. 基础服务接口
public interface userservice {
userdto createuser(usercreatedto dto);
userdto updateuser(long id, userupdatedto dto);
userdto getuserbyid(long id);
page<userdto> getusers(userquerydto query, pageable pageable);
void deleteuser(long id);
}
// 2. 服务实现(使用@transactional)
@service
@transactional
@slf4j
public class userserviceimpl implements userservice {
private final userrepository userrepository;
private final usermapper usermapper;
private final cacheservice cacheservice;
private final emailservice emailservice;
// 构造器注入(推荐方式)
public userserviceimpl(userrepository userrepository,
usermapper usermapper,
cacheservice cacheservice,
emailservice emailservice) {
this.userrepository = userrepository;
this.usermapper = usermapper;
this.cacheservice = cacheservice;
this.emailservice = emailservice;
}
@override
public userdto createuser(usercreatedto dto) {
log.info("创建用户: {}", dto.getemail());
// 验证逻辑
if (userrepository.existsbyemail(dto.getemail())) {
throw new businessexception("邮箱已存在");
}
// dto转实体
user user = usermapper.toentity(dto);
user.setstatus(userstatus.active);
user.setcreatedat(localdatetime.now());
// 保存到数据库
user saveduser = userrepository.save(user);
// 清理缓存
cacheservice.evict("users", saveduser.getid());
// 发送欢迎邮件(异步)
emailservice.sendwelcomeemail(saveduser.getemail());
// 返回dto
return usermapper.todto(saveduser);
}
@override
@transactional(readonly = true)
public userdto getuserbyid(long id) {
// 先尝试从缓存获取
string cachekey = "user:" + id;
userdto cached = cacheservice.get(cachekey, userdto.class);
if (cached != null) {
return cached;
}
// 缓存未命中,查询数据库
user user = userrepository.findbyid(id)
.orelsethrow(() -> new resourcenotfoundexception("用户不存在"));
userdto dto = usermapper.todto(user);
// 放入缓存
cacheservice.put(cachekey, dto, duration.ofminutes(30));
return dto;
}
@override
@transactional(readonly = true)
public page<userdto> getusers(userquerydto query, pageable pageable) {
// 构建查询条件
specification<user> spec = buildspecification(query);
// 执行分页查询
page<user> userpage = userrepository.findall(spec, pageable);
// 转换为dto
return userpage.map(usermapper::todto);
}
private specification<user> buildspecification(userquerydto query) {
return (root, criteriaquery, criteriabuilder) -> {
list<predicate> predicates = new arraylist<>();
if (stringutils.hastext(query.getname())) {
predicates.add(criteriabuilder.like(
root.get("name"), "%" + query.getname() + "%"
));
}
if (query.getstatus() != null) {
predicates.add(criteriabuilder.equal(
root.get("status"), query.getstatus()
));
}
if (query.getstartdate() != null) {
predicates.add(criteriabuilder.greaterthanorequalto(
root.get("createdat"), query.getstartdate()
));
}
if (query.getenddate() != null) {
predicates.add(criteriabuilder.lessthanorequalto(
root.get("createdat"), query.getenddate()
));
}
return criteriabuilder.and(predicates.toarray(new predicate[0]));
};
}
}
3.2.2 业务逻辑与事务管理
// 复杂业务事务管理示例
@service
@requiredargsconstructor
public class orderservice {
private final orderrepository orderrepository;
private final inventoryservice inventoryservice;
private final paymentservice paymentservice;
private final notificationservice notificationservice;
/**
* 创建订单的复杂业务流程
* 使用@transactional管理事务边界
*/
@transactional(rollbackfor = exception.class)
public orderdto createorder(ordercreatedto dto) {
// 1. 验证库存
inventoryservice.checkstock(dto.getitems());
// 2. 扣减库存(独立事务)
inventoryservice.deductstock(dto.getitems());
try {
// 3. 创建订单
order order = createorderentity(dto);
order = orderrepository.save(order);
// 4. 调用支付(外部系统,需要补偿机制)
paymentservice.processpayment(order);
// 5. 更新订单状态
order.setstatus(orderstatus.paid);
orderrepository.save(order);
// 6. 发送通知(异步,不影响主事务)
notificationservice.sendorderconfirmation(order);
return converttodto(order);
} catch (paymentexception e) {
// 支付失败,恢复库存
inventoryservice.restorestock(dto.getitems());
throw new businessexception("支付失败: " + e.getmessage());
}
}
/**
* 使用编程式事务处理特殊场景
*/
public void batchupdateorderstatus(list<long> orderids, orderstatus status) {
transactiontemplate transactiontemplate = new transactiontemplate(transactionmanager);
transactiontemplate.execute(status -> {
for (long orderid : orderids) {
order order = orderrepository.findbyid(orderid)
.orelsethrow(() -> new resourcenotfoundexception("订单不存在"));
// 记录状态变更历史
order.addstatushistory(order.getstatus(), status, "批量更新");
// 更新状态
order.setstatus(status);
orderrepository.save(order);
// 每处理100条记录提交一次,防止事务过大
if (orderids.indexof(orderid) % 100 == 0) {
entitymanager.flush();
entitymanager.clear();
}
}
return null;
});
}
}
3.3 数据传输对象设计
3.3.1 dto模式实现
// 1. 请求dto(验证注解)
@data
public class usercreatedto {
@notblank(message = "用户名不能为空")
@size(min = 2, max = 50, message = "用户名长度必须在2-50之间")
private string username;
@notblank(message = "邮箱不能为空")
@email(message = "邮箱格式不正确")
private string email;
@notblank(message = "密码不能为空")
@pattern(regexp = "^(?=.*[a-za-z])(?=.*\\d)[a-za-z\\d@$!%*#?&]{8,}$",
message = "密码必须至少8位,包含字母和数字")
private string password;
@notnull(message = "角色不能为空")
private userrole role;
@phonenumber // 自定义验证注解
private string phone;
}
// 2. 响应dto(嵌套对象)
@data
@builder
public class userdetaildto {
private long id;
private string username;
private string email;
private userstatus status;
private localdatetime createdat;
private list<useraddressdto> addresses;
private userprofiledto profile;
}
// 3. 查询参数dto
@data
public class userquerydto {
private string username;
private string email;
private userstatus status;
private localdatetime startdate;
private localdatetime enddate;
private string sortfield;
private sort.direction sortdirection;
public pageable topageable() {
if (sortfield != null && sortdirection != null) {
return pagerequest.of(0, 20, sort.by(sortdirection, sortfield));
}
return pagerequest.of(0, 20);
}
}
3.3.2 mapstruct映射器
// 1. 映射器接口
@mapper(componentmodel = "spring",
uses = {addressmapper.class, profilemapper.class},
unmappedtargetpolicy = reportingpolicy.ignore)
public interface usermapper {
usermapper instance = mappers.getmapper(usermapper.class);
// 简单映射
user toentity(usercreatedto dto);
userdto todto(user entity);
// 更新映射(忽略空值)
@beanmapping(nullvaluepropertymappingstrategy = nullvaluepropertymappingstrategy.ignore)
void updateentity(userupdatedto dto, @mappingtarget user entity);
// 列表映射
list<userdto> todtolist(list<user> entities);
// 分页映射
default page<userdto> todtopage(page<user> page) {
return page.map(this::todto);
}
// 自定义映射方法
@aftermapping
default void aftermapping(usercreatedto dto, @mappingtarget user user) {
if (user.getpassword() != null) {
user.setpassword(passwordencoder.encode(user.getpassword()));
}
user.setcreatedat(localdatetime.now());
}
}
// 2. 复杂映射配置
@mapper(componentmodel = "spring")
public interface ordermapper {
@mapping(source = "customer.id", target = "customerid")
@mapping(source = "customer.name", target = "customername")
@mapping(source = "items", target = "orderitems")
@mapping(target = "totalamount", expression = "java(calculatetotal(order))")
orderdto todto(order order);
default bigdecimal calculatetotal(order order) {
return order.getitems().stream()
.map(item -> item.getprice().multiply(bigdecimal.valueof(item.getquantity())))
.reduce(bigdecimal.zero, bigdecimal::add);
}
}
四、全局处理与高级特性
4.1 全局异常处理机制
4.1.1 统一异常处理器
@restcontrolleradvice
@slf4j
public class globalexceptionhandler {
// 处理验证异常
@exceptionhandler(methodargumentnotvalidexception.class)
public responseentity<errorresponse> handlevalidationexception(
methodargumentnotvalidexception ex) {
list<string> errors = ex.getbindingresult()
.getfielderrors()
.stream()
.map(error -> error.getfield() + ": " + error.getdefaultmessage())
.collect(collectors.tolist());
errorresponse response = errorresponse.builder()
.timestamp(localdatetime.now())
.status(httpstatus.bad_request.value())
.error("验证失败")
.message("请求参数无效")
.errors(errors)
.path(getrequestpath())
.build();
return responseentity.badrequest().body(response);
}
// 处理业务异常
@exceptionhandler(businessexception.class)
public responseentity<errorresponse> handlebusinessexception(
businessexception ex, httpservletrequest request) {
errorresponse response = errorresponse.builder()
.timestamp(localdatetime.now())
.status(httpstatus.conflict.value())
.error("业务错误")
.message(ex.getmessage())
.path(request.getrequesturi())
.build();
return responseentity.status(httpstatus.conflict).body(response);
}
// 处理资源不存在异常
@exceptionhandler(resourcenotfoundexception.class)
public responseentity<errorresponse> handlenotfoundexception(
resourcenotfoundexception ex, httpservletrequest request) {
errorresponse response = errorresponse.builder()
.timestamp(localdatetime.now())
.status(httpstatus.not_found.value())
.error("资源未找到")
.message(ex.getmessage())
.path(request.getrequesturi())
.build();
return responseentity.status(httpstatus.not_found).body(response);
}
// 处理所有未捕获的异常
@exceptionhandler(exception.class)
public responseentity<errorresponse> handleallexceptions(
exception ex, httpservletrequest request) {
log.error("未处理的异常: ", ex);
errorresponse response = errorresponse.builder()
.timestamp(localdatetime.now())
.status(httpstatus.internal_server_error.value())
.error("服务器内部错误")
.message("系统繁忙,请稍后重试")
.path(request.getrequesturi())
.build();
return responseentity.internalservererror().body(response);
}
private string getrequestpath() {
requestattributes attributes = requestcontextholder.getrequestattributes();
if (attributes instanceof servletrequestattributes) {
return ((servletrequestattributes) attributes).getrequest().getrequesturi();
}
return null;
}
}
// 错误响应dto
@data
@builder
@allargsconstructor
@noargsconstructor
public class errorresponse {
private localdatetime timestamp;
private int status;
private string error;
private string message;
private list<string> errors;
private string path;
}
4.1.2 自定义异常体系
// 基础业务异常
public abstract class baseexception extends runtimeexception {
private final string code;
private final map<string, object> data;
public baseexception(string code, string message) {
super(message);
this.code = code;
this.data = new hashmap<>();
}
public baseexception(string code, string message, throwable cause) {
super(message, cause);
this.code = code;
this.data = new hashmap<>();
}
public baseexception withdata(string key, object value) {
this.data.put(key, value);
return this;
}
public abstract httpstatus gethttpstatus();
}
// 具体业务异常
public class businessexception extends baseexception {
public businessexception(string message) {
super("business_error", message);
}
public businessexception(string code, string message) {
super(code, message);
}
@override
public httpstatus gethttpstatus() {
return httpstatus.conflict;
}
}
public class validationexception extends baseexception {
public validationexception(string message) {
super("validation_error", message);
}
@override
public httpstatus gethttpstatus() {
return httpstatus.bad_request;
}
}
public class authenticationexception extends baseexception {
public authenticationexception(string message) {
super("auth_error", message);
}
@override
public httpstatus gethttpstatus() {
return httpstatus.unauthorized;
}
}
4.2 数据验证高级技巧
4.2.1 自定义验证注解
// 1. 自定义注解
@target({elementtype.field, elementtype.parameter})
@retention(retentionpolicy.runtime)
@constraint(validatedby = phonenumbervalidator.class)
@documented
public @interface phonenumber {
string message() default "手机号码格式不正确";
class<?>[] groups() default {};
class<? extends payload>[] payload() default {};
string region() default "cn"; // 支持不同地区
}
// 2. 验证器实现
public class phonenumbervalidator implements constraintvalidator<phonenumber, string> {
private string region;
private static final map<string, pattern> region_patterns = new hashmap<>();
static {
// 中国大陆手机号
region_patterns.put("cn", pattern.compile("^1[3-9]\\d{9}$"));
// 美国手机号
region_patterns.put("us", pattern.compile("^\\(?([0-9]{3})\\)?[-.\\s]?([0-9]{3})[-.\\s]?([0-9]{4})$"));
// 香港手机号
region_patterns.put("hk", pattern.compile("^[569]\\d{3}\\d{4}$"));
}
@override
public void initialize(phonenumber constraintannotation) {
this.region = constraintannotation.region();
}
@override
public boolean isvalid(string phonenumber, constraintvalidatorcontext context) {
if (phonenumber == null) {
return true; // 使用@notnull处理空值
}
pattern pattern = region_patterns.get(region);
if (pattern == null) {
throw new illegalargumentexception("不支持的地区: " + region);
}
return pattern.matcher(phonenumber).matches();
}
}
// 3. 使用自定义注解
@data
public class contactdto {
@notblank(message = "姓名不能为空")
private string name;
@phonenumber(region = "cn", message = "请输入有效的中国大陆手机号")
private string phone;
@email(message = "邮箱格式不正确")
private string email;
// 跨字段验证
@asserttrue(message = "至少提供一种联系方式")
public boolean iscontactinfoprovided() {
return stringutils.hastext(phone) || stringutils.hastext(email);
}
}
4.2.2 分组验证
// 1. 定义验证组
public interface validationgroups {
interface create {}
interface update {}
interface patch {}
}
// 2. 在dto中使用分组
@data
public class userdto {
@null(groups = create.class, message = "id必须为空")
@notnull(groups = {update.class, patch.class}, message = "id不能为空")
private long id;
@notblank(groups = create.class, message = "用户名不能为空")
@size(min = 3, max = 50, groups = {create.class, update.class})
private string username;
@email(groups = {create.class, update.class})
private string email;
@notblank(groups = create.class, message = "密码不能为空")
@pattern(regexp = "^(?=.*[a-za-z])(?=.*\\d).{8,}$",
groups = create.class)
private string password;
@notnull(groups = create.class)
private userrole role;
}
// 3. 在控制器中使用分组验证
@restcontroller
@requestmapping("/api/users")
public class usercontroller {
@postmapping
public responseentity<userdto> createuser(
@validated(validationgroups.create.class)
@requestbody userdto userdto) {
// 创建逻辑
}
@putmapping("/{id}")
public responseentity<userdto> updateuser(
@pathvariable long id,
@validated(validationgroups.update.class)
@requestbody userdto userdto) {
// 更新逻辑
}
@patchmapping("/{id}")
public responseentity<userdto> patchuser(
@pathvariable long id,
@validated(validationgroups.patch.class)
@requestbody userdto userdto) {
// 部分更新逻辑
}
}
五、安全与认证授权
5.1 spring security集成
5.1.1 安全配置
@configuration
@enablewebsecurity
@enableglobalmethodsecurity(prepostenabled = true)
@requiredargsconstructor
public class securityconfig {
private final jwtauthenticationfilter jwtauthenticationfilter;
private final userdetailsservice userdetailsservice;
private final authenticationentrypoint authenticationentrypoint;
private final accessdeniedhandler accessdeniedhandler;
@bean
public securityfilterchain filterchain(httpsecurity http) throws exception {
http
// 禁用csrf(rest api通常不需要)
.csrf().disable()
// 会话管理(无状态)
.sessionmanagement()
.sessioncreationpolicy(sessioncreationpolicy.stateless)
.and()
// 异常处理
.exceptionhandling()
.authenticationentrypoint(authenticationentrypoint)
.accessdeniedhandler(accessdeniedhandler)
.and()
// 授权配置
.authorizehttprequests(auth -> auth
// 公开接口
.requestmatchers(
"/api/auth/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/actuator/health"
).permitall()
// 需要认证的接口
.requestmatchers("/api/users/**").hasanyrole("user", "admin")
.requestmatchers("/api/admin/**").hasrole("admin")
// 其他接口需要认证
.anyrequest().authenticated()
)
// 添加jwt过滤器
.addfilterbefore(jwtauthenticationfilter,
usernamepasswordauthenticationfilter.class)
// 记住我功能(可选)
.rememberme()
.tokenrepository(persistenttokenrepository())
.tokenvalidityseconds(86400) // 24小时
.and()
// 安全头配置
.headers(headers -> headers
.contentsecuritypolicy("default-src 'self'")
.frameoptions().sameorigin()
);
return http.build();
}
@bean
public passwordencoder passwordencoder() {
return new bcryptpasswordencoder();
}
@bean
public authenticationmanager authenticationmanager(
authenticationconfiguration config) throws exception {
return config.getauthenticationmanager();
}
@bean
public persistenttokenrepository persistenttokenrepository() {
jdbctokenrepositoryimpl tokenrepository = new jdbctokenrepositoryimpl();
tokenrepository.setdatasource(datasource);
return tokenrepository;
}
}
5.1.2 jwt认证实现
@component
@requiredargsconstructor
public class jwtauthenticationfilter extends onceperrequestfilter {
private final jwttokenprovider tokenprovider;
private final userdetailsservice userdetailsservice;
@override
protected void dofilterinternal(
httpservletrequest request,
httpservletresponse response,
filterchain filterchain) throws servletexception, ioexception {
try {
// 从请求头获取token
string token = extracttoken(request);
if (token != null && tokenprovider.validatetoken(token)) {
// 从token中获取用户名
string username = tokenprovider.getusernamefromtoken(token);
// 加载用户详情
userdetails userdetails = userdetailsservice.loaduserbyusername(username);
// 创建认证对象
usernamepasswordauthenticationtoken authentication =
new usernamepasswordauthenticationtoken(
userdetails, null, userdetails.getauthorities());
// 设置详情
authentication.setdetails(
new webauthenticationdetailssource().builddetails(request));
// 设置安全上下文
securitycontextholder.getcontext().setauthentication(authentication);
}
} catch (exception ex) {
logger.error("无法设置用户认证", ex);
}
filterchain.dofilter(request, response);
}
private string extracttoken(httpservletrequest request) {
string bearertoken = request.getheader("authorization");
if (stringutils.hastext(bearertoken) && bearertoken.startswith("bearer ")) {
return bearertoken.substring(7);
}
return null;
}
}
@component
public class jwttokenprovider {
@value("${app.jwt.secret}")
private string secret;
@value("${app.jwt.expiration}")
private long expiration;
public string generatetoken(authentication authentication) {
userdetails userdetails = (userdetails) authentication.getprincipal();
date now = new date();
date expirydate = new date(now.gettime() + expiration);
return jwts.builder()
.setsubject(userdetails.getusername())
.setissuedat(now)
.setexpiration(expirydate)
.signwith(signaturealgorithm.hs512, secret)
.compact();
}
public string getusernamefromtoken(string token) {
claims claims = jwts.parser()
.setsigningkey(secret)
.parseclaimsjws(token)
.getbody();
return claims.getsubject();
}
public boolean validatetoken(string token) {
try {
jwts.parser().setsigningkey(secret).parseclaimsjws(token);
return true;
} catch (signatureexception ex) {
logger.error("无效的jwt签名");
} catch (malformedjwtexception ex) {
logger.error("无效的jwt令牌");
} catch (expiredjwtexception ex) {
logger.error("jwt令牌已过期");
} catch (unsupportedjwtexception ex) {
logger.error("不支持的jwt令牌");
} catch (illegalargumentexception ex) {
logger.error("jwt claims字符串为空");
}
return false;
}
}
5.2 方法级安全控制
5.2.1 基于注解的权限控制
@restcontroller
@requestmapping("/api/products")
public class productcontroller {
// 基于角色的访问控制
@preauthorize("hasrole('admin')")
@postmapping
public productdto createproduct(@valid @requestbody productcreatedto dto) {
return productservice.create(dto);
}
// 基于权限的访问控制
@preauthorize("hasauthority('product_read')")
@getmapping("/{id}")
public productdto getproduct(@pathvariable long id) {
return productservice.getbyid(id);
}
// 基于表达式的复杂权限控制
@preauthorize("hasrole('admin') or @productsecurity.isowner(#id, authentication)")
@putmapping("/{id}")
public productdto updateproduct(
@pathvariable long id,
@valid @requestbody productupdatedto dto) {
return productservice.update(id, dto);
}
// 方法调用后权限检查
@postauthorize("returnobject.status != 'deleted'")
@getmapping("/secure/{id}")
public productdto getsecureproduct(@pathvariable long id) {
return productservice.getbyid(id);
}
// 基于过滤器的权限控制
@prefilter("filterobject.ownerid == authentication.principal.id")
@postmapping("/batch")
public list<productdto> createproducts(
@requestbody list<productcreatedto> products) {
return productservice.createbatch(products);
}
// 方法调用后过滤
@postfilter("filterobject.price > 100")
@getmapping("/expensive")
public list<productdto> getexpensiveproducts() {
return productservice.getall();
}
}
// 自定义安全表达式处理器
@component("productsecurity")
public class productsecurity {
private final productrepository productrepository;
public boolean isowner(long productid, authentication authentication) {
if (authentication == null || !authentication.isauthenticated()) {
return false;
}
string username = authentication.getname();
optional<product> product = productrepository.findbyid(productid);
return product.ispresent() &&
product.get().getcreatedby().equals(username);
}
public boolean canview(product product, authentication authentication) {
// 复杂的业务逻辑判断
if (product.ispublic()) {
return true;
}
if (authentication == null) {
return false;
}
userdetails userdetails = (userdetails) authentication.getprincipal();
return product.getowners().contains(userdetails.getusername()) ||
userdetails.getauthorities().stream()
.anymatch(a -> a.getauthority().equals("role_admin"));
}
}
5.2.2 权限缓存与性能优化
@configuration
@enablecaching
public class securitycacheconfig {
@bean
public cachemanager cachemanager() {
concurrentmapcachemanager cachemanager = new concurrentmapcachemanager();
cachemanager.setcachenames(arrays.aslist(
"userdetails",
"permissions",
"aclcache"
));
return cachemanager;
}
}
@service
public class cachinguserdetailsservice implements userdetailsservice {
private final userdetailsservice delegate;
private final cachemanager cachemanager;
@cacheable(value = "userdetails", key = "#username")
@override
public userdetails loaduserbyusername(string username) throws usernamenotfoundexception {
return delegate.loaduserbyusername(username);
}
@cacheevict(value = "userdetails", key = "#username")
public void evictusercache(string username) {
// 缓存清除
}
}
// 权限缓存服务
@service
public class permissioncacheservice {
@cacheable(value = "permissions", key = "#userid + ':' + #resource")
public boolean haspermission(long userid, string resource, string action) {
// 从数据库查询权限
return permissionrepository.existsbyuseridandresourceandaction(
userid, resource, action);
}
@cacheevict(value = "permissions", allentries = true)
public void clearallpermissioncache() {
// 清除所有权限缓存
}
}
六、api文档与测试
6.1 openapi/swagger集成
6.1.1 springdoc openapi配置
@configuration
@openapidefinition(
info = @info(
title = "订单管理系统api",
version = "1.0.0",
description = "订单管理系统rest api文档",
contact = @contact(
name = "技术支持",
email = "support@example.com",
url = "https://example.com"
),
license = @license(
name = "apache 2.0",
url = "https://www.apache.org/licenses/license-2.0"
),
termsofservice = "https://example.com/terms"
),
servers = {
@server(
url = "http://localhost:8080",
description = "开发环境"
),
@server(
url = "https://api.example.com",
description = "生产环境"
)
},
security = @securityrequirement(name = "bearerauth")
)
@securityscheme(
name = "bearerauth",
type = securityschemetype.http,
bearerformat = "jwt",
scheme = "bearer"
)
public class openapiconfig {
@bean
public openapi customopenapi() {
return new openapi()
.components(new components()
.addschemas("errorresponse", new schema<errorresponse>()
.type("object")
.addproperty("timestamp", new schema<string>()
.type("string")
.format("date-time"))
.addproperty("status", new schema<integer>()
.type("integer"))
.addproperty("error", new schema<string>()
.type("string"))
.addproperty("message", new schema<string>()
.type("string"))
.addproperty("path", new schema<string>()
.type("string")))
.addsecurityschemes("bearerauth", new securityscheme()
.type(securityscheme.type.http)
.scheme("bearer")
.bearerformat("jwt")))
.externaldocs(new externaldocumentation()
.description("更多文档")
.url("https://docs.example.com"));
}
@bean
public groupedopenapi publicapi() {
return groupedopenapi.builder()
.group("public")
.pathstomatch("/api/**")
.build();
}
@bean
public groupedopenapi adminapi() {
return groupedopenapi.builder()
.group("admin")
.pathstomatch("/api/admin/**")
.build();
}
}
6.1.2 控制器文档注解
@restcontroller
@requestmapping("/api/orders")
@tag(name = "订单管理", description = "订单相关操作")
public class ordercontroller {
@operation(
summary = "获取订单列表",
description = "分页获取订单列表,支持多种查询条件",
parameters = {
@parameter(name = "page", description = "页码,从0开始", example = "0"),
@parameter(name = "size", description = "每页大小", example = "20"),
@parameter(name = "status", description = "订单状态"),
@parameter(name = "startdate", description = "开始日期", example = "2024-01-01"),
@parameter(name = "enddate", description = "结束日期", example = "2024-12-31")
}
)
@apiresponses({
@apiresponse(
responsecode = "200",
description = "成功获取订单列表",
content = @content(
mediatype = "application/json",
array = @arrayschema(schema = @schema(implementation = orderdto.class))
)
),
@apiresponse(
responsecode = "401",
description = "未授权访问"
),
@apiresponse(
responsecode = "403",
description = "权限不足"
)
})
@getmapping
@preauthorize("hasrole('user')")
public page<orderdto> getorders(
@parameterobject pageable pageable,
@parameterobject orderquerydto query) {
return orderservice.getorders(query, pageable);
}
@operation(
summary = "创建订单",
description = "创建新订单,需要商品信息和收货地址"
)
@postmapping
@responsestatus(httpstatus.created)
public responseentity<orderdto> createorder(
@io.swagger.v3.oas.annotations.parameters.requestbody(
description = "订单创建信息",
required = true,
content = @content(
schema = @schema(implementation = ordercreatedto.class)
)
)
@valid @requestbody ordercreatedto dto) {
orderdto created = orderservice.createorder(dto);
uri location = servleturicomponentsbuilder
.fromcurrentrequest()
.path("/{id}")
.buildandexpand(created.getid())
.touri();
return responseentity.created(location).body(created);
}
@operation(
summary = "获取订单详情",
description = "根据id获取订单详细信息"
)
@getmapping("/{id}")
public orderdto getorder(
@parameter(description = "订单id", required = true, example = "123")
@pathvariable long id) {
return orderservice.getorderbyid(id);
}
@operation(
summary = "更新订单状态",
description = "更新订单状态,支持取消、完成等操作"
)
@patchmapping("/{id}/status")
public orderdto updateorderstatus(
@parameter(description = "订单id", required = true)
@pathvariable long id,
@requestbody orderstatusupdatedto dto) {
return orderservice.updatestatus(id, dto);
}
@operation(
summary = "导出订单",
description = "导出订单数据为excel文件"
)
@getmapping("/export")
public void exportorders(
@parameterobject orderquerydto query,
httpservletresponse response) throws ioexception {
list<orderdto> orders = orderservice.exportorders(query);
response.setcontenttype("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setheader("content-disposition", "attachment; filename=orders.xlsx");
exportservice.exporttoexcel(orders, response.getoutputstream());
}
}
6.2 全面测试策略
6.2.1 单元测试
@extendwith(mockitoextension.class)
class userservicetest {
@mock
private userrepository userrepository;
@mock
private passwordencoder passwordencoder;
@mock
private emailservice emailservice;
@injectmocks
private userserviceimpl userservice;
private usermapper usermapper = mappers.getmapper(usermapper.class);
@test
void createuser_shouldreturnuserdto_whenvalidinput() {
// 准备测试数据
usercreatedto createdto = new usercreatedto();
createdto.setusername("testuser");
createdto.setemail("test@example.com");
createdto.setpassword("password123");
user user = new user();
user.setid(1l);
user.setusername("testuser");
user.setemail("test@example.com");
// 设置mock行为
when(userrepository.existsbyemail(anystring())).thenreturn(false);
when(passwordencoder.encode(anystring())).thenreturn("encodedpassword");
when(userrepository.save(any(user.class))).thenreturn(user);
// 执行测试
userdto result = userservice.createuser(createdto);
// 验证结果
assertnotnull(result);
assertequals(1l, result.getid());
assertequals("testuser", result.getusername());
assertequals("test@example.com", result.getemail());
// 验证交互
verify(userrepository).existsbyemail("test@example.com");
verify(passwordencoder).encode("password123");
verify(userrepository).save(any(user.class));
verify(emailservice).sendwelcomeemail("test@example.com");
}
@test
void createuser_shouldthrowexception_whenemailexists() {
// 准备测试数据
usercreatedto createdto = new usercreatedto();
createdto.setemail("existing@example.com");
// 设置mock行为
when(userrepository.existsbyemail("existing@example.com")).thenreturn(true);
// 执行测试并验证异常
businessexception exception = assertthrows(
businessexception.class,
() -> userservice.createuser(createdto)
);
assertequals("邮箱已存在", exception.getmessage());
}
@test
@displayname("根据id获取用户 - 用户存在")
void getuserbyid_shouldreturnuser_whenuserexists() {
// 准备测试数据
long userid = 1l;
user user = new user();
user.setid(userid);
user.setusername("testuser");
// 设置mock行为
when(userrepository.findbyid(userid))
.thenreturn(optional.of(user));
// 执行测试
userdto result = userservice.getuserbyid(userid);
// 验证结果
assertnotnull(result);
assertequals(userid, result.getid());
assertequals("testuser", result.getusername());
}
@test
@displayname("根据id获取用户 - 用户不存在")
void getuserbyid_shouldthrowexception_whenusernotfound() {
// 设置mock行为
when(userrepository.findbyid(anylong()))
.thenreturn(optional.empty());
// 执行测试并验证异常
resourcenotfoundexception exception = assertthrows(
resourcenotfoundexception.class,
() -> userservice.getuserbyid(1l)
);
assertequals("用户不存在", exception.getmessage());
}
@parameterizedtest
@csvsource({
"1, admin, admin",
"2, user, user",
"3, manager, manager"
})
void getuserbyid_withdifferentusers_shouldreturncorrectuser(
long id, string username, userrole role) {
user user = new user();
user.setid(id);
user.setusername(username);
user.setrole(role);
when(userrepository.findbyid(id)).thenreturn(optional.of(user));
userdto result = userservice.getuserbyid(id);
assertequals(id, result.getid());
assertequals(username, result.getusername());
assertequals(role, result.getrole());
}
@test
void updateuser_shouldupdateandreturnuser() {
// 准备测试数据
long userid = 1l;
userupdatedto updatedto = new userupdatedto();
updatedto.setusername("updateduser");
updatedto.setemail("updated@example.com");
user existinguser = new user();
existinguser.setid(userid);
existinguser.setusername("olduser");
existinguser.setemail("old@example.com");
user updateduser = new user();
updateduser.setid(userid);
updateduser.setusername("updateduser");
updateduser.setemail("updated@example.com");
// 设置mock行为
when(userrepository.findbyid(userid))
.thenreturn(optional.of(existinguser));
when(userrepository.save(any(user.class)))
.thenreturn(updateduser);
// 执行测试
userdto result = userservice.updateuser(userid, updatedto);
// 验证结果
assertequals("updateduser", result.getusername());
assertequals("updated@example.com", result.getemail());
// 验证交互
verify(userrepository).findbyid(userid);
verify(userrepository).save(existinguser);
assertequals("updateduser", existinguser.getusername());
assertequals("updated@example.com", existinguser.getemail());
}
}
6.2.2 集成测试
@springboottest
@autoconfiguremockmvc
@testcontainers
@transactional
class usercontrollerintegrationtest {
@container
static postgresqlcontainer<?> postgres = new postgresqlcontainer<>("postgres:15")
.withdatabasename("testdb")
.withusername("test")
.withpassword("test");
@dynamicpropertysource
static void configureproperties(dynamicpropertyregistry registry) {
registry.add("spring.datasource.url", postgres::getjdbcurl);
registry.add("spring.datasource.username", postgres::getusername);
registry.add("spring.datasource.password", postgres::getpassword);
}
@autowired
private mockmvc mockmvc;
@autowired
private objectmapper objectmapper;
@autowired
private userrepository userrepository;
@beforeeach
void setup() {
userrepository.deleteall();
}
@test
void createuser_shouldreturncreateduser() throws exception {
// 准备测试数据
usercreatedto createdto = new usercreatedto();
createdto.setusername("integrationtest");
createdto.setemail("integration@test.com");
createdto.setpassword("password123!");
createdto.setrole(userrole.user);
// 执行请求
mockmvc.perform(post("/api/users")
.contenttype(mediatype.application_json)
.content(objectmapper.writevalueasstring(createdto)))
// 验证响应
.andexpect(status().iscreated())
.andexpect(header().exists("location"))
.andexpect(jsonpath("$.id").exists())
.andexpect(jsonpath("$.username").value("integrationtest"))
.andexpect(jsonpath("$.email").value("integration@test.com"))
.andexpect(jsonpath("$.role").value("user"));
// 验证数据库
optional<user> saveduser = userrepository.findbyemail("integration@test.com");
asserttrue(saveduser.ispresent());
assertequals("integrationtest", saveduser.get().getusername());
}
@test
void createuser_shouldreturnbadrequest_wheninvalidinput() throws exception {
// 准备无效的测试数据
usercreatedto createdto = new usercreatedto();
createdto.setemail("invalid-email");
mockmvc.perform(post("/api/users")
.contenttype(mediatype.application_json)
.content(objectmapper.writevalueasstring(createdto)))
.andexpect(status().isbadrequest())
.andexpect(jsonpath("$.errors").exists());
}
@test
void getuser_shouldreturnuser_whenuserexists() throws exception {
// 准备测试数据
user user = new user();
user.setusername("testuser");
user.setemail("test@example.com");
user.setpassword("encodedpassword");
user.setrole(userrole.user);
user = userrepository.save(user);
// 执行请求
mockmvc.perform(get("/api/users/{id}", user.getid()))
.andexpect(status().isok())
.andexpect(jsonpath("$.id").value(user.getid()))
.andexpect(jsonpath("$.username").value("testuser"))
.andexpect(jsonpath("$.email").value("test@example.com"));
}
@test
void getuser_shouldreturnnotfound_whenusernotexists() throws exception {
mockmvc.perform(get("/api/users/{id}", 999))
.andexpect(status().isnotfound());
}
@test
void updateuser_shouldupdateuser() throws exception {
// 创建测试用户
user user = new user();
user.setusername("olduser");
user.setemail("old@example.com");
user.setpassword("encodedpassword");
user.setrole(userrole.user);
user = userrepository.save(user);
// 准备更新数据
userupdatedto updatedto = new userupdatedto();
updatedto.setusername("updateduser");
updatedto.setemail("updated@example.com");
// 执行更新请求
mockmvc.perform(put("/api/users/{id}", user.getid())
.contenttype(mediatype.application_json)
.content(objectmapper.writevalueasstring(updatedto)))
.andexpect(status().isok())
.andexpect(jsonpath("$.username").value("updateduser"))
.andexpect(jsonpath("$.email").value("updated@example.com"));
// 验证数据库更新
optional<user> updateduser = userrepository.findbyid(user.getid());
asserttrue(updateduser.ispresent());
assertequals("updateduser", updateduser.get().getusername());
assertequals("updated@example.com", updateduser.get().getemail());
}
@test
void deleteuser_shoulddeleteuser() throws exception {
// 创建测试用户
user user = new user();
user.setusername("tobedeleted");
user.setemail("delete@example.com");
user.setpassword("encodedpassword");
user = userrepository.save(user);
// 执行删除请求
mockmvc.perform(delete("/api/users/{id}", user.getid()))
.andexpect(status().isnocontent());
// 验证用户已删除
assertfalse(userrepository.existsbyid(user.getid()));
}
@test
void getusers_shouldreturnpaginatedusers() throws exception {
// 创建测试数据
for (int i = 1; i <= 25; i++) {
user user = new user();
user.setusername("user" + i);
user.setemail("user" + i + "@example.com");
user.setpassword("password" + i);
userrepository.save(user);
}
// 执行分页查询
mockmvc.perform(get("/api/users")
.param("page", "0")
.param("size", "10")
.param("sort", "username,asc"))
.andexpect(status().isok())
.andexpect(jsonpath("$.content").isarray())
.andexpect(jsonpath("$.content.length()").value(10))
.andexpect(jsonpath("$.totalpages").value(3))
.andexpect(jsonpath("$.totalelements").value(25))
.andexpect(jsonpath("$.first").value(true))
.andexpect(jsonpath("$.last").value(false));
}
@test
@withmockuser(username = "admin", roles = {"admin"})
void adminendpoint_shouldbeaccessible_foradminuser() throws exception {
mockmvc.perform(get("/api/admin/users"))
.andexpect(status().isok());
}
@test
@withmockuser(username = "user", roles = {"user"})
void adminendpoint_shouldbeforbidden_fornonadminuser() throws exception {
mockmvc.perform(get("/api/admin/users"))
.andexpect(status().isforbidden());
}
}
七、部署与监控
7.1 docker容器化部署
7.1.1 dockerfile配置
# 构建阶段
from maven:3.8.4-openjdk-17-slim as build
# 设置工作目录
workdir /app
# 复制项目文件
copy pom.xml .
copy src ./src
# 下载依赖并构建(利用docker层缓存)
run mvn dependency:go-offline
run mvn clean package -dskiptests
# 运行时阶段
from openjdk:17-jdk-slim
# 安装必要的工具
run apt-get update && apt-get install -y \
curl \
tzdata \
&& rm -rf /var/lib/apt/lists/*
# 设置时区
env tz=asia/shanghai
# 创建非root用户
run groupadd -r spring && useradd -r -g spring spring
user spring:spring
# 设置工作目录
workdir /app
# 从构建阶段复制jar文件
copy --from=build /app/target/*.jar app.jar
# 暴露端口
expose 8080
# jvm参数
env java_opts="-xms512m -xmx1024m -xx:+useg1gc -xx:+heapdumponoutofmemoryerror -xx:heapdumppath=/tmp"
# 健康检查
healthcheck --interval=30s --timeout=3s --start-period=30s --retries=3 \
cmd curl -f http://localhost:8080/actuator/health || exit 1
# 启动命令
entrypoint ["sh", "-c", "java ${java_opts} -djava.security.egd=file:/dev/./urandom -jar /app/app.jar"]
7.1.2 docker compose配置
version: '3.8'
services:
# 主应用服务
app:
build: .
container_name: spring-app
ports:
- "8080:8080"
environment:
- spring_profiles_active=prod
- spring_datasource_url=jdbc:postgresql://postgres:5432/appdb
- spring_datasource_username=appuser
- spring_datasource_password=${db_password}
- spring_redis_host=redis
- spring_redis_port=6379
- jwt_secret=${jwt_secret}
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["cmd", "curl", "-f", "http://localhost:8080/actuator/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# postgresql数据库
postgres:
image: postgres:15-alpine
container_name: app-postgres
environment:
- postgres_db=appdb
- postgres_user=appuser
- postgres_password=${db_password}
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init-db:/docker-entrypoint-initdb.d
ports:
- "5432:5432"
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["cmd-shell", "pg_isready -u appuser"]
interval: 10s
timeout: 5s
retries: 5
# redis缓存
redis:
image: redis:7-alpine
container_name: app-redis
command: redis-server --requirepass ${redis_password}
volumes:
- redis_data:/data
ports:
- "6379:6379"
networks:
- backend
restart: unless-stopped
healthcheck:
test: ["cmd", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
# nginx反向代理
nginx:
image: nginx:alpine
container_name: app-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/conf.d:/etc/nginx/conf.d
- ./ssl:/etc/nginx/ssl
depends_on:
- app
networks:
- backend
restart: unless-stopped
# 监控系统 (prometheus + grafana)
prometheus:
image: prom/prometheus:latest
container_name: app-prometheus
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
ports:
- "9090:9090"
networks:
- backend
restart: unless-stopped
grafana:
image: grafana/grafana:latest
container_name: app-grafana
environment:
- gf_security_admin_password=${grafana_password}
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
ports:
- "3000:3000"
networks:
- backend
restart: unless-stopped
networks:
backend:
driver: bridge
volumes:
postgres_data:
redis_data:
prometheus_data:
grafana_data:
7.2 性能监控与指标
7.2.1 spring boot actuator配置
# application-prod.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
base-path: /actuator
cors:
allowed-origins: "*"
allowed-methods: get,post
endpoint:
health:
show-details: when_authorized
roles: admin
probes:
enabled: true
groups:
liveness:
include: livenessstate
readiness:
include: readinessstate
metrics:
enabled: true
prometheus:
enabled: true
metrics:
export:
prometheus:
enabled: true
step: 1m
distribution:
percentiles-histogram:
"[http.server.requests]": true
sla:
"[http.server.requests]": 10ms, 50ms, 100ms, 200ms, 500ms, 1s, 2s
info:
env:
enabled: true
java:
enabled: true
os:
enabled: true
tracing:
sampling:
probability: 0.1
server:
port: 8081 # 单独的监控端口
7.2.2 自定义健康检查
@component
public class databasehealthindicator implements healthindicator {
private final datasource datasource;
private final jdbctemplate jdbctemplate;
public databasehealthindicator(datasource datasource) {
this.datasource = datasource;
this.jdbctemplate = new jdbctemplate(datasource);
}
@override
public health health() {
try {
// 检查数据库连接
integer result = jdbctemplate.queryforobject("select 1", integer.class);
if (result != null && result == 1) {
// 检查数据库性能
map<string, object> details = new hashmap<>();
// 获取连接池信息
if (datasource instanceof hikaridatasource) {
hikaridatasource hikari = (hikaridatasource) datasource;
details.put("activeconnections", hikari.gethikaripoolmxbean().getactiveconnections());
details.put("idleconnections", hikari.gethikaripoolmxbean().getidleconnections());
details.put("totalconnections", hikari.gethikaripoolmxbean().gettotalconnections());
}
return health.up()
.withdetails(details)
.build();
}
return health.down()
.withdetail("error", "数据库查询返回异常结果")
.build();
} catch (exception e) {
return health.down()
.withexception(e)
.withdetail("error", "数据库连接失败: " + e.getmessage())
.build();
}
}
}
@component
public class cachehealthindicator implements healthindicator {
private final cachemanager cachemanager;
public cachehealthindicator(cachemanager cachemanager) {
this.cachemanager = cachemanager;
}
@override
public health health() {
map<string, object> details = new hashmap<>();
for (string cachename : cachemanager.getcachenames()) {
cache cache = cachemanager.getcache(cachename);
if (cache != null && cache.getnativecache() instanceof com.github.benmanes.caffeine.cache.cache) {
@suppresswarnings("unchecked")
com.github.benmanes.caffeine.cache.cache<object, object> caffeinecache =
(com.github.benmanes.caffeine.cache.cache<object, object>) cache.getnativecache();
map<string, object> cachestats = new hashmap<>();
cachestats.put("estimatedsize", caffeinecache.estimatedsize());
cachestats.put("stats", caffeinecache.stats());
details.put(cachename, cachestats);
}
}
return health.up()
.withdetails(details)
.build();
}
}
@component
public class externalservicehealthindicator extends abstracthealthindicator {
private final resttemplate resttemplate;
private final string serviceurl;
public externalservicehealthindicator(resttemplatebuilder resttemplatebuilder) {
this.resttemplate = resttemplatebuilder
.setconnecttimeout(duration.ofseconds(5))
.setreadtimeout(duration.ofseconds(10))
.build();
this.serviceurl = "https://api.external-service.com/health";
}
@override
protected void dohealthcheck(health.builder builder) throws exception {
try {
responseentity<string> response = resttemplate.getforentity(serviceurl, string.class);
if (response.getstatuscode().is2xxsuccessful()) {
builder.up()
.withdetail("status", response.getstatuscode().value())
.withdetail("responsetime", "正常");
} else {
builder.down()
.withdetail("status", response.getstatuscode().value())
.withdetail("error", "外部服务返回异常状态码");
}
} catch (resourceaccessexception e) {
builder.down()
.withexception(e)
.withdetail("error", "连接外部服务超时");
} catch (exception e) {
builder.down()
.withexception(e)
.withdetail("error", "检查外部服务健康状态时发生异常");
}
}
}
八、性能优化与最佳实践
8.1 数据库性能优化
8.1.1 连接池配置
# application-prod.yml
spring:
datasource:
hikari:
# 连接池配置
maximum-pool-size: 20
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
# 性能优化
auto-commit: false
connection-test-query: select 1
validation-timeout: 5000
leak-detection-threshold: 60000
# 连接属性
data-source-properties:
prepstmtcachesize: 250
prepstmtcachesqllimit: 2048
cacheprepstmts: true
useserverprepstmts: true
uselocalsessionstate: true
rewritebatchedstatements: true
cacheresultsetmetadata: true
cacheserverconfiguration: true
elidesetautocommits: true
maintaintimestats: false
8.1.2 jpa性能优化
@configuration
@enablejparepositories(
basepackages = "com.example.repository",
repositorybaseclass = customjparepositoryimpl.class
)
@enabletransactionmanagement
public class jpaconfig {
@bean
public localcontainerentitymanagerfactorybean entitymanagerfactory(
entitymanagerfactorybuilder builder,
datasource datasource) {
map<string, object> properties = new hashmap<>();
properties.put("hibernate.jdbc.batch_size", 50);
properties.put("hibernate.order_inserts", true);
properties.put("hibernate.order_updates", true);
properties.put("hibernate.batch_versioned_data", true);
properties.put("hibernate.query.in_clause_parameter_padding", true);
properties.put("hibernate.default_batch_fetch_size", 16);
properties.put("hibernate.max_fetch_depth", 3);
properties.put("hibernate.jdbc.fetch_size", 100);
// 生产环境禁用ddl自动更新
properties.put("hibernate.hbm2ddl.auto", "validate");
return builder
.datasource(datasource)
.packages("com.example.entity")
.persistenceunit("default")
.properties(properties)
.build();
}
@bean
public jpatransactionmanager transactionmanager(entitymanagerfactory emf) {
return new jpatransactionmanager(emf);
}
}
// 自定义repository实现
@norepositorybean
public class customjparepositoryimpl<t, id> extends simplejparepository<t, id>
implements customjparepository<t, id> {
private final entitymanager entitymanager;
public customjparepositoryimpl(jpaentityinformation<t, ?> entityinformation,
entitymanager entitymanager) {
super(entityinformation, entitymanager);
this.entitymanager = entitymanager;
}
@override
@transactional(readonly = true)
public list<t> findallwithpagination(int offset, int limit, sort sort) {
criteriabuilder cb = entitymanager.getcriteriabuilder();
criteriaquery<t> query = cb.createquery(getdomainclass());
root<t> root = query.from(getdomainclass());
query.select(root);
if (sort != null) {
list<order> orders = new arraylist<>();
sort.foreach(order -> {
if (order.isascending()) {
orders.add(cb.asc(root.get(order.getproperty())));
} else {
orders.add(cb.desc(root.get(order.getproperty())));
}
});
query.orderby(orders);
}
return entitymanager.createquery(query)
.setfirstresult(offset)
.setmaxresults(limit)
.getresultlist();
}
@override
@transactional
public int batchinsert(list<t> entities) {
int batchsize = 50;
int count = 0;
for (int i = 0; i < entities.size(); i++) {
entitymanager.persist(entities.get(i));
if (i % batchsize == 0 && i > 0) {
entitymanager.flush();
entitymanager.clear();
count += batchsize;
}
}
entitymanager.flush();
entitymanager.clear();
return count + (entities.size() % batchsize);
}
}
8.2 缓存策略优化
8.2.1 多级缓存配置
@configuration
@enablecaching
public class cacheconfig {
@bean
public cachemanager cachemanager() {
caffeinecachemanager cachemanager = new caffeinecachemanager();
// 全局缓存配置
cachemanager.setcaffeine(caffeine.newbuilder()
.expireafterwrite(duration.ofminutes(30))
.maximumsize(10000)
.recordstats());
// 自定义缓存配置
map<string, caffeine<object, object>> cacheconfigs = new hashmap<>();
// 用户缓存 - 较短时间,高频访问
cacheconfigs.put("users", caffeine.newbuilder()
.expireafterwrite(duration.ofminutes(10))
.maximumsize(1000)
.recordstats());
// 商品缓存 - 较长时间,低频更新
cacheconfigs.put("products", caffeine.newbuilder()
.expireafterwrite(duration.ofhours(2))
.maximumsize(5000)
.recordstats());
// 配置缓存 - 永不过期,手动刷新
cacheconfigs.put("config", caffeine.newbuilder()
.maximumsize(100)
.recordstats());
cachemanager.setcachespecification(cacheconfigs);
return cachemanager;
}
@bean
public cachemanager rediscachemanager(redisconnectionfactory factory) {
rediscacheconfiguration config = rediscacheconfiguration.defaultcacheconfig()
.entryttl(duration.ofhours(1))
.disablecachingnullvalues()
.serializekeyswith(redisserializationcontext.serializationpair
.fromserializer(new stringredisserializer()))
.serializevalueswith(redisserializationcontext.serializationpair
.fromserializer(new genericjackson2jsonredisserializer()));
// 不同缓存不同配置
map<string, rediscacheconfiguration> cacheconfigs = new hashmap<>();
cacheconfigs.put("users", config.entryttl(duration.ofminutes(30)));
cacheconfigs.put("products", config.entryttl(duration.ofhours(2)));
return rediscachemanager.builder(factory)
.cachedefaults(config)
.withinitialcacheconfigurations(cacheconfigs)
.transactionaware()
.build();
}
@bean
public cachemanager multilevelcachemanager(
cachemanager localcachemanager,
cachemanager rediscachemanager) {
return new multilevelcachemanager(localcachemanager, rediscachemanager);
}
}
// 多级缓存实现
public class multilevelcachemanager implements cachemanager {
private final cachemanager localcachemanager; // l1: caffeine
private final cachemanager rediscachemanager; // l2: redis
public multilevelcachemanager(cachemanager localcachemanager,
cachemanager rediscachemanager) {
this.localcachemanager = localcachemanager;
this.rediscachemanager = rediscachemanager;
}
@override
public cache getcache(string name) {
cache localcache = localcachemanager.getcache(name);
cache remotecache = rediscachemanager.getcache(name);
return new multilevelcache(name, localcache, remotecache);
}
@override
public collection<string> getcachenames() {
set<string> names = new hashset<>();
names.addall(localcachemanager.getcachenames());
names.addall(rediscachemanager.getcachenames());
return names;
}
}
class multilevelcache implements cache {
private final string name;
private final cache localcache;
private final cache remotecache;
public multilevelcache(string name, cache localcache, cache remotecache) {
this.name = name;
this.localcache = localcache;
this.remotecache = remotecache;
}
@override
public string getname() {
return name;
}
@override
public object getnativecache() {
return remotecache.getnativecache();
}
@override
public valuewrapper get(object key) {
// 先查本地缓存
valuewrapper value = localcache.get(key);
if (value != null) {
return value;
}
// 本地缓存未命中,查redis
value = remotecache.get(key);
if (value != null) {
// 回写到本地缓存
localcache.put(key, value.get());
}
return value;
}
@override
public <t> t get(object key, class<t> type) {
// 实现类似get方法
t value = localcache.get(key, type);
if (value != null) {
return value;
}
value = remotecache.get(key, type);
if (value != null) {
localcache.put(key, value);
}
return value;
}
@override
public void put(object key, object value) {
// 同时写入两级缓存
localcache.put(key, value);
remotecache.put(key, value);
}
@override
public void evict(object key) {
// 同时清除两级缓存
localcache.evict(key);
remotecache.evict(key);
}
@override
public void clear() {
localcache.clear();
remotecache.clear();
}
}
九、生产环境最佳实践
9.1 应用配置管理
9.1.1 多环境配置
# application.yml (基础配置)
spring:
application:
name: order-service
profiles:
active: @spring.profiles.active@
# 日志配置
logging:
level:
com.example: info
org.springframework.web: info
org.hibernate.sql: warn
pattern:
console: "%d{yyyy-mm-dd hh:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file: "%d{yyyy-mm-dd hh:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/application.log
logback:
rollingpolicy:
max-file-size: 10mb
max-history: 30
# application-dev.yml (开发环境)
server:
port: 8080
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.driver
username: sa
password:
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
h2:
console:
enabled: true
path: /h2-console
logging:
level:
com.example: debug
org.springframework.web: debug
# application-prod.yml (生产环境)
server:
port: 8080
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
min-response-size: 1024
tomcat:
max-connections: 10000
max-threads: 200
min-spare-threads: 10
connection-timeout: 5000
spring:
datasource:
hikari:
maximum-pool-size: 20
minimum-idle: 10
jpa:
hibernate:
ddl-auto: validate
show-sql: false
redis:
host: ${redis_host:localhost}
port: ${redis_port:6379}
password: ${redis_password:}
timeout: 2000ms
lettuce:
pool:
max-active: 20
max-idle: 10
min-idle: 5
# 生产环境日志配置
logging:
level:
com.example: info
org.springframework.web: warn
org.hibernate: warn
logstash:
enabled: true
host: ${logstash_host}
port: ${logstash_port}
9.2 监控告警配置
9.2.1 prometheus监控配置
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
alerting:
alertmanagers:
- static_configs:
- targets:
- alertmanager:9093
rule_files:
- "alert_rules.yml"
scrape_configs:
- job_name: 'spring-boot-app'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['app:8080']
labels:
application: 'order-service'
environment: 'production'
- job_name: 'postgres'
static_configs:
- targets: ['postgres-exporter:9187']
- job_name: 'redis'
static_configs:
- targets: ['redis-exporter:9121']
9.2.2 告警规则配置
# alert_rules.yml
groups:
- name: spring_boot_alerts
rules:
- alert: higherrorrate
expr: rate(http_server_requests_seconds_count{status=~"5..",uri!~".*actuator.*"}[5m]) > 0.05
for: 2m
labels:
severity: critical
team: backend
annotations:
summary: "高错误率报警"
description: "{{ $labels.instance }}的错误率超过5% (当前值: {{ $value }})"
- alert: highresponsetime
expr: histogram_quantile(0.95, rate(http_server_requests_seconds_bucket[5m])) > 1
for: 5m
labels:
severity: warning
team: backend
annotations:
summary: "高响应时间报警"
description: "{{ $labels.instance }}的95%响应时间超过1秒 (当前值: {{ $value }}s)"
- alert: servicedown
expr: up{job="spring-boot-app"} == 0
for: 1m
labels:
severity: critical
team: backend
annotations:
summary: "服务宕机报警"
description: "{{ $labels.instance }}服务已宕机"
- alert: highmemoryusage
expr: (sum(jvm_memory_used_bytes{area="heap"}) / sum(jvm_memory_max_bytes{area="heap"})) > 0.8
for: 5m
labels:
severity: warning
team: backend
annotations:
summary: "高内存使用率报警"
description: "{{ $labels.instance }}内存使用率超过80% (当前值: {{ $value }})"
- alert: highcpuload
expr: system_cpu_usage > 0.8
for: 5m
labels:
severity: warning
team: backend
annotations:
summary: "高cpu使用率报警"
description: "{{ $labels.instance }}cpu使用率超过80% (当前值: {{ $value }})"
十、学习路径规划
10.1 初学者入门路径(1-2周)
掌握spring boot基础
- 理解spring boot自动配置原理
- 掌握restful api设计原则
- 学习spring mvc注解使用
完成第一个crud项目
- 创建用户管理系统
- 实现增删改查接口
- 添加数据验证
10.2 进阶提升路径(3-4周)
深入spring生态
- 学习spring security实现安全控制
- 掌握spring data jpa高级特性
- 了解spring cache缓存机制
项目实战
- 实现电商系统核心模块
- 集成第三方服务(支付、短信)
- 添加api文档和单元测试
10.3 专家精通路径(2-3个月)
性能优化
- jvm调优实践
- 数据库查询优化
- 缓存策略设计
微服务架构
- spring cloud学习
- 服务注册与发现
- 分布式事务处理
生产实践
- docker容器化部署
- ci/cd流水线搭建
- 监控告警系统建设
通过以上系统学习路径,可以从spring boot新手逐步成长为restful api开发专家,掌握企业级应用开发的全套技能栈。
到此这篇关于从入门到精通详解java中的restful接口开发的文章就介绍到这了,更多相关java restful接口开发内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论