百度后的答案是:
springboot 注解加切面 后controller, service为null
报错问题:“springboot 注解加切面后controller, servise为null” 通常意味着在使用Spring Boot时,通过注解定义的切面成功创建了,但是与之相关联的Controller或Service组件没有被Spring容器正确注入。
可能的原因和解决方法:
组件扫描问题:确保Controller和Service类所在的包在Spring Boot应用的@SpringBootApplication注解或@ComponentScan注解的扫描路径之内。
切面优先级问题:如果使用了AspectJ来创建切面,并且切面的优先级设置不当,可能会导致在切面执行之后,Controller和Service还没有完成依赖注入。可以通过提升切面的优先级来解决这个问题。
切面实现问题:检查切面的实现代码,确保没有在切面中直接通过new关键字来创建Controller或Service的实例,这样会导致Spring容器控制的实例化过程被绕开,从而引发空指针异常。
代理模式问题:Spring AOP默认使用的是代理模式,如果在Service或Controller中直接调用了自己的方法,可能导致此时的调用并没有被AOP代理拦截,因此没有经过切面处理的方法内部调用会导致注入的bean为null。
配置问题:检查Spring Boot的配置文件,确保没有错误配置导致组件创建或注入失败。
版本兼容问题:确保Spring Boot的版本和其他Spring组件的版本之间是兼容的,有时候版本不匹配也会导致组件无法正常注入。
解决方法通常涉及检查和调整配置,确保Spring的组件扫描路径正确,切面优先级设置适当,并且避免在不适当的上下文中直接实例化Bean。如果问题依然存在,可以通过增加日志输出或使用调试工具来进一步诊断问题。
使用上述方法后仍然存在问题。
修改切面页面注解 @Aspect为@EnableAspectJAutoProxy后问题解决。
但是在切面上加@EnableAspectJAutoProxy虽然可以解决service调用时为null的问题,但是切面不起作用了。查阅资料后@EnableAspectJAutoProxy应该加在配置管理的代码中,也就是扫描包的文件。在切面文件中加@Aspect后,切面才可以起作用,最后查找原因,原来是使用切面的页面Controller上有方法上调用了final关键字,需要把所有的方法上的final都去掉,调用的方法才不会报null,同时需要注意的是方法不能是private类型
切面页面如下 LimitAspect.java
package com.turingoal.tms.config;
import com.google.common.collect.Maps;
import com.google.common.util.concurrent.RateLimiter;
import com.turingoal.common.exception.TgException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
@Slf4j
@Aspect
@Component
public class LimitAspect {
private final Map<String, RateLimiter> limitMap = Maps.newConcurrentMap();
@Around("@annotation(Limit)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = (MethodSignature)pjp.getSignature();
Method method = signature.getMethod();
//拿limit的注解
Limit limit = method.getAnnotation(Limit.class);
if (limit != null) {
//key作用:不同的接口,不同的流量控制
String key=limit.key();
RateLimiter rateLimiter;
//验证缓存是否有命中key
if (!limitMap.containsKey(key)) {
// 创建令牌桶
rateLimiter = RateLimiter.create(limit.permitsPerSecond(),limit.timeout(),limit.timeunit());
limitMap.put(key, rateLimiter);
log.info("新建了令牌桶={},容量={}",key,limit.permitsPerSecond());
}
rateLimiter = limitMap.get(key);
// 拿令牌
boolean acquire = rateLimiter.tryAcquire();
// 拿不到命令,直接返回异常提示
if (!acquire) {
log.debug("令牌桶={},获取令牌失败",key);
throw new TgException(limit.msg());
}
}
return pjp.proceed();
}
}
注解Limit
package com.turingoal.tms.config;
import org.junit.jupiter.api.Order;
import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Order(1)
public @interface Limit {
// 资源key
String key() default "";
// 最多访问次数
int permitsPerSecond();
// 时间
long timeout();
// 时间类型
TimeUnit timeunit() default TimeUnit.MILLISECONDS;
// 提示信息
String msg() default "系统繁忙,请稍后再试";
}
controller调用的方法:切记返回的方法上不能加final
@Limit(key = "findToolItems", permitsPerSecond = 1, timeout = 1000, msg = "当前查询较频繁,请稍后再试!")
@RequestMapping(value = "/findToolItems.gsp", method = { RequestMethod.GET, RequestMethod.POST })
@Operation(tags = {"XXX"}, summary = "查询[XXX]", description = "通过条件查询[XXX项]")
public ResponseBean findItems(final ToolItemQuery query) throws TgException {
query.setIsWorkwear(2);
Page<Entity> data = service.findItems(query);
return ResponseBean.page(data);
}