零基础学JavaWeb开发(二十一)之 spring框架(4)

29 篇文章 2 订阅

3、AOP详解

3.1、Aop常用术语

1.连接点(Join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出。在 Spring AOP 中,连接点总是方法的调用。类中的哪些方法可以被增强,这些方法就被称作为连接点。

2.切点(PointCut): 可以插入增强处理的连接点,实际被增强的方法就称作为切入点

3.通知(Advice): AOP 框架中的增强处理,通知描述了切面何时执行以及如何执行增强处理, 实际增强的业务逻辑,该过程就可以称作为通知 前置、后置、环绕通知

4.切面(Aspect): 切面是通知和切点的结合。 把通知应用到的过程 就是为切面

5.引入(Introduction):允许我们向现有的类添加新的方法或者属性。

6.织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的代理对象

1.连接点 该类中哪些方法需要被增强,这些方法就可以称作连接点

3.切点 实际被增强的方法

2.通知 在方法前后执行代码

前置通知 调用方法之前处理...

后置通知 调用完该方法之后处理

环绕通知 在我们被代理方法前后执行

异常通知

最终通知

4.切面 把通知应用到的过程 就是为切面

3.2、Aop环境准备

1.Spring框架一般都是基于AspectJ实现AOP操作

(1)什么是AspectJ

AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法,它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件,AspectJ不是Spring组成部分,独立AOP框架,一般把AspectJ和Spring框架一起使用,进行AOP操作.

2.基于AspectJ实现AOP

(1)基于xml配置文件实现

(2)基于注解方式(偏多的)

3.在项目工程目录引入AOP依赖

3.2.1、maven依赖

        <!-- aspectj支持 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.apache.geronimo.bundles</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.6.8_2</version>
        </dependency>
         <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>

3.2.2、切入点表达式

具体那个类中的那个方法来实现增强

需要描述 该类中哪些方法是需要被增强-----切入点规则

execution( [权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]));

[权限修饰符

1.public.String.com.mayikt.service.MayiktService.addUser(..) --拦截的是

MayiktService类中addUser方法名称 所有参数 返回值String

2.* com.mayikt.service.MayiktService.*(..)拦截我们的

MayiktService类中的所有方法

3.* com.mayikt.service.*.*(..)拦截就是我们 com.mayikt.service.包下

的所有的类所有的方法。

(1)语法接口:
execution( [权限修饰符] [返回类型] [类全路径] [方法名称] ([参数列表]));

//举例1:对com.mayikt.service.MayiktService类里面的 add() 进行增强 execution(*com.mayikt.service.MayiktService.add(..)); // * 表示所有, .. 表示参数列表

//举例2:对com.mayikt.service.MayiktService类里面的 所有方法 进行增强 execution(*com.mayikt.service.MayiktService.*(..));

//举例3:对com.mayikt.service.MayiktService所有类里面的 所有方法 进行增强 execution(*com.mayikt.service.MayiktService.*.*(..));

3.2.3、切入点表达式测试代码

package com.mayikt.service;

import org.springframework.stereotype.Component;

@Component
public class MayiktService {
    public String addMayikt() {
        System.out.println("addMayikt...");
        return "ok";
    }
}


package com.mayikt.proxy;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;


@Component
@Aspect// aop 代理
public class UserProxy {
    /**
     * 前置通知
     */
    @Before("execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public void before() {
        System.out.println("前置通知...");
    }

    /**
     * 后置通知
     */
    @After("execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public void after() {
        System.out.println("后通知...");
    }

    /**
     * 环绕通知
     */
    @Around(value = "execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("环绕通知...");
        System.out.println("目标方法之前开始执行...");
        Object result = proceedingJoinPoint.proceed();
        System.out.println("目标方法之后开始执行...");
        return result;
    }

    //@AfterReturning表达后置通知/返回通知,表达方法返回结果之后执行
    @AfterReturning(value = "execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public void afterReturning() {
        System.out.println("afterReturning");
    }

    //@AfterThrowing表达异常通知
    @AfterThrowing(value = "execution(* com.mayikt.service.MayiktService.addMayikt(..));")
    public void afterThrowing() {
        System.out.println("afterThrowing");
    }

}

3.2.4、开启springaop

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
      ">
    <!--开启注解方式 -->
    <context:component-scan base-package="com.mayikt"></context:component-scan>
    <!--开启 aspectj 生成代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
        ClassPathXmlApplicationContext app =
                new ClassPathXmlApplicationContext("spring_07.xml");
        MayiktService mayiktService = app.getBean("mayiktService", MayiktService.class);
        mayiktService.addMayikt();

3.3、spring框架种使用 cglib?jdk动态代理?

spring aop 底层基于 代理封装?

如果我们 被代理类 没有实现接口的情况下 则使用 cglib动态代理

如果我们被代理类 有实现接口的情况下 则使用 jdk动态代理

3.4、Aop实现统一日志输出

3.4.1、注解定义

package com.cjs.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemControllerLog {

    String description() default "";

    boolean async() default false;

}

package com.cjs.example.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemServiceLog {

    String description() default "";

    boolean async() default false;

}

3.4.2、定义一个类包含所有需要输出的字段

package com.cjs.example.service;

import lombok.Data;
import java.io.Serializable;

@Data
public class SystemLogStrategy implements Serializable {

    private boolean async;

    private String threadId;

    private String location;

    private String description;

    private String className;

    private String methodName;

    private String arguments;

    private String result;

    private Long elapsedTime;


    public String format() {
        return "线程ID: {}, 注解位置: {}, 方法描述: {}, 目标类名: {}, 目标方法: {}, 调用参数: {}, 返回结果: {}, 花费时间: {}";
    }

    public Object[] args() {
        return new Object[]{this.threadId, this.location, this.description, this.className, this.methodName, this.arguments, this.result, this.elapsedTime};
    }

}

3.4.3、定义切面

package com.cjs.example.aspect;

import com.alibaba.fastjson.JSON;
import com.cjs.example.annotation.SystemControllerLog;
import com.cjs.example.annotation.SystemRpcLog;
import com.cjs.example.annotation.SystemServiceLog;
import com.cjs.example.enums.AnnotationTypeEnum;
import com.cjs.example.service.SystemLogStrategy;
import com.cjs.example.util.JsonUtil;
import com.cjs.example.util.ThreadUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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 java.lang.reflect.Method;

@Aspect
public class SystemLogAspect {

    private static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.class);

    private static final Logger LOG = LoggerFactory.getLogger(SystemLogAspect.class);

    @Pointcut("execution(* com.ourhours..*(..)) && !execution(* com.ourhours.logging..*(..))")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object doInvoke(ProceedingJoinPoint pjp) {
        long start = System.currentTimeMillis();

        Object result = null;

        try {
            result = pjp.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            LOG.error(throwable.getMessage(), throwable);
            throw new RuntimeException(throwable);
        } finally {
            long end = System.currentTimeMillis();
            long elapsedTime = end - start;

            printLog(pjp, result, elapsedTime);

        }

        return result;
    }

    /**
     * 打印日志
     * @param pjp   连接点
     * @param result    方法调用返回结果
     * @param elapsedTime   方法调用花费时间
     */
    private void printLog(ProceedingJoinPoint pjp, Object result, long elapsedTime) {
        SystemLogStrategy strategy = getFocus(pjp);

        if (null != strategy) {
            strategy.setThreadId(ThreadUtil.getThreadId());
            strategy.setResult(JsonUtil.toJSONString(result));
            strategy.setElapsedTime(elapsedTime);
            if (strategy.isAsync()) {
                new Thread(()->LOG.info(strategy.format(), strategy.args())).start();
            }else {
                LOG.info(strategy.format(), strategy.args());
            }
        }
    }

    /**
     * 获取注解
     */
    private SystemLogStrategy getFocus(ProceedingJoinPoint pjp) {
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();
        Object[] args = pjp.getArgs();
        String targetClassName = pjp.getTarget().getClass().getName();
        try {
            Class<?> clazz = Class.forName(targetClassName);
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (methodName.equals(method.getName())) {
                    if (args.length == method.getParameterCount()) {

                        SystemLogStrategy strategy = new SystemLogStrategy();
                        strategy.setClassName(className);
                        strategy.setMethodName(methodName);

                        SystemControllerLog systemControllerLog = method.getAnnotation(SystemControllerLog.class);
                        if (null != systemControllerLog) {
                            strategy.setArguments(JsonUtil.toJSONString(args));
                            strategy.setDescription(systemControllerLog.description());
                            strategy.setAsync(systemControllerLog.async());
                            strategy.setLocation(AnnotationTypeEnum.CONTROLLER.getName());
                            return strategy;
                        }
                        SystemServiceLog systemServiceLog = method.getAnnotation(SystemServiceLog.class);
                        if (null != systemServiceLog) {
                            strategy.setArguments(JsonUtil.toJSONString(args));
                            strategy.setDescription(systemServiceLog.description());
                            strategy.setAsync(systemServiceLog.async());
                            strategy.setLocation(AnnotationTypeEnum.SERVICE.getName());
                            return strategy;
                        }
                        
                        return null;
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            LOG.error(e.getMessage(), e);
        }
        return null;
    }

}

3.4.4、配置

这里也可以用组件扫描,执行在Aspect上加@Component注解即可,但是这样的话有个问题。

就是,如果你的这个Aspect所在包不是Spring Boot启动类所在的包或者子包下就需要指定@ComponentScan,因为Spring Boot默认只扫描和启动类同一级或者下一级包。

package com.cjs.example.config;

import com.cjs.example.aspect.SystemLogAspect;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@AutoConfigureOrder(2147483647)
@EnableAspectJAutoProxy(proxyTargetClass = true)
@ConditionalOnClass(SystemLogAspect.class)
@ConditionalOnMissingBean(SystemLogAspect.class)
public class SystemLogAutoConfiguration {

    @Bean
    public SystemLogAspect systemLogAspect() {
        return new SystemLogAspect();
    }
}

3.4.5、自动配置(resources/META-INF/spring.factories)

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ourhours.logging.config.SystemLogAutoConfiguration

3.4.6、其它工具类

3.4.6.1. 获取客户端IP

package com.cjs.example.util;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

public class HttpContextUtils {

    public static HttpServletRequest getHttpServletRequest() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return servletRequestAttributes.getRequest();
    }

    public static String getIpAddress() {
        HttpServletRequest request = getHttpServletRequest();
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
        }else if (ip != null && ip.length() > 15) {
            String[] ips = ip.split(",");
            for (int index = 0; index < ips.length; index++) {
                String strIp = (String) ips[index];
                if (!("unknown".equalsIgnoreCase(strIp))) {
                    ip = strIp;
                    break;
                }
            }
        }
        return ip;
    }
}

3.4.6.2. 格式化成JSON字符串

package com.cjs.example.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class JsonUtil {

    public static String toJSONString(Object object) {
        return JSON.toJSONString(object, SerializerFeature.DisableCircularReferenceDetect);
    }

}

3.4.6.3. 存取线程ID

package com.cjs.example.util;

import java.util.UUID;

public class ThreadUtil {

    private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static String getThreadId() {
        String threadId = threadLocal.get();
        if (null == threadId) {
            threadId = UUID.randomUUID().toString();
            threadLocal.set(threadId);
        }
        return threadId;
    }

}

3.4.7、同时还提供静态方法

package com.cjs.example;

import com.cjs.example.util.JsonUtil;
import com.cjs.example.util.ThreadUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Log {

    private static Logger LOGGER = null;

    private static class SingletonHolder{
        public static Log instance = new Log();
    }

    private Log(){}

    public static Log getInstance(Class<?> clazz){
        LOGGER = LoggerFactory.getLogger(clazz);
        return SingletonHolder.instance;
    }

    public void info(String description, Object args, Object result) {
        LOGGER.info("线程ID: {}, 方法描述: {}, 调用参数: {}, 返回结果: {}", ThreadUtil.getThreadId(), description, JsonUtil.toJSONString(args), JsonUtil.toJSONString(result));
    }

    public void error(String description, Object args, Object result, Throwable t) {
        LOGGER.error("线程ID: {}, 方法描述: {}, 调用参数: {}, 返回结果: {}", ThreadUtil.getThreadId(), description, JsonUtil.toJSONString(args), JsonUtil.toJSONString(result), t);
    }

}

3.4.8、pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.cjs.example</groupId>
    <artifactId>cjs-logging</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>cjs-logging</name>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <aspectj.version>1.8.13</aspectj.version>
        <servlet.version>4.0.0</servlet.version>
        <slf4j.version>1.7.25</slf4j.version>
        <fastjson.version>1.2.47</fastjson.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>${servlet.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectj.version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <goals>
                            <goal>jar</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

3.4.9、工程结构

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

出世&入世

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值