AOP 注解详解
配置
对负责扫描组件的配置文件类(@Configuration) 添加 @EnableAspectJAutoProxy
注解,启用 AOP 功能。
**默认通过 JDK 动态代理方式进行织入。**但必须代理一个实现接口的类,否则会抛出异常。
注解改为 @EnableAspectJAutoProxy(proxyTargetClass = true)
:
通过 cglib 的动态代理方式进行织入。但如果拓展类的方法被 final 修饰,则织入无效。
@Configuration
@ComponentScan(basePackageClasses = {com.company.project.service.Meal.class})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig {
}Copy to clipboardErrorCopied
复制代码
切面
对组件类(@component) 添加 @Aspect
注解,表示该类为切面类。
增强类型
前置通知
切面方法注解 @Before
表示目标方法调用前,执行该切面方法。
@Before("execution(* com.company.project.service.Meal.eat(..))")
public void cook() {
System.out.println("cook");
}Copy to clipboardErrorCopied
复制代码
后置通知
切面方法注解 @After
表示目标方法返回或抛出异常后,执行该切面方法。
切面方法注解 @AfterReturning
只在目标方法返回后,执行该切面方法。
切面方法注解 @AfterThrowing
只在目标方法抛出异常后,执行该切面方法。
@AfterReturning("execution(* com.company.project.service.Meal.eat(..))")
public void clean() {
System.out.println("clean");
}Copy to clipboardErrorCopied
复制代码
环绕通知
切面方法注解 @Around
表示切面方法执行过程中,执行目标方法。
传入参数为 ProceedingJoinPoint 类对象,表示目标方法。在切面方法中调用其 proceed 方法来执行。
@Around("execution(* com.company.project.service.Meal.eat(..))")
public void party(ProceedingJoinPoint pj) {
try {
System.out.println("cook");
pj.proceed();
System.out.println("clean");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}Copy to clipboardErrorCopied
复制代码
切点声明
在切面方法中需要声明切面方法要切入的目标方法,execution 指示器是我们定义切点时最主要使用的指示器。
格式为: execution(返回数据类型 路径.类.方法(传入参数类型))
还有一些其他指示器:
如参数 execution(String com.company.project.service.test1.IBuy.buy(double)) && args(price) && bean(girl)
要求返回类型为 String ;参数类型为 double ;参数名为 price ;调用目标方法的 bean 名称为 girl 。
简化代码
对于类中要频繁要切入的目标方法,我们可以使用 @Pointcut
注解声明切点表达式,简化代码。
@Aspect
@Component
public class EatPlus {
@Pointcut("execution(* com.company.project.service.Meal.eat(..))")
public void point(){}
@Before("point()")
public void cook() {
System.out.println("cook");
}
@Around("point()")
public void party(ProceedingJoinPoint pj) {
try {
System.out.println("cook");
pj.proceed();
System.out.println("clean");
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
@Pointcut("execution(String com.company.project.service.Meal.eat(double)) && args(price) && bean(people)")
public void point2(double price) {
}
@Around("point2(price)")
public String pay(ProceedingJoinPoint pj, double price){
try {
pj.proceed();
if (price > 100) {
System.out.println("can not afford");
return "没有购买";
}
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return "购买";
}
}Copy to clipboardErrorCopied
复制代码
常用 AOP
异常处理
@RestControllerAdvice
public class ControllerExceptionHandler {
@ExceptionHandler(Throwable.class)
public ResultBean handleOtherException(Throwable e) {
String message = String.format("错误=%s,位置=%s", e.toString(), e.getStackTrace()[0].toString());
return ResultBean.error(ErrorCode.UNKNOWN_ERROR.getErrorCode(), message);
}
@ExceptionHandler(StreamPlatformException.class)
public ResultBean handleVenusException(StreamPlatformException e) {
return ResultBean.error(e.getErrorCode(), e.getMessageToUser());
}
@ExceptionHandler(FormValidationException.class)
public ResultBean handleFormValidationException(FormValidationException e) {
StringBuilder message = new StringBuilder();
e.getResult().getAllErrors().forEach(objectError -> {
if (objectError instanceof FieldError) {
FieldError fieldError = (FieldError) objectError;
message.append("参数").append(fieldError.getField())
.append("错误值为").append(fieldError.getRejectedValue())
.append(fieldError.getDefaultMessage());
} else {
message.append(objectError.getDefaultMessage());
}
});
return ResultBean.error(ErrorCode.PARAMETER_VALIDATION_ERROR.getErrorCode(),
String.format(ErrorCode.PARAMETER_VALIDATION_ERROR.getMessage(), message));
}
}Copy to clipboardErrorCopied
复制代码
拦截器
Java Web 中,在执行 Controller 方法前后对 Controller 请求进行拦截和处理。依赖于 web 框架,在 Spring 配置。在实现上基于 Java 的反射机制。
Java Web 中,在 request/response 传入 Servlet 前,过滤信息或设置参数。依赖于 servlet 容器,在 web.xml 配置。在实现上基于函数回调。
两者常用于修改字符编码、删除无用参数、登录校验等。Spring 框架中优先使用拦截器:功能接近、使用更加灵活。
拦截器配置
// 在配置中引入拦截器对象(单独编写拦截器类)
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 导入拦截器对象,默认拦截全部
InterceptorRegistration addInterceptor = registry.addInterceptor(new myInterceptor());
// 排除配置
addInterceptor.excludePathPatterns("/error","/login","/user/login");
addInterceptor.excludePathPatterns("/asserts/**");
addInterceptor.excludePathPatterns("/webjars/**");
addInterceptor.excludePathPatterns("/public/**");
// 拦截配置
addInterceptor.addPathPatterns("/**");
}Copy to clipboardErrorCopied
复制代码
拦截器类通过实现 HandlerInterceptor 接口或者继承 HandlerInterceptorAdapter 类。
// 定义拦截器
public class myInterceptor extends HandlerInterceptorAdapter {
// Session key
public final static String SESSION_KEY = "user";
// preHandle 预处理
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 检查 session
HttpSession session = request.getSession();
if (session.getAttribute(SESSION_KEY) != null) return true;
// 重定向到登录页面
request.setAttribute("message","登录失败,请先输入用户名和密码。");
request.getRequestDispatcher("login").forward(request,response);
return false;
}
// postHandle 善后处理
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) {
System.out.println("INTERCEPTOR POSTHANDLE CALLED");
}
}Copy to clipboardErrorCopied
复制代码
过滤器类通过继承 Filter 类实现,直接添加注解即可。
@Component // 作为组件,交给容器处理
@ServletComponentScan // 扫描组件
@WebFilter(urlPatterns = "/login/*",filterName = "loginFilter") // 设定过滤路径和名称
@Order(1) // 设定优先级(值小会优先执行)
public class LoginFilter implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 过滤器前执行
System.out.println("before");
// 执行内部逻辑
filterChain.doFilter(servletRequest,servletResponse);
// 过滤器后执行
System.out.println("after");
}
@Override
public void destroy() {
}
}Copy to clipboardErrorCopied
复制代码
调用顺序
评论