starter+AOP拦截+RestTemplate实现日志服务

设计这个日志服务,也是用了分布式思想,实现流程为:

        请求项目资源,被LogStarter的AOP拦截器拦截,通过AOP环绕通知中ProceedingJoinPoint获取HttpServletRequest然后拿到请求信息,发送给日志服务器logService,日志服务器收到日志后就可以做相应处理了,最常见的就是在日志服务将接收的日志放入redis中。

源码:https://gitee.com/feiliangren/log-service.git

首先需要创建一个日志收集 starter,如果对自定义starter不了解的可以参考自定starter实现

log的starter的目录结构:

首先引入依赖

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

创建日志starter的properties的配置类——LogProperties.java

package org.mystarter.logstringbootstarter.properties;


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;


/**
 *
 * properties配置文件类
 *
 */
@ConfigurationProperties(prefix = "spring.log")
@Data
public class LogProperties {

    //默认的请求地址
    private String host = "http://127.0.0.1:9000";


}

LogService.java :

package org.mystarter.logstringbootstarter.server;

import org.mystarter.logstringbootstarter.properties.LogProperties;

/**
 * service  将properties 注入到ioc 需要用到
 */
public class LogService {

//    service 定义 LogProperties 的属性
    public LogProperties dp;

}

 LogProxy :

package org.mystarter.logstringbootstarter.proxy;

import lombok.extern.slf4j.Slf4j;
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.mystarter.logstringbootstarter.properties.LogProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.Executor;

/**
 * AOP 切面
 */

@Aspect
@Slf4j
public class LogProxy {

    @Autowired
    RestTemplate rt;

    @Autowired
    LogProperties lp;

    /**
     * 使用ioc的自定义线程,单独处理日志上传服务器
     * logExecutor 线程的名字
     */
    @Qualifier("logExecutor")
    @Autowired
    Executor executor;

    /**
     * 切点
     * 扫描所有  @RequestMapping  注解类  (因为每个控制层都会有@RequestMapping注解,所以直接拦截被这个注解标注的类)
     */
    @Pointcut("@within(org.springframework.web.bind.annotation.RequestMapping)")
    public void request(){

    }

    /**
     * 环绕通知
     * @param point
     * @return
     */
    @Around("request()")
    public Object logAround(ProceedingJoinPoint point) {
        Object proceed = null;
        try {
//            通过ProceedingJoinPoint获取Request
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = requestAttributes.getRequest();

            /**
             * 通过HttpServletRequest获取请求url、方法本身
             * 还可以获取很多-常见的有
             *  getMethod()  :获取请求提交的方式
             *  getProtocol()  :获取请求的协议
             *  getContextPath()  :获取项目名称
             *  getServletPath()  :获取servlet路径
             *  getRequestURI()、getRequestURL()  :获取请求路径
             */
            String getRequestURL = request.getRequestURL().toString();

//            这里需要我们执行拦截方法本身的代码(最后返回)
             proceed = point.proceed();
//             声明一个map 放入日志信息
            LinkedMultiValueMap<String, Object> mv = new LinkedMultiValueMap<String, Object>();
            mv.add("RequestURL",getRequestURL);
            mv.add("return",proceed);

//            打印map
            log.info(String.valueOf(mv));

            /**
             * 异步上传服务器
             * 上传的路径是LogProperties的host 值
             */
            executor.execute(() -> rt.postForObject(lp.getHost()+"log",mv,String.class));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }


        return proceed;
    }



}

LogAutoConfiguration:

package org.mystarter.logstringbootstarter.configuration;

import lombok.extern.slf4j.Slf4j;
import org.mystarter.logstringbootstarter.properties.LogProperties;
import org.mystarter.logstringbootstarter.proxy.LogProxy;
import org.mystarter.logstringbootstarter.server.LogService;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 自动配置类
 */
@Configuration
@EnableConfigurationProperties({LogProperties.class})
@Slf4j
public class LogAutoConfiguration {


    /**
     * 服务对象
     * @param logProperties
     * @return
     */
    @Bean
    LogService logBean(LogProperties logProperties){
        LogService logService = new LogService();
        logService.dp = logProperties;
        return logService;
    }

    /**
     * 将RestTemplate对象 放入ioc
     * @return
     */
    @Bean
    RestTemplate restTemplate(){
        return  new RestTemplate();
    }

    /**
     * 将日志AOP切面对象放入ioc
     * @return
     */
    @Bean
    LogProxy logProxy(){
        return new LogProxy();
    }

    /**
     * 定义一个线程
     * 放入ioc
     * @return
     */
    @Bean("logExecutor")
    Executor executor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程数:线程池创建时候初始化的线程数
        executor.setCorePoolSize(5);
        // 最大线程数:线程池最大的线程数,只有在缓冲队列满了之后才会申请超过核心线程数的线程
        executor.setMaxPoolSize(10);
        // 缓冲队列:用来缓冲执行任务的队列
        executor.setQueueCapacity(50);
        // 允许线程的空闲时间60秒:当超过了核心线程之外的线程在空闲时间到达之后会被销毁
        executor.setKeepAliveSeconds(60);
        // 线程池名的前缀:设置好了之后可以方便我们定位处理任务所在的线程池
        executor.setThreadNamePrefix("do-something-");
        // 缓冲队列满了之后的拒绝策略:由调用线程处理(一般是主线程)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        executor.initialize();
        return executor;
    }

    @Bean
    public void afterPropertiesSet(){
        log.info("日志准备就绪");
    }

}

最后打包成启动器(日志收集starter完成)

准备日志服务(springboot项目)

项目结构:

引入依赖

  <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

日志服务只提供一个接收日志的方法(既然都可以接收到日志,后续就可以对这些日志为所欲为,比如放入redis中)

修改运行端口:

        application.properties

# 应用服务 WEB 访问端口
server.port=9000

LogServiceController :

package com.example.logservice.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;

/**
 * 接收日志 controller
 * */
@RestController
@RequestMapping("log")
@Slf4j
public class LogServiceController {

    @PostMapping
    public String logService(@RequestParam Map<String,Object> m){
//      这里我只打印接收到的日志
        log.info("接收的日志为"+m);
        return "ok";
    }

}

到此日志服务就准备好了

创建测试项目:

项目结构:

引入依赖:

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

<!--      引入日志starter   -->
        <dependency>
            <groupId>org.my</groupId>
            <artifactId>log-spring-boot-starter</artifactId>
            <version>1.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

application.properties中配置:

# 日志服务器的地址 (因为我的logService端口是9000并且在本地所以不用改配置)
spring.log.host=http://127.0.0.1:9000

编写测试controller

package com.example.test.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *  Test controller
 */
@RestController
@RequestMapping("demos")
public class TestController {

    @GetMapping
    List<Map<String,Object>> test(String msg){
//        封装一个返回值并返回
        List l=new ArrayList();
        Map m=new HashMap();
        m.put("key",msg);
        l.add(m);
        return l;
    }


}

到这测试项目也完成了

测试项目:运行日志服务和测试项目,访问测试项目的API,在日志服务控制台中就能看到打印的日志信息

到这里就完成了


 

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值