Why Unified Controller Interfaces Matter: Best Practices and AOP in Java
This article explains common pitfalls in Java controller interface design—such as inconsistent return formats, missing failure handling, and inappropriate parameters—and demonstrates how a unified ResultBean response combined with AOP logging and exception handling can improve code readability, maintainability, and testability.
Common Interface Definition Issues
In daily development we often define various APIs, but many teams make recurring mistakes:
Inconsistent return format : the same endpoint sometimes returns an array, sometimes a single object, or mixes JSON, Map, and plain objects.
Ignoring failure scenarios : only success cases are considered, leading to costly refactoring when errors appear.
Unrelated input parameters : parameters like language or user ID are passed explicitly instead of being obtained from the session.
Complex input parameters : raw JSON strings are used as method arguments, reducing readability.
Missing required return data : create‑operations return only a boolean instead of the newly created object's identifier.
Bad Example 1 – Inconsistent Return Types
@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang) {
// ...
try {
boolean result = configService.delete(id, lang);
return result; // returns boolean on success, string on failure – a big no‑no
} catch (Exception e) {
log.error(e);
return e.toString();
}
}Bad Example 2 – No Failure Handling
@PostMapping("/update")
public void update(long id, Object xxx) {
// no return, no error handling – forces later refactoring
}Bad Example 3 – Unrelated Parameters
@PostMapping("/delete")
public Map<String, Object> delete(long id, String lang, String userId) {
// passing userId directly is a serious design flaw
}Bad Example 4 – Complex JSON Parameter
@PostMapping("/update")
public Map<String, Object> update(long id, String jsonStr) {
// raw JSON string makes the method hard to read and test
}Bad Example 5 – Missing Return Data
@PostMapping("/add")
public boolean add(Object xxx) {
return configService.add(); // should return the new object's ID instead of a plain boolean
}Controller Specification Guidelines
All controller methods must return a unified ResultBean (or PageResultBean for pagination). ResultBean / PageResultBean are controller‑only objects and must not be passed to downstream services.
Convert raw request data to proper Java beans in the controller; never pass raw Map, JSON or HttpServletRequest/Response to service layers.
Avoid logging inside controllers; let AOP handle logging and exception handling.
ResultBean Definition (Simplified)
@Data
public class ResultBean<T> implements Serializable {
private static final long serialVersionUID = 1L;
public static final int SUCCESS = 0;
public static final int FAIL = 1;
public static final int NO_LOGIN = -1;
public static final int NO_PERMISSION = 2;
private int code = SUCCESS;
private String msg = "success";
private T data;
public ResultBean() {}
public ResultBean(T data) { this.data = data; }
public ResultBean(Throwable e) {
this.msg = e.toString();
this.code = FAIL;
}
}AOP for Logging and Exception Wrapping
public class ControllerAOP {
private static final Logger logger = LoggerFactory.getLogger(ControllerAOP.class);
public Object handlerControllerMethod(ProceedingJoinPoint pjp) {
long start = System.currentTimeMillis();
ResultBean<?> result;
try {
result = (ResultBean<?>) pjp.proceed();
logger.info(pjp.getSignature() + " use time:" + (System.currentTimeMillis() - start));
} catch (Throwable e) {
result = handlerException(pjp, e);
}
return result;
}
private ResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) {
ResultBean<?> result = new ResultBean<>();
if (e instanceof CheckException) {
result.setMsg(e.getLocalizedMessage());
result.setCode(ResultBean.FAIL);
} else if (e instanceof UnloginException) {
result.setMsg("Unlogin");
result.setCode(ResultBean.NO_LOGIN);
} else {
logger.error(pjp.getSignature() + " error", e);
result.setMsg(e.toString());
result.setCode(ResultBean.FAIL);
}
return result;
}
}AOP Configuration (XML)
<aop:aspectj-autoproxy/>
<bean id="controllerAop" class="xxx.common.aop.ControllerAOP"/>
<aop:config>
<aop:aspect id="myAop" ref="controllerAop">
<aop:pointcut id="target" expression="execution(public xxx.common.beans.ResultBean *(..))"/>
<aop:around method="handlerControllerMethod" pointcut-ref="target"/>
</aop:aspect>
</aop:config>Benefits
Using a unified ResultBean format enables consistent API responses, simplifies AOP‑based logging and exception handling, and improves code readability and testability. Adopting these conventions reduces unnecessary refactoring and helps teams focus on business logic rather than repetitive boilerplate.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
