Spring AOP

AOP(Aspect Oriented Program, 面向切面编程)把业务功能分为核心, 非核心两部分
核心: 用户登录, 数据增删
非核心: 性能统计, 日志, 事务管理

在AOP的思想里, 非核心业务功能被定义为切面, 核心业务功能和切面功能先被分别进行独立开发, 然后把切面功能和核心业务功能”编织”在一起. aop将那些与业务无关, 却为业务模块所共同调用的逻辑封装起来, 一边减少系统的重复代码, 降低模块的耦合度, 利于未来的拓展和维护.

相关概念:

  • 切入点(pointcut): 在哪些类, 哪些方法上切入
  • 通知(advice): 在方法前, 方法后, 方法前后做什么
  • 切面(aspect): 切面=切入点+通知, 即在什么时机, 什么地方, 做什么
  • 织入(weaving): 把切面加入对象, 并创建出代理对象的过程
  • 环绕通知: aop中最强大, 灵活的通知, 它集成了前置和后置的通知, 保留了连接点原有的方法


    使用示例:
    1.依赖

    1. <dependency>
    2. <groupId>org.springframework.boot</groupId>
    3. <artifactId>spring-boot-starter-aop</artifactId>
    4. </dependency>

    2.配置 ```java package top.xinzhang0618.springboot.demo;

import javax.servlet.http.HttpServletRequest; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes;

/**

  • AopLog *
  • @author xinzhang
  • @author Shenzhen Greatonce Co Ltd
  • @version 2020/5/19 */ @Aspect @Component public class AopLog {

    private Logger logger = LoggerFactory.getLogger(AopLog.class); ThreadLocal startTime = new ThreadLocal<>();

    /**

    • 定义切点 / @Pointcut(“execution( top.xinzhang0618.springboot.demo.AopLogController.testLog(..)) ||”
      • “execution(* top.xinzhang0618.springboot.demo.AopLogController.testLog2(..)) ||”
      • “execution(* top.xinzhang0618.springboot.demo.AopLogController.testLog3(..))”) public void aopWebLog() {

    }

    @Before(“aopWebLog()”) public void doBefore(JoinPoint joinPoint) throws Throwable { startTime.set(System.currentTimeMillis()); ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); logger.info(“URL: “ + request.getRequestURI().toString()); logger.info(“HTTP方法: “ + request.getMethod()); logger.info(“IP地址: “ + request.getRemoteAddr()); logger.info(“类的方法: “ + joinPoint.getSignature().getDeclaringTypeName() + “.” + joinPoint.getSignature().getName()); logger.info(“参数: “ + request.getQueryString()); }

    @AfterReturning(pointcut = “aopWebLog()”, returning = “retObject”) public void doAfterReturning(Object retObject) throws Throwable { logger.info(“应答值: “ + retObject); logger.info(“费时: “ + (System.currentTimeMillis() - startTime.get())); }

    @AfterThrowing(pointcut = “aopWebLog()”, throwing = “ex”) public void addAfterThrowingLogger(JoinPoint joinPoint, Exception ex) { logger.error(“执行抛异常”, ex); } }

  1. 测试如下:
  2. ```java
  3. @RequestMapping("/log")
  4. @RestController
  5. public class AopLogController {
  6. @GetMapping("/test")
  7. public String testLog() {
  8. return "hello! aoplog!";
  9. }
  10. @GetMapping("/test2")
  11. public String testLog2() {
  12. return "hello! aoplog2!";
  13. }
  14. @GetMapping("/test3")
  15. public String testLog3() throws Exception {
  16. throw new Exception("测试日志抛异常");
  17. }
  18. }
  19. ----------------------
  20. 2020-05-19 13:10:08.330 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : URL: /log/test
  21. 2020-05-19 13:10:08.330 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : HTTP方法: GET
  22. 2020-05-19 13:10:08.331 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : IP地址: 127.0.0.1
  23. 2020-05-19 13:10:08.332 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : 类的方法: top.xinzhang0618.springboot.demo.AopLogController.testLog
  24. 2020-05-19 13:10:08.332 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : 参数: null
  25. 2020-05-19 13:10:08.337 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : 应答值: hello! aoplog!
  26. 2020-05-19 13:10:08.338 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : 费时: 7
  27. 2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : URL: /log/test2
  28. 2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : HTTP方法: GET
  29. 2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : IP地址: 127.0.0.1
  30. 2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : 类的方法: top.xinzhang0618.springboot.demo.AopLogController.testLog2
  31. 2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : 参数: null
  32. 2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : 应答值: hello! aoplog2!
  33. 2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : 费时: 0
  34. 2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : URL: /log/test3
  35. 2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : HTTP方法: GET
  36. 2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : IP地址: 127.0.0.1
  37. 2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : 类的方法: top.xinzhang0618.springboot.demo.AopLogController.testLog3
  38. 2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : 参数: null
  39. 2020-05-19 13:11:09.523 ERROR 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : 执行 异常

IOC容器以及Servlet容器

IOC(Inversion of Control)容器, 是面向对象编程中的一种设计原则, 意为控制反转, 将程序中创建对象的控制权交给spring框架来管理, 以便降低计算机代码之间的耦合度.

控制反转的实质是获得依赖对象的过程被反转了. 这个过程由自身管理变为由IOC容器主动注入, 这正是ioc实现的方式之一: 依赖注入(dependency injection, DI), 由IOC容器在运行期间动态的将某种依赖关系注入到对象之中.

IOC的实现方法只要有两种:
1.依赖注入
IOC容器通过类型或名称等信息将不同对象注入不同属性中, 组件不做定位查询, 只提供普通的java方法让容器去决定依赖关系

  • 设置注入(setter injection): 让ioc容器调用注入所依赖类型的对象
  • 接口注入(interface injection): 实现特定接口
  • 构造注入(constructor injection): 实现特定的构造函数
  • 基于注解, 例如使用@Autowired

2.依赖查找
通过调用容器提供的回调接口和上下文环境来获取对象, 在获取时需要提供相关的配置文件路径, key等信息来确定获取对象的状态, 依赖查找通常有两个方法-依赖拖拽(DP)和上下文化依赖查找(CDL)

servlet容器是javax.servlet包中定义的一个接口
springboot的核心控制器dispatcherServlet会处理所有的请求, 如果自定义servlet, 则需要进行注册, 以便dispatcherServlet核心控制器知道他的所用, 以及处理请求uri-pattern.

过滤器和监听器

简单demo:
(因为是自定义servlet, 因此在启动类上都要加上@ServletComponentScan注解)
过滤器(拦截器)示例:

  1. // 如果有多个filter, 则order越小越早被执行
  2. @Order(1)
  3. @WebFilter(filterName = "FilterDemo", urlPatterns = "/*")
  4. public class FilterDemo implements Filter {
  5. @Override
  6. public void init(FilterConfig filterConfig) throws ServletException {
  7. }
  8. @Override
  9. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
  10. throws IOException, ServletException {
  11. System.out.println("测试过滤器");
  12. filterChain.doFilter(servletRequest, servletResponse);
  13. }
  14. @Override
  15. public void destroy() {
  16. }
  17. }

监听器示例: (servlet中的监听器有3种类型, 这里不详讲)

  1. @WebListener
  2. public class ListenerDemo implements ServletContextListener {
  3. @Override
  4. public void contextInitialized(ServletContextEvent sce) {
  5. System.out.println("ServletContext初始化");
  6. System.out.println(sce.getServletContext().getServerInfo());
  7. }
  8. @Override
  9. public void contextDestroyed(ServletContextEvent sce) {
  10. System.out.println("ServletContext销毁");
  11. }
  12. }

自动配置

借助AutoConfigurationImportSelector调用SpringFactoriesLoader的loadFactoryNames方法, 从classpath中寻找所有的META-INF/spring.factories配置文件(spring.factories配置了自动装载的类); 然后, 再借助AutoConfigurationImportSelector, 将所有符合条件的@configuration配置都加载到ioc容器中
image.png

元注解

@Target, 告诉java自定义注解放的地方
属性值如下:

  1. public enum ElementType {
  2. /** Class, interface (including annotation type), or enum declaration */
  3. TYPE,
  4. /** Field declaration (includes enum constants) */
  5. FIELD,
  6. /** Method declaration */
  7. METHOD,
  8. /** Formal parameter declaration */
  9. PARAMETER,
  10. /** Constructor declaration */
  11. CONSTRUCTOR,
  12. /** Local variable declaration */
  13. LOCAL_VARIABLE,
  14. /** Annotation type declaration */
  15. ANNOTATION_TYPE,
  16. /** Package declaration */
  17. PACKAGE,
  18. /**
  19. * Type parameter declaration
  20. *
  21. * @since 1.8
  22. */
  23. TYPE_PARAMETER,
  24. /**
  25. * Use of a type
  26. *
  27. * @since 1.8
  28. */
  29. TYPE_USE
  30. }

@Retention, 用于说明自定义注解的生命周期
属性值如下:

  1. public enum RetentionPolicy {
  2. /**
  3. * Annotations are to be discarded by the compiler.
  4. 例如@Override, @SuppressWarinnings
  5. */
  6. SOURCE,
  7. /**
  8. * Annotations are to be recorded in the class file by the compiler
  9. * but need not be retained by the VM at run time. This is the default
  10. * behavior.
  11. */
  12. CLASS,
  13. /**
  14. * Annotations are to be recorded in the class file by the compiler and
  15. * retained by the VM at run time, so they may be read reflectively.
  16. *
  17. * @see java.lang.reflect.AnnotatedElement
  18. */
  19. RUNTIME
  20. }

@Inherited , 表明被标注的类型是可以被继承的
@Documented, 表示是否将注解信息添加在java文档中
@interface, 用来声明一个注解

自定义注解示例:

  1. @Target({ElementType.METHOD, ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Component
  5. public @interface MyTestAnnotation {
  6. String value();
  7. }

示例2:

  1. @Target({ElementType.TYPE, ElementType.METHOD})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @ConditionalOnProperty(name = "oms.consumer.translate.order.enabled", havingValue = "true")
  5. public @interface TranslatorOrderCondition {
  6. }

自定义注解+切面:

  1. @Aspect
  2. @Component
  3. public class TestAnnotationAspect {
  4. /**
  5. * 拦截被MyTestAnnotation注解的方法, 如果需要拦截指定包, 指定规则名称的方法, 则可以使用表达式execution(..)
  6. */
  7. @Pointcut("@annotation(top.xinzhang0618.springboot.demo.MyTestAnnotation)")
  8. public void myAnnotationPointCut() {
  9. }
  10. @Before("myAnnotationPointCut()")
  11. public void before(JoinPoint joinPoint) throws Throwable {
  12. MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  13. Method method = signature.getMethod();
  14. MyTestAnnotation annotation = method.getAnnotation(MyTestAnnotation.class);
  15. System.out.println("注解参数为: " + annotation.value());
  16. }
  17. }

补充: 这里注意

  • 若注解使用在类上, 使用@within
  • 若注解使用在方法上, 使用@annotation

Spring Boot进阶 - 图2

异常处理

springboot提供了一个默认处理异常的映射—BasicErrorController
自定义错误处理器示例:

  1. @RestController
  2. public class TestErrorController implements ErrorController {
  3. @Override
  4. public String getErrorPath() {
  5. return null;
  6. }
  7. /**
  8. * 要覆盖error映射
  9. */
  10. @RequestMapping("/error")
  11. public Map<String, Object> handleError() {
  12. HashMap<String, Object> map = new HashMap<>(2);
  13. map.put("code", 404);
  14. map.put("msg", "不存在");
  15. return map;
  16. }
  17. }

自定义业务异常类+全局捕获异常

控制器通知: @ControllerAdvice或@RestControllerAdvice, 将所有的控制器作为一个切面, 可以对异常进行全局同意处理, 默认对所有的controller有效, 如果要限定生效范围, 则可以使用ControllerAdvice支持的限定范围方式

  • 按注解: @ControllerAdvice(annotations = RestController.class)
  • 按包名: @ControllerAdvice(“top.xinzhang0618.springboot.demo”)
  • 按类型: @ControllerAdvice(assignableTypes = {AopLogController.class})

业务异常类实例:

  1. public class BizException extends RuntimeException {
  2. private Integer code;
  3. public BizException(Integer code, String message) {
  4. super(message);
  5. this.code = code;
  6. }
  7. public Integer getCode() {
  8. return code;
  9. }
  10. public void setCode(Integer code) {
  11. this.code = code;
  12. }
  13. }

全局捕获异常示例:

  1. @ControllerAdvice
  2. public class BizExceptionHandler {
  3. @ResponseBody
  4. @ExceptionHandler(BizException.class)
  5. public Map<String, Object> bizExceptionHandle(BizException e) {
  6. HashMap<String, Object> map = new HashMap<>(2);
  7. map.put("code", e.getCode());
  8. map.put("msg", e.getMessage());
  9. // 此处可以添加日志记录等等
  10. return map;
  11. }
  12. }

测试:

  1. @GetMapping("/exception")
  2. public String testExceptionHandler() throws Exception {
  3. throw new BizException(11, "测试异常全局处理");
  4. }
  5. -----------
  6. 结果: {"msg":"测试异常全局处理","code":11}