AOP是Aspect-oriented Programming(面向切面编程)的简称,AOP通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。在OOP中模块的基础单位是类,在AOP中模块的基础单位是切面,所以AOP相比较OOP处理粒度更细,Aop能够实现跨越多种类型和对象的关注点(例如事务管理)的模块化。

虽然IOC和AOP作为Spring核心的功能,但AOP功能是独立的,AOP 补充了 Spring IoC 以提供一个非常强大的中间件解决方案。Spring提供了XML配置文件和@AspectJ注解风格两种方式自定义切面,相比较繁杂的配置文件官方更推荐使用注解形式编写切面。

说明:@AspectJ 指的是一种将方面声明为带有注解的常规 Java 类的风格。@AspectJ 样式由AspectJ 项目作为 AspectJ 5 版本的一部分引入 。Spring 解释与 AspectJ 5 相同的注释,使用 AspectJ 提供的库进行切入点解析和匹配。但是,AOP 运行时仍然是纯 Spring AOP,并且不依赖于 AspectJ 编译器或编织器。

1.AOP的核心概念

  • 切面(Aspect):跨越多个类的关注点的模块化。事务管理是企业 Java 应用程序中横切关注点的一个很好的例子。在 Spring AOP 中,方面是通过使用常规类(基于模式的方法)或使用@Aspect注解注解的常规类 (@AspectJ 风格)实现的。
  • 连接点(Join point):程序执行过程中的一个点,例如方法的执行或异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法的执行。
  • 通知(Advice):方面在特定连接点采取的行动。不同类型的建议包括“周围”、“之前”和“之后”建议。(通知类型将在后面讨论。)许多 AOP 框架,包括 Spring,将通知建模为拦截器,并在连接点周围维护一个拦截器链。
  • 切入点(Pointcut):匹配连接点的谓词。Advice 与切入点表达式相关联,并在与切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。由切入点表达式匹配的连接点的概念是 AOP 的核心,Spring 默认使用 AspectJ 切入点表达式语言。
  • 引入(Introduction):代表类型声明额外的方法或字段。Spring AOP 允许您向任何建议的对象引入新的接口(和相应的实现)。引入提供了一种扩展机制,外部接口可以为切面提供额外的功能。
  • 目标对象(Target object):被一个或多个切面建议的对象。也称为“建议对象”。由于 Spring AOP 是使用运行时代理实现的,所以这个对象始终是一个被代理的对象。
  • AOP 代理(AOP proxy):由 AOP 框架创建的对象,用于实现方面契约(建议方法执行等)。在 Spring Framework 中,AOP 代理是 JDK 动态代理或 CGLIB 代理。
  • 织入(Weaving):将方面与其他应用程序类型或对象联系起来以创建建议对象。这可以在编译时(例如,使用 AspectJ 编译器)、加载时或运行时完成。Spring AOP 与其他纯 Java AOP 框架一样,在运行时执行编织。

简单来说AOP是对一个类中方法的代理操作。需要代理方法的所在类被称为切面,即明确了横切的关注点,而连接点是指需要代理的目标方法,即明确了哪些切面类的方法需要被代理,通知是指被代理的方法被代理时切入点执行时机,切入点是指被通知时执行的逻辑单元,通知的类型有:在被代理方法执行前执行切入点、在被代理方法执行完毕后执行切入点、在被代理方法执行前或执行完毕后执行切入点、在被代理方法抛出异常时执行切入点。引入是AOP提供了一种扩展机制,允许外部接口(及其接口实现类)向目标对象提供字段或方法进行扩展。

Spring AOP 通知的类型如下:

  • 前置通知(Before advice):在连接点之前运行的通知,但没有能力阻止执行流继续到连接点(除非它抛出异常)。
  • 后置通知(After acvice):在连接点正常完成后运行的通知(例如,如果方法返回而没有抛出异常)。
  • 后置异常通知(After throwing advice):如果连接点方法通过抛出异常退出,则运行通知。
  • 最终后置通知(After finally advice):不管连接点退出的方式(正常或异常返回)都将运行的通知。
  • 环绕通知(Around advice):绕连接点的通知,例如方法调用。这是最有力的建议。环绕通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续连接点还是通过返回自己的返回值或抛出异常来缩短建议的方法执行。

    2.AOP的实践

    2.1 Aop基础实践

    步骤:
    (1).添加Aop依赖。
    (2).定义切面类。定义切点指定切点表达式(下面例子中使用@Aop注解作为切点表达式),定义通知。
    (3).测试。
    1. <dependencys>
    2. <!-- aop依赖 -->
    3. <dependency>
    4. <groupId>org.springframework.boot</groupId>
    5. <artifactId>spring-boot-starter-aop</artifactId>
    6. </dependency>
    7. <!-- 测试依赖 -->
    8. <dependency>
    9. <groupId>org.springframework.boot</groupId>
    10. <artifactId>spring-boot-starter-test</artifactId>
    11. </dependency>
    12. </dependencys>
    ```java // 自定义注解 package com.fly.annotation; import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) // 注解的作用范围 @Documented @Inherited public @interface Aop {}

```java
// Aop切面类
package com.fly.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.stereotype.Component;

@Aspect // 使用@Aspect注解标注当前类为一个切面类
@Component // 注入到Spring容器
public class AopAspect implements ThrowsAdvice {

    // 使用@Pointcut注解指定连接点表达式定义切点,当遇到Aop注解时就会进行通知
    @Pointcut("@annotation(com.fly.annotation.Aop)")
    public void pointcut(){}

    /**
     * 定义前置通知并指定连接点(也可以指定表达式),前置通知方法在连接点方法执行前执行。
     * @param joinPoint JoinPoint对象封装了SpringAop中切面方法的信息,
     * 例如代理的目标对象、目标对象的参数、签名等等。
     * @return
     */
    @Before("pointcut()")
    public void beforeAdvice(JoinPoint joinPoint){
        // 获取目标拦截方法参数
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            System.out.println("参数:"+arg);
        }
        // 获取目标拦截方法签名
        MethodSignature signature =(MethodSignature) joinPoint.getSignature();
        System.out.println("目标代理对象:"+joinPoint.getTarget());
        System.out.println("连接点方法实例:"+joinPoint.getThis());
        System.out.println("目标拦截方法名:"+signature.getName());
        /**
         * PUBLIC: 1
         * PRIVATE: 2
         * PROTECTED: 4
         * STATIC: 8
         * FINAL: 16
         * SYNCHRONIZED: 32
         * VOLATILE: 64
         * TRANSIENT: 128
         * NATIVE: 256
         * INTERFACE: 512
         * ABSTRACT: 1024
         * STRICT: 2048
         */
        System.out.println("目标拦截方法修饰符:"+signature.getModifiers());
        System.out.println("拦截方法所在类的类型:"+signature.getDeclaringType());
        System.out.println("拦截方法所在类的类型名:"+signature.getDeclaringTypeName());
        System.out.println("拦截方法返回值类型:"+signature.getReturnType());
        System.out.println("拦截方法参数名数组:"+signature.getParameterNames());
        System.out.println("拦截方法异常数组:"+signature.getExceptionTypes());
        System.out.println("AopAspect beforeAdvice...");
    }

    /**
     * 使用@After注解定义后置通知并指定连接表达式,后置通知方法在连接点方法执行后执行。
     * @param joinPoint JoinPoint对象封装了SpringAop中切面方法的信息,
     * 例如代理的目标对象、目标对象的参数、签名等等。
     */
    @After("@annotation(com.fly.annotation.Aop)")
    public void afterAdvice(JoinPoint joinPoint){
        System.out.println("AopAspect afterAdvice...");
    }

    /**
     * 使用@AfterReturning注解定义异常通知。在连接点方法抛出异常时执行。
     * @param joinPoint JoinPoint对象封装了SpringAop中切面方法的信息,
     * 例如代理的目标对象、目标对象的参数、签名等等。
     * @param ex 连接点方法抛出的异常对象
     */
    @AfterThrowing(value="pointcut()",throwing="ex")
    public void throwingAdvice(JoinPoint joinPoint,Exception ex){
        System.out.println("AopAspect throwingAdvice...");
        System.out.println("异常信息:"+ex.getMessage());
    }

    /**
     * 使用@AfterReturning注解定义最终后置通知(有返回值),在连接点方法执行后总会执行。
     * @param joinPoint JoinPoint对象封装了SpringAop中切面方法的信息,
     * 例如代理的目标对象、目标对象的参数、签名等等。
     * @param returnValue 目标连接点方法的返回值。
     */
    @AfterReturning(value="pointcut()",returning = "returnValue")
    public Object afterReturningAdvice(JoinPoint joinPoint,Object returnValue) throws Throwable {
        System.out.println("AopAspect afterReturningAdvice... 目标方法返回值:"+returnValue);
        return returnValue;
    }

    /**
     * 使用@Around注解定义环绕通知,在连接点方法执行前或执行后都会执行。
     * 注意:只有环绕通知的方法第一个可以是ProceedingJoinPoint类型外,其他通知类型方法第一个参数都是
     * JoinPoint,若使用ProceedingJoinPoint类型程序将抛出错误。
     * @param joinPoint ProceedingJoinPoint接口继承自JoinPoint接口,
     * ProceedingJoinPoint接口是在JoinPoint的基础上暴露出 proceed 这个方法,
     * proceed方法是aop代理链执行的方法。
     * @return 目标方法的返回值,环绕通知的返回值就是目标连接点方法的返回值。joinPoint.proceed()
     * 表示调用目标代理方法。
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("AopAspect aroundAdvice...");
        return  joinPoint.proceed()+" aroundAdvice...";
    }
}
package com.fly.component;
import com.fly.annotation.Aop;
import org.springframework.stereotype.Component;

@Component
public class AopComponent {

    @Aop
    public String testBaseAdvice(String text){
        return  text;
    }

    @Aop
    public void testThrowingAdvice() throws IllegalArgumentException {
        throw  new  IllegalArgumentException("custom error");
    }
}
package com.fly;
import com.fly.component.AopComponent;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
public class Test {
    @Autowired
    AopComponent aopComponent;

    @org.junit.Test
    public void test01(){
        String result=aopComponent.testBaseAdvice("我叫z乘风");
        System.out.println("testBaseAdvice返回值:"+result);
    }

    @org.junit.Test
    public void test02() throws Exception {
        // 故意使用1/0抛出错误
        aopComponent.testThrowingAdvice();
    }
}
#test01()执行结果
AopAspect aroundAdvice...
参数:我叫z乘风
目标代理对象:com.fly.component.AopComponent@7bca6fac
连接点方法实例:com.fly.component.AopComponent@7bca6fac
目标拦截方法名:testBaseAdvice
目标拦截方法修饰符:1
拦截方法所在类的类型:class com.fly.component.AopComponent
拦截方法所在类的类型名:com.fly.component.AopComponent
拦截方法返回值类型:class java.lang.String
拦截方法参数名数组:[Ljava.lang.String;@5c60b0a0
拦截方法异常数组:[Ljava.lang.Class;@7a2b1eb4
AopAspect beforeAdvice...
AopAspect afterReturningAdvice... 目标方法返回值:我叫z乘风
AopAspect afterAdvice...
testBaseAdvice返回值:我叫z乘风 aroundAdvice...


#test02执行结果
AopAspect aroundAdvice...
目标代理对象:com.fly.component.AopComponent@7bca6fac
连接点方法实例:com.fly.component.AopComponent@7bca6fac
目标拦截方法名:testThrowingAdvice
目标拦截方法修饰符:1
拦截方法所在类的类型:class com.fly.component.AopComponent
拦截方法所在类的类型名:com.fly.component.AopComponent
拦截方法返回值类型:void
拦截方法参数名数组:[Ljava.lang.String;@1dc76fa1
拦截方法异常数组:[Ljava.lang.Class;@5eed2d86
AopAspect beforeAdvice...
AopAspect throwingAdvice...
异常信息:custom error
AopAspect afterAdvice...

2.2 @Pointcut表达式

2.2.1 execution表达式

由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的部件,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是比较广泛的。execution语法如下:

#?表示可选
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

modifiers-pattern(可选):表示方法的可见性。例如:public、protected、private。
ret-type-pattern:方法的返回值类型。例如:int、void等,*号通配符表示任意类型返回值。
declaring-type-pattern:方法所在类的全路径名。例如:com.spring.Aspect。
name-pattern:方法名。例如:buisinessService()。
param-pattern:方法的参数类型。例如:java.lang.String。
throws-pattern:方法抛出的异常类型。例如:java.lang.Exception。

通配符:
*通配符:该通配符主要用于匹配单个单词,或者是以某个词为前缀或后缀的单词。
..通配符:该通配符表示0个或多个项,主要用于declaring-type-pattern和param-pattern中,如果用于declaring-type-pattern中,则表示匹配当前包及其子包,如果用于param-pattern中,则表示匹配0个或多个参数。

#例子1:
@Pointcut("execution(public * com.spring.service.BusinessObject.businessService(java.lang.String,..))")
public void pointcut(){}

#解析:
以上表达式会匹配使用public修饰,返回值为任意类型,且是com.spring.BusinessObject类中名称为businessService的方法,方法可以有多个参数,但是第一个参数必须是java.lang.String类型的方法
#例子2:
@Pointcut("execution(* com.spring.service..*.businessService())")
public void pointcut(){}

#解析:
以上表达式会匹配任意类型返回值,并且是com.spring.service包及其子包下的任意类的名称为businessService的方法,且方法不能包含参数

2.2.2 within表达式

within表达式的粒度为类,其参数为全路径的类名(可使用通配符),表示匹配当前表达式的所有类都将被当前方法环绕,语法为:

within(declaring-type-pattern)

例子1:within表达式只能指定到类级别,如下示例表示匹配com.fly.User中的所有方法。

@Pointcut("within(com.fly.User)")
public void pointcut(){}

例子2: within表达式路径和类名都可以使用通配符进行匹配,比如如下表达式将匹配com.fly.service包下的所有类,但不包括子包中的类的所有方法。

@Pointcut("within(com.fly.service.*)")
public void pointcut(){}

例子3:下面表达式表示匹配com.fly.service包及子包下的所有类的所有方法。

@Pointcut("within(com.fly.service.*)")
public void pointcut(){}

2.2.3 args表达式

args表达式的作用是匹配指定参数类型和指定参数数量的方法,无论其类路径或者是方法名是什么。这里需要注意的是,args指定的参数必须是全路径的。args表达式的语法:

args(param-pattern)

例子1:下面args表达式表示匹配所有只有一个参数,并且参数类型是java.lang.String类型的方法。

@Pointcut("args(java.lang.String)")
public void pointcut(){}

例子2:args表达式中也可以使用通配符.但通配符只能使用..,而不能使用*。如下是使用通配符的实例,该切点表达式将匹配第一个参数为java.lang.String,最后一个参数为java.lang.Integer,并且中间可以有任意个数和类型参数的方法:

@Pointcut("args(java.lang.String,..,java.lang.Integer)")
public void pointcut(){}

2.2.4 this与target表达式

this与target的作用类似,都可以用于指定类或接口。在面向切面编程规范中,this表示匹配调用当前切点表达式所指代对象方法的对象,target表示匹配切点表达式指定类型的对象。假设有A和B两个类,A类调用了B类的某个方法,如果切点表达式为this(B),那么A的实例将会被匹配,表示A类中的所有方法会被当前切点表达式的Advice环绕;如果这里切点表达式为target(B),那么B的实例会被匹配,表示B类中的所有方法会被当前切点表达式的Advice环绕。

在切面编程中,有一个目标对象(target Object),也有一个代理对象(this Object),目标对象是我们声明的业务逻辑对象,而代理对象是使用切面逻辑对业务逻辑进行包裹之后生成的对象。如果使用的是Jdk动态代理,那么业务对象和代理对象将是两个对象,在调用代理对象逻辑时,其切面逻辑中会调用目标对象的逻辑;如果使用的是Cglib代理,由于是使用的子类进行切面逻辑织入的,那么只有一个对象,即织入了代理逻辑的业务类的子类对象,此时是不会生成业务类的对象的。
在Spring中,对this的语义进行了改写,即如果当前对象生成的代理对象符合this指定的类型,那么就为其织入切面逻辑。简单的说就是,this将匹配代理对象为指定类型的类。target的语义则没有发生变化,即其将匹配业务对象为指定类型的类。如下是使用this和target表达式的简单示例:

this(com.fly.service.UserService)
target(com.fly.service.UserService)

this和target的使用区别其实不大,大部分情况下其使用效果是一样的,但其区别也还是有的。Spring使用的代理方式主要有两种:JDK代理和Cglib代理,针对这两种代理类型,关于目标对象与代理对象,理解如下两点是非常重要的:

  • 如果目标对象被代理的方法是其实现的某个接口的方法,那么将会使用Jdk代理生成代理对象,此时代理对象和目标对象是两个对象,并且都实现了该接口。
  • 如果目标对象是一个类,并且其没有实现任意接口,那么将会使用Cglib代理生成代理对象,并且只会生成一个对象,即Cglib生成的代理类的对象。

结合上述两点说明,这里理解this和target的异同就相对比较简单了,可以分为三种情况说明:

  • this(SomeInterface)或target(SomeInterface):这种情况下,无论是对于Jdk代理还是Cglib代理,其目标对象和代理对象都是实现SomeInterface接口的(Cglib生成的目标对象的子类也是实现了SomeInterface接口的),因而this和target语义都是符合的,此时这两个表达式的效果一样。
  • this(SomeObject)或target(SomeObject),这里SomeObject没实现任何接口,这种情况下,Spring会使用Cglib代理生成SomeObject的代理类对象,由于代理类是SomeObject的子类,子类的对象也是符合SomeObject类型的,因而this将会被匹配,而对于target,由于目标对象本身就是SomeObject类型,因而这两个表达式的效果一样。
  • this(SomeObject)或target(SomeObject),这里SomeObject实现了某个接口,对于这种情况,虽然表达式中指定的是一种具体的对象类型,但由于其实现了某个接口,因而Spring默认会使用Jdk代理为其生成代理对象,Jdk代理生成的代理对象与目标对象实现的是同一个接口,但代理对象与目标对象还是不同的对象,由于代理对象不是SomeObject类型的,因而此时是不符合this语义的,而由于目标对象就是SomeObject类型,因而target语义是符合的,此时this和target的效果就产生了区别;这里如果强制Spring使用Cglib代理,因而生成的代理对象都是SomeObject子类的对象,其是SomeObject类型的,因而this和target的语义都符合,其效果就是一致的。

    2.2.5 @annotation表达式

    @annotation的使用方式与@within的相似,表示匹配使用@annotation指定注解标注的方法将会被环绕,语法如下:
    @annotation(annotation-type)
    
    例子1:下面表达式表示匹配使用com.fly.annotation.Logger注解标注的方法
    @Pointcut("@annotation(com.fly.annotation.Logger)")
    public void pointcut(){}
    

    2.3 使用Introduction(引入)扩展对象

    @DeclareParents也称为Introduction(引入),表示为指定的目标类引入新的属性和方法。关于@DeclareParents的原理其实比较好理解,因为无论是Jdk代理还是Cglib代理,想要引入新的方法,只需要通过一定的方式将新声明的方法织入到代理类中即可,因为代理类都是新生成的类,因而织入过程也比较方便。 ```java package com.fly.introduction; public interface Performance { void dance(); }

package com.fly.introduction; public class Dancer implements Performance{ @Override public void dance() { System.out.println(“dance”); } }

// Singer就是被引入的接口 package com.fly.introduction; public interface Singer { void sing(); }

package com.fly.introduction; public class BackSinger implements Singer{ @Override public void sing() { System.out.println(“sing”); } }

```java
// 核心代码
package com.fly.introduction;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class SingerIntroducer {
    /**
     * @DeclareParents可以将标注的接口引入到@DeclareParents注解value对应的接口,
     * 由于@DeclareParents标注接口并没有实际作用,所以@DeclareParents提供了defaultImpl属性
     * 用于指定标注接口的实现类。
     *
     * 在下面示例中,@DeclareParents标识接口为Singer,value属性值为com.fly.introduction
     * .Performance+(+表示所有Performance接口),defaultImpl为BackSinger.class,大致意思为
     * 所有实现了Performance接口的类都具有Singer接口中的方法,即Singer接口引入(Introducer)到        * 了Performance接口的实现类中,Singer接口通过织入增强了Performance接口。
     */
    @DeclareParents(value="com.fly.introduction.Performance+",defaultImpl = BackSinger.class)
    public static Singer singer;
}
package com.fly.introduction;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@EnableAspectJAutoProxy // 开启切面自动代理
@ComponentScan("com.fly.introduction")
public class ConcertConfig {
    @Bean(name = "dancer")
    public Performance dancer(){
        return new Dancer();
    }
    @Bean
    SingerIntroducer singerIntroducer(){
        return new SingerIntroducer();
    }
}
package com.fly;
import com.fly.component.AopComponent;
import com.fly.introduction.Performance;
import com.fly.introduction.Singer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


@SpringBootApplication
public class TestStartApp implements ApplicationRunner {

    @Autowired
    AopComponent aopComponent;

    public static void main(String[] args) throws InterruptedException {
        SpringApplication.run(TestStartApp.class);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        ApplicationContext context=new
                AnnotationConfigApplicationContext("com.fly.introduction");
        Performance performance=(Performance) context.getBean("dancer");
        // 由于Performance接口引入了Singer,即使它们直接的联系仍可以进行强转
        Singer singer=(Singer) performance;
        singer.sing(); // 打印:sing
    }
}

3.AOP的应用场景

自定义注解和AOP是最佳搭档,一般通过自定义注解标记作为连接点。Aop的好处是可以进行代码逻辑复用,提高程序的灵活性。Aop的使用场景有:

  • Authentication 权限。利用Aop前置通知判断是否具有权限,
  • Caching 缓存。利用Aop前置通知判断传入key是否命中缓存,若命中则返回缓存中的数据,否则读取数据源中的数据并存入缓存。
  • Context passing 内容传递。
  • Error handling 错误处理。通过Aop的后置最终通知可以获取到异常信息。
  • Lazy loading 懒加载
  • Debugging 调试
  • logging, tracing, profiling and monitoring 记录跟踪 优化 校准
  • Performance optimization 性能优化
  • Persistence 持久化
  • Resource pooling 资源池
  • Synchronization 同步
  • Transactions 事务。

    2.1 Aop+自定义注解+SpEL实现日志注解

    利用AOP+自定义注解+SpEL实现日志注解,记录接口的请求时间、调用状态(正常还是异常)、方法参数、方法返回值、操作人、操作动作等等。 ```java package com.fly.entity;

import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import lombok.experimental.Accessors; import java.time.LocalDateTime;

/**

  • 日志类。(lombok我们的好兄弟)
  • @Data:为当前标记类所有属性成员生成getter和setter方法
  • @AllArgsConstructor:为当前标记类生成有参构造
  • @NoArgsConstructor:为当前标记类生成无参构造
  • @ToString:为当前标记类生成toString()
  • @Accessors:开启链式调用 */ @Data @AllArgsConstructor @NoArgsConstructor @ToString @Accessors(chain = true) public class SysLogger { // 日志id private Integer logId; // 操作用户id private Integer userId; // 请求时间 private LocalDateTime reqTime; // 请求ip private String reqIP; // 请求状态,0成功,1失败 private int status; // 操作动作 private Integer operation; // 请求方法名,由请求方法所在类全限定名+.+请求方法名,例如:com.fly.component.LoggerComponent.hello() private String methodName; // 接口参数,例如(String name,int age) 转换为 {name:xxx,age:18}格式存储 private String parameters; // 接口返回值类型 private String returnType; // 接口返回值 private String returnValue; // 接口执行时间 private Long executionTime; // 描述,由@Logger注解的value属性获得 private String describe; // 关键字,解析@Logger注解的spElValue表达式获得 private String keyword; } java package com.fly.enums;

/**

  • 操作枚举类(枚举真的很强大) */ public enum OperationEnum { CREATE(0, “创建”), DELETE(1, “删除”), UPDATE(2, “修改”), SELECT(3, “查询”), VERIFY(4, “验证或审核”), CANCEL_VERIFY(5, “取消审核或验证”), CALL(6, “执行”), START(7, “启动”), STOP(8, “停止”), RESTART(9, “重启”), CIRCULATION(10, “流转”), IMPORT(11, “导入”), EXPORT(12, “导出”), SEND(13, “发送”), RECEIVE(14, “接收”), REPLY(15, “回复”), COMMENT(16, “评论”), LIKE(17, “点赞”), CANCEL_LIKE(18,”取消点赞”);

    private final int value; private final String action;

    public int value() {

     return this.value;
    

    }

    OperationEnum(int value, String action) {

     this.value = value;
     this.action = action;
    

    } } java package com.fly.annotation;

import com.fly.enums.OperationEnum; import java.lang.annotation.*;

/**

  • 如果一个类用上了@Inherited修饰的注解,那么其子类也会继承这个注解。 / @Inherited /*
  • 注解应该被 javadoc工具记录,默认情况下,javadoc是不包括注解的,
  • 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理,
  • 所以注解类型信息也会被包括在生成的文档中。 / @Documented /*
  • @Retention定义标记的注解保留状态,它有如下三种策略:
  • RetentionPolicy.SOURCE:注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃;被编译器忽略。
  • RetentionPolicy.CLASS:注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。
  • RetentionPolicy.RUNTIME(常用):注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。 / @Retention(RetentionPolicy.RUNTIME) /*
  • @Target定义标记注解的作用目标,有如下枚举值(可以组合多个使用):
  • ElementType.TYPE:作用于接口、类、枚举、注解上。
  • ElementType.FIELD:作用于字段、枚举的常量上。
  • ElementType.METHOD:作用于方法上。
  • ElementType.PARAMETER:作用于方法的参数上。
  • ElementType.LOCAL_VARIABLE:作用于布局变量上。
  • ElementType.ANNOTATION_TYPE:作用于注解上。
  • ElementType.PACKAGE:作用于包上。
  • ElementType.CONSTRUCTOR:作用于构造方法上。
  • ElementType.TYPE_PARAMETER:作用于类型参数,JDK1.8提供。
  • ElementType.TYPE_USE:作用于任何类型名称,JDK1.8提供。 / @Target(ElementType.METHOD) public @interface Logger { // 日志说明 String value(); // SpEL表达式 String spElValue(); /*
    • 操作类型。这里也可以不用此属性,可以根据@Logger标记的方法名的前缀判断操作类型,例如selectUser()方法
    • 以select为前缀,操作类型则为OperationEnum.SELECT */ OperationEnum operation() default OperationEnum.SELECT; } java package com.fly.util;

import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.lang.reflect.Method;

/**

  • 用于解析SpEl表达式 */ public class SpElUtil { // 用于解析SpEl表达式 private static SpelExpressionParser parser=new SpelExpressionParser();

    // 用于获取方法的参数名称 private static DefaultParameterNameDiscoverer nameDiscoverer=new DefaultParameterNameDiscoverer();

    public static String generateKeyBySpEL(String spELString, ProceedingJoinPoint joinPoint){

     // 通过joinPoint获取方法签名
     MethodSignature signature=(MethodSignature) joinPoint.getSignature();
     // 根据签名获取方法
     Method method = signature.getMethod();
     // 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
     String[] parameterNames = nameDiscoverer.getParameterNames(method);
     // 解析spEL表达式得到表达式对象
     Expression expression = parser.parseExpression(spELString);
     // 实例化Spring的表达式上下文对象
     EvaluationContext context=new StandardEvaluationContext();
     // 通过joinPoint获取方法形参
     Object[] args = joinPoint.getArgs();
     // 循环给表达式上下文对象赋值
     for (int i = 0; i < args.length; i++) {
         context.setVariable(parameterNames[i], args[i]);
     }
     return expression.getValue(context).toString();
    

    } }

```java
package com.fly.aspect;

import com.fly.annotation.Logger;
import com.fly.entity.SysLogger;
import com.fly.util.SpElUtil;
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.aspectj.lang.reflect.MethodSignature;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;


/**
 * LoggerAspect 切面类
 * @Aspect:将当前类标记为切面类
 * @Component:将当前类标记为组件Bean并注入到Spring IOC容器
 */
@Aspect
@Component
public class LoggerAspect {
    @Autowired
    private HttpServletRequest request;

    // 定义连接点
    @Pointcut("@annotation(com.fly.annotation.Logger)")
    public void pointcut(){}

    /**
     * long转LocalDateTime
     * @param time 时间戳
     * @return time转为LocalDateTime的结果值
     */
    public LocalDateTime convert(long time){
       return LocalDateTime.ofInstant(Instant.ofEpochMilli(time), ZoneId.systemDefault());
    }

    /**
     * Object[]转json字符串,此方法只是做简单处理,对于复杂类型(嵌套类型,如List嵌套Map等等)并未提供支持
     * @param args 形参值数组
     * @param parameters 参数数组
     * @return 解析好的json字符串
     */
    public String toJsonStr(Object[] args,Parameter[] parameters){
        int len=args.length;
        if(len==0) return "{}";
        StringBuilder sb=new StringBuilder();
        sb.append("{");
        // 逆向循环性能最优
        for (int i = len-1; i >=0; i--) {
            sb.append(args[i]+":"+parameters[i].getName()+",");
        }
        return sb.substring(0,sb.length()-1)+"}";
    }

    /**
     * 获取ip地址
     * @return ip
     */
    public String getIpAddress(){
        String ip = null;

        //X-Forwarded-For:Squid 服务代理
        String ipAddresses = request.getHeader("X-Forwarded-For");

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //Proxy-Client-IP:apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //WL-Proxy-Client-IP:weblogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //HTTP_CLIENT_IP:有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }

        if (ipAddresses == null || ipAddresses.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            //X-Real-IP:nginx服务代理
            ipAddresses = request.getHeader("X-Real-IP");
        }

        //有些网络通过多层代理,那么获取到的ip就会有多个,一般都是通过逗号(,)分割开来,并且第一个ip为客户端的真实IP
        if (ipAddresses != null && ipAddresses.length() != 0) {
            ip = ipAddresses.split(",")[0];
        }

        //还是不能获取到,最后再通过request.getRemoteAddr();获取
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ipAddresses)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 通过环绕通知,得到SysLogger所需要的属性值并保存到数据库
     * @param joinPoint JoinPoint接口封装了SpringAop中切面方法的信息,ProceedingJoinPoint接口继承自
     *                  JoinPoint接口
     * @return 返回拦截方法的返回值
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable{
        long beginTime=System.currentTimeMillis();
        SysLogger sysLogger=new SysLogger()
                .setUserId(1)
                .setReqTime(convert(beginTime))
                .setReqIP(getIpAddress());
        Object result=null;
        try{
            result = joinPoint.proceed();
            sysLogger.setReturnValue(result.toString())
                    .setStatus(0);
        }catch (Exception e){
            sysLogger.setStatus(1);
        }finally {
            // 执行时长(毫秒)
            long endTime=System.currentTimeMillis()-beginTime;
            sysLogger.setExecutionTime(endTime);
            addLogger(joinPoint,sysLogger);
        }
        return result;
    }
    // 添加日志方法
    public void addLogger(ProceedingJoinPoint joinPoint,SysLogger sysLogger){
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Logger logger = method.getAnnotation(Logger.class);
        String returnType=method.getReturnType().getName();
        sysLogger.setReturnType(returnType);
        // 首先判断方法上使用使用@Logger注解标记,
        if(logger!=null){
           // 判断@Logger注解的value属性是否为空
           if(StringUtils.isNotBlank(logger.value())){
               sysLogger.setDescribe(logger.value());
           }
           // 判断@Logger注解的spElValue属性是否为空
           if(StringUtils.isNotBlank(logger.spElValue())){
               String result = SpElUtil.generateKeyBySpEL(logger.spElValue(), joinPoint);
               sysLogger.setKeyword(result);
           }
           int operation= logger.operation().value();
           if(StringUtils.isNotBlank(String.valueOf(operation))){
               sysLogger.setOperation(operation);
           }
        }
        // 获取Aop拦截方法所在类的类全限定名称
        String className = joinPoint.getTarget().getClass().getName();
        // 获取请求方法名
        String methodName=signature.getName();
        sysLogger.setMethodName(className+"."+methodName+"()");

        // 获取拦截方法形参,不含参数名,只包含参数值
        Object[] args = joinPoint.getArgs();
        // 获取拦截方法参数对象
        Parameter[] parameters = method.getParameters();
        sysLogger.setParameters(toJsonStr(args,parameters));
        // 填充sysLogger数据后保存到数据库(模拟)...
        System.out.println(sysLogger.toString());
    }
}
package com.fly.component;

import com.fly.annotation.Logger;
import com.fly.enums.OperationEnum;
import org.springframework.stereotype.Component;

@Component
public class LoggerComponent {

    @Logger(value = "hello()测试",spElValue ="'name:'+ #name",operation=OperationEnum.SELECT)
    public String hello(String name,int age){
        return "hello,我叫"+name+",今年:"+age+"岁。";
    }
}
package com.fly;

import com.fly.component.LoggerComponent;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * 测试类
 */
@SpringBootTest
@RunWith(SpringRunner.class)
public class Test01 {

    @Autowired
    LoggerComponent loggerComponent;

    @Test
    public void test01(){
        loggerComponent.hello("z乘风", 18);
    }
}
# 执行结果:
SysLogger(logId=null, userId=1, reqTime=2021-08-16T00:17:48.431, reqIP=127.0.0.1, status=0, operation=3, methodName=com.fly.component.LoggerComponent.hello(), parameters={18:age,z乘风:name}, returnType=java.lang.String, returnValue=hello,我叫z乘风,今年:18岁。, executionTime=18, describe=hello()测试, keyword=name:z乘风)

4.AOP的实现原理

4.1 代理模式

代理模式是动态代理的基石,代理模式(Proxy Pattern)是23种设计模式中属性结构性模式的一种,其作用是为对象提供一种代理以控制对这个对象的访问。代理模式中有抽象接口、被代理类和代理类三个角色,被代理类和代理类都需要实现这个抽象接口。例如代购商品场景,假设客户想购买国外的光刻机,但由于路途遥远等成本问题,客户交由境外公司进行代购,公司会在购买商品前进行商品质量的筛选,购买后将商品快递给客户。上面例子的购买商品就是一个抽象接口,客户就是被代理类,境外公司就是代理类,客户和境外公司都具有购买商品的功能,但是境外公司在购买商品前提供了筛选功能和购买后提供了发货功能,即代理类可以组织逻辑增强被代理类。

package com.fly.proxy.statics;
/**
 * 商品抽象接口,提供购买商品功能,被代理类和代理类都需要实现此接口
 */
public interface Goods {
    void buyGoods(String goodsName);
}
package com.fly.proxy.statics;
/**
 * 被代理类
 */
public class XiaoMing implements Goods {
    @Override
    public void buyGoods(String goodsName) {
        System.out.println("小明要买"+goodsName);
    }
}
package com.fly.proxy.statics;

/**
 * 商品代理类
 */
public class GoodsProxy implements Goods {
    // 获取被代理类
    private Goods goods=new XiaoMing();
    @Override
    public void buyGoods(String goodsName) {
        System.out.println("代购"+goodsName+"前进行商品筛选,查看商品是否有现货...");
        goods.buyGoods(goodsName);
        System.out.println("代购"+goodsName+"后发货给客户...");
    }
}
package com.fly.proxy.statics;

public class Test {
    public static void main(String[] args) {
        GoodsProxy proxy=new GoodsProxy();
        proxy.buyGoods("爱疯12 Pro Max");
    }
}
执行结果:
代购爱疯12 Pro Max前进行商品筛选,查看商品是否有现货...
小明要买爱疯12 Pro Max
代购爱疯12 Pro Max后发货给客户...

从上面结果来看,代理模式具有良好的扩展性,代理类可以在被代理类调用方法前后进行逻辑增加,但静态代理具有一个致命的缺点:一个代理类只能服务一个被代理类,如果要为其他客户进行代理,那么则需要再编写一个代理类,当有很多被代理类时,就需要编写大量的代理类,相对来说比较繁琐。

4.2 动态代理

动态代理指利用反射机制在运行时创建代理类,动态代理的出现就是解决静态代理的弊端,通过反射机制动态生成代理类,从而无需编写大量代理类。动态代理的实现方式主要分为JDK动态代理字节码类库动态代理两种方式,常用字节码类库如下:

  • Apache BCEL(Byte Code Engineering Library):是Java classworking广泛使用的一种框架,它可以深入到JVM汇编语言进行类操作的细节。
  • ObjectWeb ASM:是一个Java字节码操作框架,它可以用于直接以二进制形式动态生成stub根类或其他代理类,或者在加载时动态修改类。
  • CGLIB(Code Generation Library):是一个功能强大,高性能和高质量的代码生成库,用于扩展JAVA类并在运行时实现接口。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
  • Javassist:是Java的加载时反射系统,它是一个用于在Java中编辑字节码的类库; 它使Java程序能够在运行时定义新类,并在JVM加载之前修改类文件。

    4.2.1 JDK动态代理

    java.lang.reflect.InvocationHandler接口和java.lang.reflect.Proxy类是实现JDK动态代理的核心成员,其中InvocationHandler接口提供了invoke()方法,每当生成后代理类对象调用方法时都会转发至invoke(),invoke()负责组织代理对象调用真实方法的逻辑控制,例如在真实方法调用前后或异常情况下执行一些业务,AOP的通知机制就可以通过该方式实现。Proxy类通过newProxyInstance()方法动态生成代理类。 ```java // 提供一个抽象接口 package com.fly.proxy.dynamic.jdk; public interface People { void run(); }

package com.fly.proxy.dynamic.jdk; public class Student implements People{ @Override public void run() { System.out.println(“Student run…”); } }

```java
package com.fly.proxy.dynamic.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 动态代理处理类。动态代理必须要实现InvocationHandler接口,每当生成的动态代理对象调用方法时,
 * 就会被转发到实现InvocationHandler接口的invoke方法调用
 */
public class LogHandler implements InvocationHandler {

    // 被代理的目标对象
    Object target;
    public LogHandler(Object target){
        this.target=target;
    }

    /**
     * 当动态代理对象调用方法时就会转发至invoke()方法执行
     * @param proxy 代理类代理的真实代理对象
     * @param method 被代理目标对象的Method对象
     * @param args 被代理目标对象方法的参数
     * @return 被代理目标对象方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        // 通过invoke传入参数调用被代理的目标对象(target)的方法得到方法执行结果
        Object invoke = method.invoke(target, args);
        after();
        return invoke;
    }
    public void before(){
        System.out.println("before...");
    }
    public void after(){
        System.out.println("after...");
    }
}
package com.fly.proxy.dynamic.jdk;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;


/**
 * java.lang.reflect.InvocationHandler接口和 java.lang.reflect.Proxy类是实现
 * JDK动态代理的核心成员,其中InvocationHandler接口提供了invoke()方法,每当生成后代理类对象
 * 调用方法时都会转发至invoke(),invoke()负责组织代理对象调用真实方法的逻辑控制,例如在真实方法调用
 * 前后或异常情况下执行一些业务,AOP的通知机制就可以通过该方式实现。Proxy类通过newProxyInstance()
 * 方法动态生成代理类。
 *
 * JDK实现动态代理目标类必须要实现InvocationHandler接口,相比较cglib这是JDK动态代理的缺点,
 * cglib支持实现接口和继承类两种方式生成代理类。
 */
public class Test {
    public static void main(String[] args) {
        // 创建被代理的目标对象
        Student student=new Student();
        People proxy =(People) getInstance(student);
        proxy.run();

        // 窥探生成代理类的模样
        generateClassFile(student.getClass(),"PeopleProxy");
    }

    /**
     * 根据目标代理对象获取代理类
     * @param target 目标代理对象
     * @return
     */
    public static Object getInstance(Object target){
        // 1.获取被代理目标对象的classLoader,ClassLoader的作用是将.class文件加载到JVM中
        ClassLoader classLoader = target.getClass().getClassLoader();
        // 2.获取被代理目标对象的所有接口,JDK只能对接口进行动态代理
        Class<?>[] interfaces = target.getClass().getInterfaces();
        // 3.根据目标代理对象得到对应的动态代理类
        InvocationHandler handler = new LogHandler(target);
        /**
         * 4.JDK根据传入的类加载器、接口、动态代理类动态地在内存中创建和.class 文件等同的字节码,
         * 并根据相应的字节码转换成对应的class,最后使用newInstance()创建代理实例
         */
        return Proxy.newProxyInstance(classLoader, interfaces, handler);
    }

    /**
     * 这里提供一个工具方法查看生成代理类
     * @param clazz
     * @param proxyName
     */
    public static void generateClassFile(Class clazz, String proxyName){
        // 根据类信息和提供的代理类名称,生成字节码
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
        // 根据类获取类所在路径
        String paths = clazz.getResource(".").getPath();
        System.out.println("目标对象代理类path:"+paths);
        FileOutputStream out = null;
        try {
            // 保留到硬盘中
            out = new FileOutputStream(paths + proxyName + ".class");
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

before...
Student run...
after...
目标对象代理类path:/study/java/springBoot/springBoot-demo01/target/classes/com/fly/proxy/dynamic/jdk/

生成后的代理类如下:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

import com.fly.proxy.dynamic.jdk.People;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class PeopleProxy extends Proxy implements People {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public PeopleProxy(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void run() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.fly.proxy.dynamic.jdk.People").getMethod("run");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

通过生成的代理类我们可以得知:

  • 生成的代理类继承了Proxy类,并实现了被代理对象实现的所有接口,以及equals、hashCode、toString等方法。
  • 由于代理类继承了Proxy类,每个代理类都会关联一个InvocationHandler方法调用处理器。
  • 类和所有方法都被 public final 修饰,所以代理类只可被使用,不可以再被继承。
  • 每个方法都有一个 Method 对象来描述,Method 对象在static静态代码块中创建以 m + 数字 的格式命名。
  • 调用方法的时候通过 super.h.invoke(this, m1, (Object[])null)调用,其中的 super.h.invoke 实际上是在创建代理的时候传递给 Proxy.newProxyInstance 的 LogHandler 对象,它继承 InvocationHandler 类,负责实际的调用处理逻辑,而 LogHandler 的 invoke 方法接收到 method、args 等参数后,进行一些处理,然后通过反射让被代理的对象 target 执行方法。

4.2.2 Cglib动态代理

4.2.3 JDK动态代理与CGlib动态代理对比