面向切面编程

1.什么是AOP

AOP (Aspect Oriented Program,面向切面编程)把业务功能分为核心、非核心两部分。

  • 核心业务功能:用户登录、增加数据、删除数据。
  • 非核心业务功能:性能统计、日志、事务管理。

在Spring的面向切面编程(AOP)思想里,非核心业务功能被定义为切面。核心业务功能和切面功能先被分别进行独立开发,然后把切面功能和核心业务功能“编织”在一起,这就是AOP

  AOP将那 些与业务无关,却为业务模块所共同调用的逻辑封装起来,以便减少系统的重复代码,降低模块间的耦合度,利于未来的拓展和维护。这正是AOP的目的,它是Spring最为重要的功能之一,被广泛使用。

2.AOP中的概念

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

实例:用AOP方式管理日志

1.编写AOP注解日志类

package com.itheima.domain;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.ServletRequest;
import javax.servlet.https.httpsServletRequest;

@Aspect
//使之成为切面类
@Component
//把切面类加入Ioc容器中
public class AopLog {
private Logger logger = LoggerFactory.getLogger(this.getClass());
ThreadLocal<Long> startTime = new ThreadLocal<Long>();
//线程局部变量,用于解决多线程中相同变量的访问冲突问题
@Pointcut("execution(public * com.itheima.controller.AopLogController.avoid())")
//定义切点
public void aopWebLog(){
System.out.println("切点");
}
@Before("aopWebLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable{
startTime.set(System.currentTimeMillis());
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
//接收到请求,记录请求内容
httpsServletRequest request = attributes.getRequest();
logger.info("URL: " + request.getRequestURI().toString());
logger.info("https方法:"+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注解:
  1. execution:一般用于指定方法的执行,用的最多。
  2. within:指定某些类型的全部方法执行,也可用来指定一个包。
  3. this:Spring Aop是基于动态代理的,生成的bean也是一个代理对象,this就是这个代理对象,当这个对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
  4. target:当被代理的对象可以转换为指定的类型时,对应的切入点就是它了,Spring Aop将生效。
  5. args:当执行的方法的参数是指定类型时生效。
  6. @target:当代理的目标对象上拥有指定的注解时生效。
  7. @args:当执行的方法参数类型上拥有指定的注解时生效。
  8. @within:与@target类似,看官方文档和网上的说法都是@within只需要目标对象的类或者父类上有指定的注解,则@within会生效,而@target则是必须是目标对象的类上有指定的注解。而根据笔者的测试这两者都是只要目标类或父类上有指定的注解即可。
  9. @annotation:当执行的方法上拥有指定的注解时生效。
  10. reference pointcut:(经常使用)表示引用其他命名切入点,只有@ApectJ风格支持,Schema风格不支持
  11. bean:当调用的方法是指定的bean的方法时生效。(Spring AOP自己扩展支持的)

  Pointcut定义时,还可以使用&&、||、! 这三个运算。进行逻辑运算。可以把各种条件组合起来使用

ThreadLocal类:

  此类提供线程局部变量。这些变量不同于它们的正常对应变量,因为每个访问一个(通过它的get或set方法)的线程都有它自己的、独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段(例如,用户 ID 或事务 ID)。
  例如,下面的类生成每个线程本地的唯一标识符。线程的 id 在第一次调用ThreadId.get()时被分配,并且在后续调用中保持不变。

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);

// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
};

// Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}

例子:

public class ThreadLocalTest02 {

public static void main(String[] args) {

ThreadLocal<String> local = new ThreadLocal<>();

IntStream.range(0, 10).forEach(i -> new Thread(() -> {
local.set(Thread.currentThread().getName() + ":" + i);
System.out.println("线程:" + Thread.currentThread().getName() + ",local:" + local.get());
}).start());
}
}

输出结果:
线程:Thread-0,local:Thread-0:0
线程:Thread-1,local:Thread-1:1
线程:Thread-2,local:Thread-2:2
线程:Thread-3,local:Thread-3:3
线程:Thread-4,local:Thread-4:4
线程:Thread-5,local:Thread-5:5
线程:Thread-6,local:Thread-6:6
线程:Thread-7,local:Thread-7:7
线程:Thread-8,local:Thread-8:8
线程:Thread-9,local:Thread-9:9

  从结果可以看到,每一个线程都有自己的local 值,这就是TheadLocal的基本使用 。

  只要线程处于活动状态并且ThreadLocal实例可访问,每个线程都持有对其线程局部变量副本的隐式引用;在线程消失后,它的所有线程本地实例副本都将受到垃圾回收(除非存在对这些副本的其他引用)

  • 1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
  • 2、线程间数据隔离
  • 3、进行事务操作,用于存储线程事务信息。
  • 4、数据库连接,Session会话管理。
JoinPoint类:

  提供对连接点可用状态和有关它的静态信息的反射访问。此信息可使用特殊形式thisJoinPoint从建议正文中获得。此反射信息的主要用途是跟踪和记录应用程序。

  joinPoint.getSignature():获取当前切点方法,其值为String com.itheima.controller.AopLogController.avoid()

  joinPoint.getSignature().getDeclaringTypeName():获取切点方法所在类的类型,这相当于调用 getDeclaringType().getName(),但会缓存结果以提高效率。其值为:com.itheima.controller.AopLogController

  joinPoint.getSignature().getName():此签名的标识符部分。对于方法,这将返回方法名称,其值为:avoid()

ServletRequestAttributes:

  RequestAttributes:用于访问与请求关联的属性对象的抽象。支持访问请求范围的属性以及会话范围的属性,具有“全局会话”的可选概念。可以为任何类型的请求/会话机制实现,特别是 servlet 请求。

  ServletRequestAttributes为RequestAttributes接口的基于 Servlet 的实现。从 servlet 请求和 https 会话范围访问对象,“会话”和“全局会话”之间没有区别。

  

  ServletRequest:定义一个对象以向 servlet 提供客户端请求信息。 servlet 容器创建一个ServletRequest对象并将其作为参数传递给 servlet 的service方法。ServletRequest对象提供的数据包括参数名称和值、属性和输入流。扩展ServletRequest的接口可以提供额外的特定于协议的数据(例如,https 数据由javax.servlet.https.httpsServletRequest提供。

  httpsServletRequest:扩展ServletRequest接口以提供 https servlet 的请求信息。servlet 容器创建一个httpsServletRequest对象并将其作为参数传递给 servlet 的服务方法( doGet 、 doPost等)。

@Before和@AfterReturning注解

类似于使用@Before注解可以修饰Before增强处理,使用@AfterReturning可修饰AfterReturning增强处理,AfterReturning增强处理将在目标方法正常完成后被织入。

使用@AfterReturning注解可指定如下两个常用属性。

  1.    pointcut/value:这两个属性的作用是一样的,它们都属于指定切入点对应的切入[表达式](httpss://so.csdn.net/so/search?q=%E8%A1%A8%E8%BE%BE%E5%BC%8F&spm=1001.2101.3001.7020)。一样既可以是已有的切入点,也可直接定义切入点表达式。当指定了pointcut属性值后,value属性值将会被覆盖。
    
  2.    returning:该属性指定一个形参名,用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值。除此之外,在Advice方法中定义该形参(代表目标方法的返回值)时指定的类型,会限制目标方法必须返回指定类型的值或没有返回值。
    
@AfterThrowing注解

使用@AfterThrowing注解可以修饰AfterThrowing增强处理,AfterThrowing增强处理主要用于处理程序中未处理的异常。使用@AfterThrowing注解时可指定如下的常用属性:

  1.    pointcut/value:这两个属性的作用是一样的,它们都用于指定该切入点对应的切入[表达式](httpss://so.csdn.net/so/search?q=%E8%A1%A8%E8%BE%BE%E5%BC%8F&spm=1001.2101.3001.7020)。一样既可是一个已有的切入点,也可以直接定义切入点表达式。当指定了pointcut属性后,value属性值将会被覆盖。
    
  2.    throwing:该属性指定一个形参名,用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法抛出的异常。除此之外,在Advice方法中定义该参数时,指定的类型,会限制方法必须抛出指定类型的异常。
    
  • @Before:在切入点开始处切入内容。

  • @After:在切入点结尾处切入内容。

  • @AfterReturn:在切入点返回(return)内容之后切入内容,可以用来对处理返回值做 一些加工处理。

  • @Around:在切入点前后切入内容,并控制何时执行切入点自身的内容。

  • @AfterThrowing:用来处理当切入内容部分抛出异常之后的处理逻辑。

  • @Aspect:标记为切面类。

  • @Component:把切面类加入loC容器中,让Spring进行管理。

2.编写控制器用于测试

package com.itheima.controller;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AopLogController {
@GetMapping(value = "/aoptest")
public String avoid(){
Logger logger = LoggerFactory.getLogger(this.getClass());
logger.info("切点");
return "hello aop";
}
}

 看下访问后控制台打印信息

2022-07-16 17:21:49.785  INFO 1412 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-07-16 17:21:49.785 INFO 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-07-16 17:21:49.786 TRACE 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Detected org.springframework.web.multipart.support.StandardServletMultipartResolver@4886333
2022-07-16 17:21:49.786 TRACE 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Detected org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver@7451a4c0
2022-07-16 17:21:49.786 TRACE 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Detected org.springframework.web.servlet.theme.FixedThemeResolver@4f5dd299
2022-07-16 17:21:49.787 TRACE 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Detected DefaultRequestToViewNameTranslator
2022-07-16 17:21:49.787 TRACE 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Detected SessionFlashMapManager
2022-07-16 17:21:49.787 DEBUG 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2022-07-16 17:21:49.787 INFO 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
2022-07-16 17:21:49.805 TRACE 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/aoptest", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
2022-07-16 17:21:49.807 TRACE 1412 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to com.itheima.controller.AopLogController#avoid()
2022-07-16 17:21:49.813 TRACE 1412 --- [nio-8080-exec-1] o.s.web.method.HandlerMethod : Arguments: []
2022-07-16 17:21:49.815 INFO 1412 --- [nio-8080-exec-1] com.itheima.domain.AopLog : URL: /aoptest
2022-07-16 17:21:49.815 INFO 1412 --- [nio-8080-exec-1] com.itheima.domain.AopLog : https方法:GET
2022-07-16 17:21:49.815 INFO 1412 --- [nio-8080-exec-1] com.itheima.domain.AopLog : IP方法:0:0:0:0:0:0:0:1
2022-07-16 17:21:49.817 INFO 1412 --- [nio-8080-exec-1] com.itheima.domain.AopLog : 类的方法:com.itheima.controller.AopLogController.avoid
2022-07-16 17:21:49.817 INFO 1412 --- [nio-8080-exec-1] com.itheima.domain.AopLog : 参数:null
2022-07-16 17:21:49.824 INFO 1412 --- [nio-8080-exec-1] com.itheima.controller.AopLogController : 切点
2022-07-16 17:21:49.824 INFO 1412 --- [nio-8080-exec-1] com.itheima.domain.AopLog : 应答值:hello aop
2022-07-16 17:21:49.824 INFO 1412 --- [nio-8080-exec-1] com.itheima.domain.AopLog : 费时:9
2022-07-16 17:21:49.833 DEBUG 1412 --- [nio-8080-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Using 'text/html', given [text/html, application/xhtml+xml, image/avif, image/webp, application/xml;q=0.9, */*;q=0.8] and supported [text/plain, */*, text/plain, */*, application/json, application/*+json, application/json, application/*+json]
2022-07-16 17:21:49.834 TRACE 1412 --- [nio-8080-exec-1] m.m.a.RequestResponseBodyMethodProcessor : Writing ["hello aop"]
2022-07-16 17:21:49.841 TRACE 1412 --- [nio-8080-exec-1] s.w.s.m.m.a.RequestMappingHandlerAdapter : Applying default cacheSeconds=-1
2022-07-16 17:21:49.841 TRACE 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned.
2022-07-16 17:21:49.841 DEBUG 1412 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK, headers={masked}
2022-07-16 17:21:49.907 TRACE 1412 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : GET "/favicon.ico", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
2022-07-16 17:21:49.909 TRACE 1412 --- [nio-8080-exec-2] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped to HandlerExecutionChain with [ResourcehttpsRequestHandler ["classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/", "/"]] and 3 interceptors
2022-07-16 17:21:49.911 DEBUG 1412 --- [nio-8080-exec-2] o.s.w.s.r.ResourcehttpsRequestHandler : Resource not found
2022-07-16 17:21:49.911 TRACE 1412 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned.
2022-07-16 17:21:49.911 DEBUG 1412 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed 404 NOT_FOUND, headers={masked}
2022-07-16 17:21:49.913 TRACE 1412 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'
2022-07-16 17:21:49.914 TRACE 1412 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : 2 matching mappings: [{ [/error]}, { [/error], produces [text/html]}]
2022-07-16 17:21:49.914 TRACE 1412 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(httpsServletRequest)
2022-07-16 17:21:49.916 TRACE 1412 --- [nio-8080-exec-2] o.s.web.method.HandlerMethod : Arguments: [org.apache.catalina.core.ApplicationhttpsRequest@349cdda4]
2022-07-16 17:21:49.925 DEBUG 1412 --- [nio-8080-exec-2] o.s.w.s.m.m.a.httpsEntityMethodProcessor : Using 'application/json', given [image/avif, image/webp, */*] and supported [application/json, application/*+json, application/json, application/*+json]
2022-07-16 17:21:49.925 TRACE 1412 --- [nio-8080-exec-2] o.s.w.s.m.m.a.httpsEntityMethodProcessor : Writing [{timestamp=Sat Jul 16 17:21:49 CST 2022, status=404, error=Not Found, message=No message available, path=/favicon.ico}]
2022-07-16 17:21:49.958 TRACE 1412 --- [nio-8080-exec-2] s.w.s.m.m.a.RequestMappingHandlerAdapter : Applying default cacheSeconds=-1
2022-07-16 17:21:49.958 TRACE 1412 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : No view rendering, null ModelAndView returned.
2022-07-16 17:21:49.958 DEBUG 1412 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Exiting from "ERROR" dispatch, status 404, headers={masked}