Spring AOP
AOP(Aspect Oriented Program, 面向切面编程)把业务功能分为核心, 非核心两部分
核心: 用户登录, 数据增删
非核心: 性能统计, 日志, 事务管理
在AOP的思想里, 非核心业务功能被定义为切面, 核心业务功能和切面功能先被分别进行独立开发, 然后把切面功能和核心业务功能”编织”在一起. aop将那些与业务无关, 却为业务模块所共同调用的逻辑封装起来, 一边减少系统的重复代码, 降低模块的耦合度, 利于未来的拓展和维护.
相关概念:
- 切入点(pointcut): 在哪些类, 哪些方法上切入
- 通知(advice): 在方法前, 方法后, 方法前后做什么
- 切面(aspect): 切面=切入点+通知, 即在什么时机, 什么地方, 做什么
- 织入(weaving): 把切面加入对象, 并创建出代理对象的过程
环绕通知: aop中最强大, 灵活的通知, 它集成了前置和后置的通知, 保留了连接点原有的方法
使用示例:
1.依赖<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</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); } }
- 定义切点
/
@Pointcut(“execution( top.xinzhang0618.springboot.demo.AopLogController.testLog(..)) ||”
测试如下:
```java
@RequestMapping("/log")
@RestController
public class AopLogController {
@GetMapping("/test")
public String testLog() {
return "hello! aoplog!";
}
@GetMapping("/test2")
public String testLog2() {
return "hello! aoplog2!";
}
@GetMapping("/test3")
public String testLog3() throws Exception {
throw new Exception("测试日志抛异常");
}
}
----------------------
2020-05-19 13:10:08.330 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : URL: /log/test
2020-05-19 13:10:08.330 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : HTTP方法: GET
2020-05-19 13:10:08.331 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : IP地址: 127.0.0.1
2020-05-19 13:10:08.332 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : 类的方法: top.xinzhang0618.springboot.demo.AopLogController.testLog
2020-05-19 13:10:08.332 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : 参数: null
2020-05-19 13:10:08.337 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : 应答值: hello! aoplog!
2020-05-19 13:10:08.338 INFO 3776 --- [nio-9999-exec-1] top.xinzhang0618.springboot.demo.AopLog : 费时: 7
2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : URL: /log/test2
2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : HTTP方法: GET
2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : IP地址: 127.0.0.1
2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : 类的方法: top.xinzhang0618.springboot.demo.AopLogController.testLog2
2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : 参数: null
2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : 应答值: hello! aoplog2!
2020-05-19 13:10:54.463 INFO 3776 --- [nio-9999-exec-3] top.xinzhang0618.springboot.demo.AopLog : 费时: 0
2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : URL: /log/test3
2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : HTTP方法: GET
2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : IP地址: 127.0.0.1
2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : 类的方法: top.xinzhang0618.springboot.demo.AopLogController.testLog3
2020-05-19 13:11:09.512 INFO 3776 --- [nio-9999-exec-5] top.xinzhang0618.springboot.demo.AopLog : 参数: null
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注解)
过滤器(拦截器)示例:
// 如果有多个filter, 则order越小越早被执行
@Order(1)
@WebFilter(filterName = "FilterDemo", urlPatterns = "/*")
public class FilterDemo 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("测试过滤器");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
监听器示例: (servlet中的监听器有3种类型, 这里不详讲)
@WebListener
public class ListenerDemo implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext初始化");
System.out.println(sce.getServletContext().getServerInfo());
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext销毁");
}
}
自动配置
借助AutoConfigurationImportSelector调用SpringFactoriesLoader的loadFactoryNames方法, 从classpath中寻找所有的META-INF/spring.factories配置文件(spring.factories配置了自动装载的类); 然后, 再借助AutoConfigurationImportSelector, 将所有符合条件的@configuration配置都加载到ioc容器中
元注解
@Target, 告诉java自定义注解放的地方
属性值如下:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
@Retention, 用于说明自定义注解的生命周期
属性值如下:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
例如@Override, @SuppressWarinnings
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
@Inherited , 表明被标注的类型是可以被继承的
@Documented, 表示是否将注解信息添加在java文档中
@interface, 用来声明一个注解
自定义注解示例:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface MyTestAnnotation {
String value();
}
示例2:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ConditionalOnProperty(name = "oms.consumer.translate.order.enabled", havingValue = "true")
public @interface TranslatorOrderCondition {
}
自定义注解+切面:
@Aspect
@Component
public class TestAnnotationAspect {
/**
* 拦截被MyTestAnnotation注解的方法, 如果需要拦截指定包, 指定规则名称的方法, 则可以使用表达式execution(..)
*/
@Pointcut("@annotation(top.xinzhang0618.springboot.demo.MyTestAnnotation)")
public void myAnnotationPointCut() {
}
@Before("myAnnotationPointCut()")
public void before(JoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyTestAnnotation annotation = method.getAnnotation(MyTestAnnotation.class);
System.out.println("注解参数为: " + annotation.value());
}
}
补充: 这里注意
- 若注解使用在类上, 使用@within
- 若注解使用在方法上, 使用@annotation
异常处理
springboot提供了一个默认处理异常的映射—BasicErrorController
自定义错误处理器示例:
@RestController
public class TestErrorController implements ErrorController {
@Override
public String getErrorPath() {
return null;
}
/**
* 要覆盖error映射
*/
@RequestMapping("/error")
public Map<String, Object> handleError() {
HashMap<String, Object> map = new HashMap<>(2);
map.put("code", 404);
map.put("msg", "不存在");
return map;
}
}
自定义业务异常类+全局捕获异常
控制器通知: @ControllerAdvice或@RestControllerAdvice, 将所有的控制器作为一个切面, 可以对异常进行全局同意处理, 默认对所有的controller有效, 如果要限定生效范围, 则可以使用ControllerAdvice支持的限定范围方式
- 按注解: @ControllerAdvice(annotations = RestController.class)
- 按包名: @ControllerAdvice(“top.xinzhang0618.springboot.demo”)
- 按类型: @ControllerAdvice(assignableTypes = {AopLogController.class})
业务异常类实例:
public class BizException extends RuntimeException {
private Integer code;
public BizException(Integer code, String message) {
super(message);
this.code = code;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
}
全局捕获异常示例:
@ControllerAdvice
public class BizExceptionHandler {
@ResponseBody
@ExceptionHandler(BizException.class)
public Map<String, Object> bizExceptionHandle(BizException e) {
HashMap<String, Object> map = new HashMap<>(2);
map.put("code", e.getCode());
map.put("msg", e.getMessage());
// 此处可以添加日志记录等等
return map;
}
}
测试:
@GetMapping("/exception")
public String testExceptionHandler() throws Exception {
throw new BizException(11, "测试异常全局处理");
}
-----------
结果: {"msg":"测试异常全局处理","code":11}