赞
踩
在 Spring Boot 中使用切面来拦截操作日志,以及配合使用 MyBatis-Plus 框架进行操作,并使用 Thymeleaf 视图显示商品列表,同时配置 Logback 日志输出到文件。
CREATE TABLE product ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, price DECIMAL(10, 2) NOT NULL, description TEXT ); INSERT INTO product (name, price, description) VALUES ('商品 1', 100.00, '商品描述 1'), ('商品 2', 150.00, '商品描述 2'), ('商品 3', 200.00, '商品描述 3'), ('商品 4', 50.00, '商品描述 4'), ('商品 5', 300.00, '商品描述 5'), ('商品 6', 120.00, '商品描述 6'), ('商品 7', 80.00, '商品描述 7'), ('商品 8', 250.00, '商品描述 8'), ('商品 9', 180.00, '商品描述 9'), ('商品 10', 90.00, '商品描述 10');
<dependencies> <!-- Spring Boot Starter Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>最新版本</version> </dependency> <!-- MySQL Driver --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>最新版本</version> </dependency> <!-- Thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- Jackson for JSON --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <!-- Logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> </dependency> </dependencies>
application.properties配置文件
# 数据源配置 spring.datasource.url=jdbc:mysql://localhost:3306/your_database_name spring.datasource.username=your_database_username spring.datasource.password=your_database_password spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # MyBatis-Plus 配置 mybatis-plus.mapper-locations=classpath:mapper/*.xml mybatis-plus.global-config.id-type=auto # Thymeleaf 配置 spring.thymeleaf.mode=HTML spring.thymeleaf.cache=false # 日志配置 logging.level.root=INFO logging.level.com.icoderoad.example=DEBUG logging.file=logs/application.log logging.pattern.console=%msg%n
实体类Product
@Data
@TableName("product")
public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private double price;
private String description;
}
商品Mapper
@Repository
public interface ProductMapper extends BaseMapper<Product> {
}
Controller
@Controller
public class ProductController{
@Autowired
private final ProductService productService;
@GetMapping("/products")
public String listProducts(Model model){
model.Addtttribute("product",productService.list());
return "product/list";
}
}
Service
public interface ProductService extends IService<Product> {
}
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}
日志配置信息
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration> <configuration> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-mm-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <appender name="FILE" class="ch.qos.logback.core.FileAppender"> <file>logs/application.log</file> <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"> <pattern>%d{yyyy-mm-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE" /> </root> </configuration>
WebLog类
/** * Controller层的日志封装类 */ @Data @EqualsAndHashCode public class WebLog { /** * 操作描述 */ private String description; /** * 操作用户 */ private String username; /** * 操作时间 */ private Long startTime; /** * 消耗时间 */ private Integer spendTime; /** * 根路径 */ private String basePath; /** * URI */ private String uri; /** * URL */ private String url; /** * 请求类型 */ private String method; /** * IP地址 */ private String ip; /** * 请求参数 */ private Object parameter; /** * 返回结果 */ private Object result; }
创建切面类
拦截操作日志并将其转化为 JSON 格式,输出转换后的 JSON 数据。
@Aspect @Component public class LogAspect{ private static final Logger LOGGER = LoggerFactory.getLogger(LogAspect.class); @Autowired public final ObjectMapper objectMapper; //切入点定义:拦截所有Controller方法 @Pointcut("execution(* com.icoderoad.example.product.controller.*.*(..))") public void webLog(){ } //在方法返回后执行 @AfterReturning(returning="result",pointcut="webLog()") public void doAfterReturning(JoinPoint joinPoint,Object result) throws Throwable { //获取当前请求的属性 ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); //创建weblog对象,并填充信息 WebLog webLog = new WebLog(); webLog.setStartTime(System.currentTimeMillis()); webLog.setBasePath(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort()); webLog.setUri(request.getRequestURI()); webLog.setUrl(request.getRequestURL().toString()); webLog.setMethod(request.getMethod()); webLog.setIp(getClientIp(request)); // 获取客户端真实 IP 地址 webLog.setParameter(Arrays.toString(joinPoint.getArgs())); webLog.setResult(result); //将WebLog对象转换为JSON格式,并输出到控制台(实际应该输出到日志文件) String logJson = objectMapper.writeValueAsString(webLog); LOGGER.info(logJson); } // 获取客户端真实 IP 地址 private String getClientIp(HttpServletRequest request) { String ipAddress = request.getHeader("X-Forwarded-For"); if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); } if (ipAddress != null && ipAddress.contains(",")) { ipAddress = ipAddress.split(",")[0].trim(); } return ipAddress; } }
项目启动类,启动的时候扫描mapper所在的包
@SpringBootApplication
@MapperScan("com.icoderoad.example.product.mapper")
public class AopLogbackProductApplication {
public static void main(String[] args) {
SpringApplication.run(AopLogbackProductApplication.class, args);
}
}
视图展示层Thymeleaf
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>商品列表</title> <!-- 引入 Bootstrap 的 CSS 文件 --> <link href="https://cdn.jsdelivr.net/npm/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container"> <h1>商品列表</h1> <table class="table table-striped"> <thead> <tr> <th>ID</th> <th>名称</th> <th>价格</th> <th>描述</th> </tr> </thead> <tbody> <tr th:each="product : ${products}"> <td th:text="${product.id}"></td> <td th:text="${product.name}"></td> <td th:text="${product.price}"></td> <td th:text="${product.description}"></td> </tr> </tbody> </table> </div> </body> </html>
=====================================================
Filter和HandlerInterceptor自身的一些局限性:
过滤器(Filter)是与servlet相关联的一个接口,主要适用于java web项目中,依赖于Servlet容器,是利用java的回调机制来实现过滤拦截来自浏览器端的http请求,可以拦截到访问URL对应的方法的请求和响应(ServletRequest request, ServletResponse response),但是不能对请求和响应信息中的值进行修改;一般用于设置字符编码、鉴权操作等;
如果想要做到更细一点的类和方法或者是在非servlet环境中使用,则是做不到的;所以凡是依赖Servlet容器的环境,过滤器都可以使用,如Struts2、SpringMVC;
拦截器的(HandlerInterceptor)使用范围以及功能和过滤器很类似,但是也是有区别的。首先,拦截器(HandlerInterceptor)适用于SpringMVC中,因为HandlerInterceptor接口是SpringMVC相关的一个接口,而实现java Web项目,SpringMVC是目前的首选选项,但不是唯一选项,还有struts2等;因此,如果是非SpingMVC的项目,HandlerInterceptor无法使用的;
其次,和过滤器一样,拦截器可以拦截到访问URL对应的方法的请求和响应(ServletRequest request, ServletResponse response),但是不能对请求和响应信息中的值进行修改;一般用于设置字符编码、鉴权操作等;如果想要做到更细一点的类和方法或者是在非servlet环境中使用,则也是是做不到的;
拦截器和过滤器的区别:
总之,过滤器和拦截器的功能很类似,但是拦截器的适用范围比过滤器更小
优先级:过滤器>拦截器>SpringAOP
拦截器依赖于springMVC,可以在SpringMVC项目中使用,SpringMVC的核心是DispatcherServlet,
而DispatcherServlet又属于Servlet的子类,因此作用域和过滤器类似
SpringAOP对作用域没有限制,只要定义好切点,可以在请求-想用的入口层(controller)拦截处理,也可以在请求的业务处理层(service)拦截处理
拦截器提供了preHandle()/postHandle()/afterCompletion()可以在controller对请求处理之前、请求处理之后、请求响应完毕织入业务操作
SpringAOP提供了前置通知、后置通知、返回后通知、异常通知、环绕通知,比拦截器更加精细化的颗粒度控制,甚至可以修改返回值;
案例:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
create table if not exists bus_log
(
id bigint auto_increment comment '自增id'
primary key,
bus_name varchar(100) null comment '业务名称',
bus_descrip varchar(255) null comment '业务操作描述',
oper_person varchar(100) null comment '操作人',
oper_time datetime null comment '操作时间',
ip_from varchar(50) null comment '操作来源ip',
param_file varchar(255) null comment '操作参数报文文件'
)
comment '业务操作日志' default charset ='utf8';
/** 定义业务日志注解@BusLog */ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface BusLog{ /** * 功能名称 * @return */ String name() default ""; /** * 功能描述 * @return */ String descrip() default ""; }
/** 把业务操作日志注解BusLog标记在PersonController类和方法上 */ @RestController @Slf4j @BusLog(name="人员管理") @RequestMapping("/person") public class PersonController{ @Autowired private IPersonService personService; private Integer maxCount = 100; @PostMapping @NeedEncrypt @BusLog(descrip = "添加单挑人员信息") public Person add(@RequestBody Person person){ Person result = this.personService.registe(person); log.info("增加person执行完成"); return result; } @PostMapping("/batch") @BusLog(descrip = "批量添加人员信息") public String addBatch(@RequestBody List<Person> personList){ this.personService.addBatch(personList); return String.valueOf(System.currentTimeMillis()); } @GetMapping @NeedDecrypt @BusLog(descrip = "人员信息列表查询") public PageInfo<Person> list(Integer page, Integer limit, String searchValue) { PageInfo<Person> pageInfo = this.personService.getPersonList(page,limit,searchValue); log.info("//查询person列表执行完成"); return pageInfo; } @GetMappping("/{loginNo}") @NeedDecrypt @BusLog(descip = "人员信息详情查询") public Person info(@PathVariable String loginNo,String phoneVal){ Person person = this.personService.get(loginNo); log.info("查询person详情执行完成"); return person; } @PutMapping @NeedEncrypt @BusLog(descrip = "修改人员信息") public String edit(@RequestBody Person person) { this.personService.update(person); log.info("//查询person详情执行完成"); return String.valueOf(System.currentTimeMillis()); } @DeleteMapping @BusLog(descrip = "删除人员信息") public String edit(@PathVariable(name = "id") Integer id) { this.personService.delete(id); log.info("//查询person详情执行完成"); return String.valueOf(System.currentTimeMillis()); } }
切面类
使用@BusLog定义切入点,在环绕通知内执行过目标方法后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述, 把方法的参数报文写入到文件中,最后保存业务操作日志信息;
@Component @Aspect @Slf4j public class BusLogAop implements Ordered { @Autowired private BusLogDao busLogDao; /** * 定义BusLogAop的切入点为标记@BusLog注解的方法 */ @Pointcut(value = "@annotation(com.fanfu.anno.BusLog)") public void pointcut() { } /** * 业务操作环绕通知 * * @param proceedingJoinPoint * @retur */ @Around("pointcut()") public Object around(ProceedingJoinPoint proceedingJoinPoint) { log.info("----BusAop 环绕通知 start"); //执行目标方法 Object result = null; try { result = proceedingJoinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } //目标方法执行完成后,获取目标类、目标方法上的业务日志注解上的功能名称和功能描述 Object target = proceedingJoinPoint.getTarget(); Object[] args = proceedingJoinPoint.getArgs(); MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); BusLog anno1 = target.getClass().getAnnotation(BusLog.class); BusLog anno2 = signature.getMethod().getAnnotation(BusLog.class); BusLogBean busLogBean = new BusLogBean(); String logName = anno1.name(); String logDescrip = anno2.descrip(); busLogBean.setBusName(logName); busLogBean.setBusDescrip(logDescrip); busLogBean.setOperPerson("fanfu"); busLogBean.setOperTime(new Date()); JsonMapper jsonMapper = new JsonMapper(); String json = null; try { json = jsonMapper.writeValueAsString(args); } catch (JsonProcessingException e) { e.printStackTrace(); } //把参数报文写入到文件中 OutputStream outputStream = null; try { String paramFilePath = System.getProperty("user.dir") + File.separator + DateUtil.format(new Date(), DatePattern.PURE_DATETIME_MS_PATTERN) + ".log"; outputStream = new FileOutputStream(paramFilePath); outputStream.write(json.getBytes(StandardCharsets.UTF_8)); busLogBean.setParamFile(paramFilePath); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (outputStream != null) { try { outputStream.flush(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } //保存业务操作日志信息 this.busLogDao.insert(busLogBean); log.info("----BusAop 环绕通知 end"); return result; } @Override public int getOrder() { return 1; } }
调试
平时后端调试接口,一般都是使用postman,这里给大家安利一款工具,即Intellij IDEA的Test RESTful web service,功能和使用和postman差不多,唯一的好处就是不用在电脑上再额外装个postman,功能入口:工具栏的Tools–>http client–>Test RESTful web
简单几句就可以发起一个http请求,还可以一次批量执行;
结果如下:
hutool工具包提供的脱敏工具类DesensitizedUtil,它提供了常见的手机号、身份证号、银行卡、邮箱等脱敏的方法,将敏感数据部分加*处理。
1、依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.4</version>
</dependency>
hutool工具类
利用hutool工具包,对常见的手机号、身份证号、银行卡号、邮箱进行脱敏处理。还给了一个默认的DEFAULT枚举类型按原数据返回,同时自定义了一个CUSTOM枚举,可根据实现CustomMaskService这个接口去自定义脱敏逻辑
public class SensitiveHutoolTest{
public static void main(){
System.out.println(DesensitizedUtils.mobilePhone("13812345678"));
System.out.println(DesensitizedUtil.idCardNum("110101200007283706", 3, 4));
System.out.println(DesensitizedUtil.bankCard("6225809637392380845"));
System.out.println(DesensitizedUtil.email("zhangsanfeng@test.com"));
}
}
代码实现:
一、定义脱敏类型枚举
上述对对常见的手机号、身份证号、银行卡号、邮箱进行脱敏处理。还给了一个默认的DEFAULT枚举类型按原数据返回,同时自定义了一个CUSTOM枚举,可根据实现CustomMaskService这个接口去自定义脱敏逻辑
实际情况可根据实际需求进行输入输出参数的扩展
public enum SensitiveTypeEnum{ MOBILE("mobile","手机号"){ //手机号前3位后4位脱敏,中间部分加*处理 @Override return DesensitizedUtil.mobilePhone(data); }, IDENTIFY("identify","身份证号"){ @Override public String maskSensitiveData(String data){ return DesensitizedUtil.idCardNum(data,3,4); } }, BANKCARD("bankcard","银行卡号"){ @Override public String maskSensitiveData(String data){ return DesensitizedUtil.bankCard(data); } }, EMAIL("email","邮箱"){ //邮箱@符号后明文显示,@符号前的字符串,只显示第一个字符,其余加*处理,比如:z***********@test.com @Override public String maskSensitiveData(String data){ return DesensitizedUtil.email(data); } }, DEFAULT("default","默认"){ // 默认原值返回,其他这个也没啥意义^_^ @Override public String maskSensitiveData(String data){ return data; } }, CUSTOM("custom","自定义"){ @Override public String maskSensitiveData(String data, CustomMaskService customMaskService) { // 可以自定义处理的service,根据实际使用情况可能需要添加参数,调整一下即可 return customMaskService.maskData(data); } }; @Getter private String type; @Getter private String desc; SensitiveTypeEnum(String type,String desc){ this.type = type; this.desc = desc; } /** 遮挡敏感数据 */ public String maskSensitiveData(String data){ return data; } public String maskSensitiveData(String data,CustomMaskService customMaskService){ return null; } }
二、定义一个脱敏注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType,FIELD)
public @interface SensitiveData{
SensitiveTypeEnum type() default SensitiveTypeEnum.DEFAULT;
}
三、定义mybatis拦截器
拦截器注解里写明要拦截的对象和方法
类:ResultSetHandler,方法:handleResultSets
在sql执行完成后拿出结果集并对结果集进行处理返回
@Intercepts({ @Signature(type=ResultSetHandler.class,method="handleResultSets",args = {Statement.class}) }) @Slf4j public class SensitiveDataInterceptor implements Interceptor{ @Autowired private CustomMaskService customMaskService; @Override public Object intercept(Invocation invocation) throws Throwable{ Object result = invocation.proceed(); log.debug("进入数据脱敏拦截器……"); if(result instanceof List){ List<?> resultList = (List<?>)result; for(Object obj : resultList){ doSensitiveFields(obj); } }else if(result instanceof Map){ Map<?,?> resultMap = (Map<?,?>)result; for(Object obj:resultMap.values()){ doSensitiveFields(obj); } }else{ doSensitiveFields(result); } return result; } private void doSensitiveFields(Object obj) throws IllegalAccessException{ Field[] fields = obj.getClass().getDeclaredFields();//获取其所有属性 for(Field field:fields){ if(field.isAnnotationPresent(SensitiveData.class)){ field.setAccessible(true);//包括private属性 Object value = field.get(obj); if(value == null){ return; } SensitiveData sensitiveData = field.getAnnotation(SensitiveData.class); SensitiveTypeEnum type = sensitiveData.type(); String result; if(type == SensitiveTypeEnum.CUSTOM){ result = type.maskSensitiveData(value.toString(), customMaskService); }else{ result = type.maskSensitiveData(value.toString()); } field.set(obj,result); } } } }
四、配置拦截器
@Confituration
@MapperScan("com.xxx.mybatisplus.mapper")
public class MyBatisPlusConfig{
@Bean
public SensitiveDataInterceptor sensitiveDataInterceptor(){
return new SensitiveDataInterceptor();
}
}
五、测试
@Data public class UserDto { /** * id */ private Long id; /** * 姓名 */ @SensitiveData(type = CUSTOM) private String name; /** * 年龄 */ private Integer age; /** * 邮箱 */ @SensitiveData(type = EMAIL) private String email; /** * 手机号 */ @SensitiveData(type = MOBILE) private String mobile; /** * 身份证号 */ @SensitiveData(type = IDENTIFY) private String identify; /** * 银行卡号 */ @SensitiveData(type = BANKCARD) private String bankcard; }
Service接口及其实现类
public interface CustomMaskService{
String maskData(String data);
}
@Service
public class CustomMaskServiceImpl implements CustomMaskService{
@Override
public String maskData(){
//第一个字符明文,其他都加*处理
return CharSequenceUtil.hide(data,1,data.length());
}
}
Mapper
@Mapper
public interface UserMapper extends BaseMapper<User>{
@Select({"select * from user where id=#{id}"})
UserDto getSpecialUser(String id);
@Select({"select * from user"})
List<UserDto> queryAll();
}
Controller
@RestController @RequestMapping("/sensitive") public class SensitiveMyBatisInterceptorTestController{ @Resource private UserMapper userMapper; @GetMapping("/user/{id}") public Result<UserDto> queryUserInfo(@PathVariable String id){ return Result.success(userMapper.getSpecialUser(id)); } @GetMapping("/userlist") public Result<List<UserDto>> queryUserList(){ return Result.success(userMapper.queryAll()); } }
一、动态管理advice端点实现
@RestControllerEndpoint(id="proxy") @RequireArgsConstructor public class ProxyMetaDefinitionControllerEndPoint{ private final ProxyMetaDefinitionRespository proxyMetaDefinitionRepository; @GetMapping("listMeta") public List<ProxyMetaDefinition> getProxyMetaDefinitions(){ return proxyMetaDefinitionRepository.getProxyMetaDefinitions(); } @GetMapping("{id}") public ProxyMetaDefinition getProxyMetaDefinition(@PathVariable("id") String proxyMetaDefinitionId){ return proxyMetaDefinitionRepository.getProxyMetaDefinition(proxyMetaDefinitionId); } @PostMapping("save") public String save(@RequestBody ProxyMetaDefinition definition){ try{ proxyMetaDefinitionRepository.save(definition); return "success"; }catch(Exception e){ } return "fail"; } @PostMapping("delete/{id}") public String delete(@PathVariable("id") String proxyMetaDefinitionId){ try{ proxyMetaDefinitionRepository.delete(proxyMetaDefinitionId); return "success"; }catch(Exception e){ } return "fail"; } }
二、利用事件监听机制捕获安装或者卸载插件
@RequiredArgsConstructor public class ProxyMetaDefinitionChangeListener{ private final AopPluginFactory aopPluginFactory; @EventListener public void listener(ProxyMetaDefinitionChangeEvent proxyMetaDefinitionChangeEvent){ ProxyMetaInfo proxyMetaInfo = aopPluginFactory.getProxyMetaInfo(proxyMetaDefinitionChangeEvent.getProxyMetaDefinition()); switch (proxyMetaDefinitionChangeEvent.getOperateEventEnum()){ case ADD: aopPluginFactory.installPlugin(proxyMetaInfo); break; case DEL: aopPluginFactory.uninstallPlugin(proxyMetaInfo.getId()); break; } } }
三、安装插件
public void installPlugin(ProxyMetaInfo proxyMetaInfo){
if(StringUtils.isEmpty(proxyMetaInfo.getId())){
proxyMetaInfo.setId(proxyMetaInfo.getProxyUrl() + SPIILT + proxyMetaInfo.getProxyClassName());
}
AopUtil.registerProxy(defaultListableBeanFactory,proxyMetaInfo);
}
四、安装插件核心实现
public static void registerProxy(DefaultListableBeanFactory beanFactory,ProxyMetaInfo proxyMetaInfo){ AspectJExpressionPointcutAdvisor advisor = getAspectJExpressionPointcutAdvisor(beanFactory, proxyMetaInfo); addOrDelAdvice(beanFactory,OperateEventEnum.ADD,advisor); } private static AspectJExpressionPointcutAdvisor getAspectJExpressionPointcutAdvisor(DefaultListableBeanFactory beanFactory, ProxyMetaInfo proxyMetaInfo) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(); GenericBeanDefinition beanDefinition = (GenericBeanDefinition) builder.getBeanDefinition(); beanDefinition.setBeanClass(AspectJExpressionPointcutAdvisor.class); AspectJExpressionPointcutAdvisor advisor = new AspectJExpressionPointcutAdvisor(); advisor.setExpression(proxyMetaInfo.getPointcut()); advisor.setAdvice(Objects.requireNonNull(getMethodInterceptor(proxyMetaInfo.getProxyUrl(), proxyMetaInfo.getProxyClassName()))); beanDefinition.setInstanceSupplier((Supplier<AspectJExpressionPointcutAdvisor>) () -> advisor); beanFactory.registerBeanDefinition(PROXY_PLUGIN_PREFIX + proxyMetaInfo.getId(),beanDefinition); return advisor; }
五、卸载插件
public void uninstallPlugin(String id){
String beanName = PROXY_PLUGIN_PREFIX + id;
if(defaultListableBeanFactory.containsBean(beanName)){
AopUtil.destoryProxy(defaultListableBeanFactory,id);
}else{
throw new NoSuchElementException("Plugin not found: " + id);
}
}
六、卸载插件核心
public static void destoryProxy(DefaultListableBeanFactory beanFactory,String id){
String beanName = PROXY_PLUGIN_PREFIX + id;
if(beanFactory.containsBean(beanName)){
AspectJExpressionPointcutAdvisor advisor = beanFactory.getBean(beanName,AspectJExpressionPointcutAdvisor.class);
addOrDelAdvice(beanFactory,OperateEventEnum.DEL,advisor);
beanFactory.destroyBean(beanFactory.getBean(beanName));
}
}
七、操作advice
public static void addOrDelAdvice(DefaultListableBeanFactory beanFactory, OperateEventEnum operateEventEnum, AspectJExpressionPointcutAdvisor advisor){ AspectJExpressionPointcut pointcut = (AspectJExpressionPointcut) advisor.getPointcut(); for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) { Object bean = beanFactory.getBean(beanDefinitionName); if(!(bean instanceof Advised)){ if(operateEventEnum == OperateEventEnum.ADD){ buildCandidateAdvised(beanFactory,advisor,bean,beanDefinitionName); } continue; } Advised advisedBean = (Advised) bean; boolean isFindMatchAdvised = findMatchAdvised(advisedBean.getClass(),pointcut); if(operateEventEnum == OperateEventEnum.DEL){ if(isFindMatchAdvised){ advisedBean.removeAdvice(advisor.getAdvice()); log.info("########################################## Remove Advice -->【{}】 For Bean -->【{}】 SUCCESS !",advisor.getAdvice().getClass().getName(),bean.getClass().getName()); } }else if(operateEventEnum == OperateEventEnum.ADD){ if(isFindMatchAdvised){ advisedBean.addAdvice(advisor.getAdvice()); log.info("########################################## Add Advice -->【{}】 For Bean -->【{}】 SUCCESS !",advisor.getAdvice().getClass().getName(),bean.getClass().getName()); } } } }
热插拔AOP演示实例
一、创建一个service
@Service @Slf4j public class HelloService implements BeanNameAware, BeanFactoryAware { private BeanFactory beanFactory; private String beanName; @SneakyThrows public String sayHello(String message) { Object bean = beanFactory.getBean(beanName); log.info("============================ {} is Advised : {}",bean, bean instanceof Advised); TimeUnit.SECONDS.sleep(new Random().nextInt(3)); log.info("============================ hello:{}",message); return "hello:" + message; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this.beanFactory = beanFactory; } @Override public void setBeanName(String name) { this.beanName = name; } }
二、创建一个controller
@RestController
@RequestMapping("hello")
@RequiredArgsConstructor
public class HelloController {
private final HelloService helloService;
@GetMapping("{message}")
public String sayHello(@PathVariable("message")String message){
return helloService.sayHello(message);
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。