Spring AOP 实现日志功能

理解AOP

AOP,面向切面编程。

  • 举一个不是很恰当的例子:在一个工厂的车间中,有很多工人在工作,为了保证车间的生产效率,
    管理人员都会给车间装上摄像头,通过摄像头来反映车间内部工人的工作情况。摄像头就是从侧面监控的,就相当于我们的切面,我们通过摄像头就不会打扰到车间工人的工作,我们不用走进车间去问每个工人做了多少事,做了哪些事,一切都交给了摄像头来记录。车间就相当于我们的任务主体,比喻说我们程序运行时,用户做的CRUD。
    所以,AOP采取的就是横向的抽取机制,取代了传统的纵向继承体系重复性

AOP,应用场景

  • 性能监视,
  • 事务管理,
  • 安全检查,
  • 缓存,
  • 扩展日志功能
    等等
    AOP,操作术语
  • JoinPoint连接点:类里面可以被增强的方法
  • Pointcut切入点:实际被增强的方法,也就是新增的方法
  • Advice通知/增强:增强的逻辑,类型:前置通知、后置通知、异常通知、最终通知、环绕通知
  • Aspect切面:把增强应用到具体方法上,过程称为切面

使用表达式配置切入点

  • 切入点:实际被增强的方法
  • 常用表达式——execution表达式
    语法: execution(<修饰符模式>? <返回类型模式> <方法名模式>(<参数模式>) <异常模式>?)
    例如: execution(* com.baobaotao.Waiter.*(…))匹配Waiter接口的所有方法,它匹配NaiveWaiter和
    NaughtyWaiter类的greetTo()和serveTo()方法。第一个代表返回任意类型,
    com.baobaotao.Waiter.代表Waiter接口中的所有方法;

AOP实例——通过自定义注解,实现日志功能
本实例借鉴了Eladmin开源项目中的日志功能

  • 首先看下用日志存储的数据库表在这里插入图片描述
    requestip该字段会获取用户ip,但是如果你本地启动项目localhost 则表现为0:0:0:0:0:0:0:1。如果是上线外网用户访问,则会通过ip地址得到该用户的所在地xx省xx市
  • 要是现实该功能需要用到如下依赖
<!--获取浏览器信息 必须依赖-->
<dependency>
    <groupId>eu.bitwalker</groupId>
    <artifactId>UserAgentUtils</artifactId>
    <version>1.21</version>
</dependency>

 <!--获取用户ip 必须依赖-->
<dependency>
    <groupId>org.lionsoul</groupId>
    <artifactId>ip2region</artifactId>
    <version>1.7.2</version>
</dependency>

<!-- 阿里巴巴json处理工具 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>
<!-- hutool工具包包含有很多工具类 文件上传 下载导入导出等等非常丰富 -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.2.3</version>
</dependency>
<!-- 工具依赖 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>
<!-- lombok 可选依赖 -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<!-- 阿里连接池 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.10</version>
</dependency>

<!-- 以下是常用依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.2</version>
</dependency>
<!-- 安全框架 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring AOP 必须依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
  • 配置文件
server.port=8080
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql:///customer?serverTimezone=GMT
  • 通过ip查询地址需要用到ip2region库
    放在resource下面即可 在这里插入图片描述
  • 会使用到的工具类
    在utils包下
    FileUtils —— 将nputStream 转 File对象 该类继承了hutool工具类(上文中依赖中的工具类)
    该类主要是为通过ip查询地址而服务
    RequestUtils —— 获取请求信息,通过request请求获取用户的ip
    SecurityUtils —— 安全框架获取用户名和用户详细信息
    StringUtils —— 有两个方法获取ip 和 根据ip获取详细地址
  • 编写安全配置类
    简要配置了下,主要是学习AOP,密码是123
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("test").roles("user").password("$2a$10$V0VWQ4I0Hr/Z2Be2veQh3emu2fbl9eH80RK7wuGEkR7BeDORutiSa")
                .and().withUser("zddu").roles("admin").password("$2a$10$V0VWQ4I0Hr/Z2Be2veQh3emu2fbl9eH80RK7wuGEkR7BeDORutiSa");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").hasRole("user")
                .anyRequest().authenticated()
                .and()
                .formLogin() //表单登录配置
                .loginProcessingUrl("/login")//表单登陆提交数据的路径
                .and()
                .csrf().disable();
    }
}

  • 创建自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)// 注解会在class字节码文件中存在,在运行时可以通过反射获取到
public @interface Log {
    String value() default "";
}
  • 编写切面类
/**
 * 切面
 */
@Component
@Aspect
@Slf4j
public class LogAspect {

    @Autowired
    private LogService logService;

    @Pointcut("@annotation(com.cidp.aoplog.aop.Log)")
    public void logPointcut(){
        // 该方法无方法体,主要为了让同类中其他方法使用此切入点
    }
    @Around("logPointcut()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result;
        result = joinPoint.proceed(); //获取目标方法的返回值
        com.cidp.aoplog.model.Log log  = new com.cidp.aoplog.model.Log("INFO");
        HttpServletRequest req = RequestUtils.getHttpServletRequest();
        logService.save(getUsername(), StringUtils.getBrowser(req),StringUtils.getIp(req),joinPoint,log);
        return result;
    }

    public String getUsername() {
        try {
            return SecurityUtils.getUsername();
        }catch (Exception e){
            return "";
        }
    }
}

主要使用到了环绕通知。

  • Log实体类
  • 这里用到lombok 省去了自己生成getter和setter等方法
@Data
public class Log {
    private Integer id;
    /**请求用户*/
    private String username;
    private String description;
    private String method;
    private String logType;
    private String requestIp;
    private String address;
    private String browser;
    private String createDate;

    public Log(String logType) {
        this.logType = logType;
    }
}
  • logMapper
    我这里用到了xml,大家可以根据自己的来
public interface logMapper {
    void InsertLog(Log log);
}
  • logService
public class LogService {
    @Autowired
    logMapper logMapper;
    @Async //该方法为异步方法
    public void save(String username, String browser, String ip, ProceedingJoinPoint joinPoint, Log log) {
        SimpleDateFormat dateFormat= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        //通过反射机制 拿到注解中的操作名description
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        com.cidp.aoplog.aop.Log description = method.getAnnotation(com.cidp.aoplog.aop.Log.class);

        //方法路径 例如:com.cidp.aoplog.controller.getUserName() 方法路径
        String methodName = joinPoint.getTarget().getClass().getName() + "." + signature.getName() + "()";
        if (log !=null){
            log.setDescription(description.value());
            log.setRequestIp(ip);
        }
        log.setAddress(StringUtils.getCityInfo(log.getRequestIp()));
        log.setMethod(methodName);
        log.setUsername(username);
        log.setBrowser(browser);
        log.setCreateDate(dateFormat.format(new Date()));
        logMapper.InsertLog(log);
    }

}
  • 测试接口
@RestController
public class TestController {

    @Log("查询测试")
    @GetMapping("/hello")
    public String hello(){
        return "aop log1";
    }

    @Log("删除测试")
    @GetMapping("/test")
    public String hello2(){
        return "aop log2";
    }
}

路漫漫其修远兮

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值