Spring中的可插拔组件技术(低耦合高扩展)
Spring AOP
1.Spring中的可插拔组件技术
2.Spring AOP
Spring AOP – Aspect Oriented Programming 面向切面编程
AOP的做法是将通用的、与业务无关的功能抽象封装为切面类
切面可配置在目标方法的执行前、后运行,真正做到即插即用
可以在不修改源码的情况下对程序行为进行扩展
应用场景:可用于日志记录、性能统计、权限管理等
3.几个关键概念
4.Spring AOP
5.Pointcut 切点表达式
6.JoinPoint的核心方法
7.五种通知类型
8.在 pom.xml 中引入 AOP 依赖
<!-- aop依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.10.RELEASE</version>
</dependency>
9.给controller包中的类添加切面类
package com.example.mall.aspect;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
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.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
//如果不想要这个扩展功能就把@Component @Aspect注解注释掉就好了(可插拔)
@Component //标注为组件,可被实例化
@Aspect //标注为切面
public class ControllerAspect {
private Logger logger = LoggerFactory.getLogger(ControllerAspect.class);
//切入点execution表达式, 这里表示切入点为controller包下的所有方法
// (第一个* :返回值类型任意类型 com.example.mall.controller.*.*(..) :这个包中的所有类的所有方法 )
@Pointcut("execution(public * com.example.mall.controller.*.*(..))")
public void pointcut(){
}
//这是一个前置通知,该方法在目标方法执行之前执行
@Before(value = "pointcut()")
public void doBefore(JoinPoint joinPoint){
//收到请求,记录请求内容
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
//请求方法
String requestMethod = request.getMethod();
//请求url
String url = request.getRequestURL().toString();
//请求接口的名称
String apiName = joinPoint.getTarget().getClass().getName() + "." +
joinPoint.getSignature().getName();
//请求中的参数,参数可能有多个
String args = Arrays.toString(joinPoint.getArgs());
logger.info("【请求方法】:{}, 【请求URL】:{}, 【请求接口方法】:{}, 【请求参数】:{}", requestMethod, url, apiName, args);
}
//这是一个返回通知,在该方法中可以获取目标方法的返回值
@AfterReturning(value = "pointcut()", returning = "res")
public void doAfterReturning(Object res){
try {
//把返回的对象转化为字符串
String resString = new ObjectMapper().writeValueAsString(res);
logger.info("【方法返回数据】:{}", resString);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
10.给service包添加切面类(计算本类的执行时间)
package com.example.mall.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class ServiceAspect {
private Logger logger = LoggerFactory.getLogger(ServiceAspect.class);
@Pointcut("execution(public * com.example.mall.service.*.*(..))")
public void pointcut() {
}
// 环绕通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 记录开始时间
long startTime = System.currentTimeMillis();
//目标方法执行,res保存目标方法的返回值
Object res = joinPoint.proceed();
logger.info("【服务层方法】: {}----【执行时间】{} 毫秒",
joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName(),
System.currentTimeMillis() - startTime
);
return res;
}
}
11.控制台输出效果
"C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\lib\idea_rt.jar=53113:D:\Program Files\JetBrains\IntelliJ IDEA 2021.1.1\bin" -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=GBK -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;G:\spring-boot课堂作业\第三节课\mall\target\classes;C:\Users\DELL\.m2\repository\org\springframework\boot\spring-boot-starter-web\2.4.5\spring-boot-starter-web-2.4.5.jar;C:\Users\DELL\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.5\spring-boot-starter-2.4.5.jar;C:\Users\DELL\.m2\repository\org\springframework\boot\spring-boot\2.4.5\spring-boot-2.4.5.jar;C:\Users\DELL\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.5\spring-boot-autoconfigure-2.4.5.jar;C:\Users\DELL\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.5\spring-boot-starter-logging-2.4.5.jar;C:\Users\DELL\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\DELL\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\DELL\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\DELL\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\DELL\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\DELL\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\DELL\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\DELL\.m2\repository\org\springframework\boot\spring-boot-starter-json\2.4.5\spring-boot-starter-json-2.4.5.jar;C:\Users\DELL\.m2\repository\com\fasterxml\jackson\core\jackson-databind\2.11.4\jackson-databind-2.11.4.jar;C:\Users\DELL\.m2\repository\com\fasterxml\jackson\core\jackson-annotations\2.11.4\jackson-annotations-2.11.4.jar;C:\Users\DELL\.m2\repository\com\fasterxml\jackson\core\jackson-core\2.11.4\jackson-core-2.11.4.jar;C:\Users\DELL\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.11.4\jackson-datatype-jdk8-2.11.4.jar;C:\Users\DELL\.m2\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.11.4\jackson-datatype-jsr310-2.11.4.jar;C:\Users\DELL\.m2\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.11.4\jackson-module-parameter-names-2.11.4.jar;C:\Users\DELL\.m2\repository\org\springframework\boot\spring-boot-starter-tomcat\2.4.5\spring-boot-starter-tomcat-2.4.5.jar;C:\Users\DELL\.m2\repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.45\tomcat-embed-core-9.0.45.jar;C:\Users\DELL\.m2\repository\org\glassfish\jakarta.el\3.0.3\jakarta.el-3.0.3.jar;C:\Users\DELL\.m2\repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.45\tomcat-embed-websocket-9.0.45.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-web\5.3.6\spring-web-5.3.6.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-beans\5.3.6\spring-beans-5.3.6.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-webmvc\5.3.6\spring-webmvc-5.3.6.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-context\5.3.6\spring-context-5.3.6.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-expression\5.3.6\spring-expression-5.3.6.jar;C:\Users\DELL\.m2\repository\org\mybatis\spring\boot\mybatis-spring-boot-starter\2.1.4\mybatis-spring-boot-starter-2.1.4.jar;C:\Users\DELL\.m2\repository\org\springframework\boot\spring-boot-starter-jdbc\2.4.5\spring-boot-starter-jdbc-2.4.5.jar;C:\Users\DELL\.m2\repository\com\zaxxer\HikariCP\3.4.5\HikariCP-3.4.5.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-jdbc\5.3.6\spring-jdbc-5.3.6.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-tx\5.3.6\spring-tx-5.3.6.jar;C:\Users\DELL\.m2\repository\org\mybatis\spring\boot\mybatis-spring-boot-autoconfigure\2.1.4\mybatis-spring-boot-autoconfigure-2.1.4.jar;C:\Users\DELL\.m2\repository\org\mybatis\mybatis\3.5.6\mybatis-3.5.6.jar;C:\Users\DELL\.m2\repository\org\mybatis\mybatis-spring\2.0.6\mybatis-spring-2.0.6.jar;C:\Users\DELL\.m2\repository\mysql\mysql-connector-java\8.0.23\mysql-connector-java-8.0.23.jar;C:\Users\DELL\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-core\5.3.6\spring-core-5.3.6.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-jcl\5.3.6\spring-jcl-5.3.6.jar;C:\Users\DELL\.m2\repository\org\springframework\boot\spring-boot-starter-aop\2.3.10.RELEASE\spring-boot-starter-aop-2.3.10.RELEASE.jar;C:\Users\DELL\.m2\repository\org\springframework\spring-aop\5.3.6\spring-aop-5.3.6.jar;C:\Users\DELL\.m2\repository\org\aspectj\aspectjweaver\1.9.6\aspectjweaver-1.9.6.jar" com.example.mall.MallApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.5)
2021-05-18 22:27:30.420 [main] INFO com.example.mall.MallApplication - Starting MallApplication using Java 1.8.0_144 on DESKTOP-QLIMIR2 with PID 1396 (G:\spring-boot课堂作业\第三节课\mall\target\classes started by DELL in G:\spring-boot课堂作业\第三节课\mall)
2021-05-18 22:27:30.426 [main] INFO com.example.mall.MallApplication - No active profile set, falling back to default profiles: default
2021-05-18 22:27:32.620 [main] INFO o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http)
2021-05-18 22:27:32.635 [main] INFO org.apache.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]
2021-05-18 22:27:32.637 [main] INFO org.apache.catalina.core.StandardService - Starting service [Tomcat]
2021-05-18 22:27:32.637 [main] INFO org.apache.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.45]
2021-05-18 22:27:32.794 [main] INFO o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
2021-05-18 22:27:32.795 [main] INFO o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 2288 ms
2021-05-18 22:27:34.063 [main] INFO o.s.scheduling.concurrent.ThreadPoolTaskExecutor - Initializing ExecutorService 'applicationTaskExecutor'
2021-05-18 22:27:34.811 [main] INFO org.apache.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]
2021-05-18 22:27:34.856 [main] INFO o.s.boot.web.embedded.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path ''
2021-05-18 22:27:34.871 [main] INFO com.example.mall.MallApplication - Started MallApplication in 5.332 seconds (JVM running for 8.421)
2021-05-18 22:28:31.626 [http-nio-8080-exec-1] INFO o.a.c.core.ContainerBase.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-05-18 22:28:31.626 [http-nio-8080-exec-1] INFO org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
2021-05-18 22:28:31.629 [http-nio-8080-exec-1] INFO org.springframework.web.servlet.DispatcherServlet - Completed initialization in 3 ms
2021-05-18 22:28:31.740 [http-nio-8080-exec-1] INFO com.example.mall.aspect.ControllerAspect - 【请求方法】:GET, 【请求URL】:http://localhost:8080/user/1, 【请求接口方法】:com.example.mall.controller.UserController.getUser, 【请求参数】:[1]
2021-05-18 22:28:31.827 [http-nio-8080-exec-1] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Starting...
2021-05-18 22:28:32.587 [http-nio-8080-exec-1] INFO com.zaxxer.hikari.HikariDataSource - HikariPool-1 - Start completed.
2021-05-18 22:28:32.754 [http-nio-8080-exec-1] INFO com.example.mall.aspect.ServiceAspect - 【服务层方法】: com.example.mall.service.UserService.getUserByPK----【执行时间】991 毫秒
2021-05-18 22:28:32.884 [http-nio-8080-exec-1] INFO com.example.mall.aspect.ControllerAspect - 【方法返回数据】:{"id":1,"username":"1","password":"1","personalizedSignature":"3","role":1,"createTime":1576435053000,"updateTime":1581244872000}
2021-05-18 22:29:19.047 [http-nio-8080-exec-2] INFO com.example.mall.aspect.ControllerAspect - 【请求方法】:GET, 【请求URL】:http://localhost:8080/user/1, 【请求接口方法】:com.example.mall.controller.UserController.getUser, 【请求参数】:[1]
2021-05-18 22:29:19.054 [http-nio-8080-exec-2] INFO com.example.mall.aspect.ServiceAspect - 【服务层方法】: com.example.mall.service.UserService.getUserByPK----【执行时间】7 毫秒
2021-05-18 22:29:19.069 [http-nio-8080-exec-2] INFO com.example.mall.aspect.ControllerAspect - 【方法返回数据】:{"id":1,"username":"1","password":"1","personalizedSignature":"3","role":1,"createTime":1576435053000,"updateTime":1581244872000}