【Java高级技术】

1.1单元测试

1.2概念:

就是针对最小的功能单元(方法),编写测试代码对其进行正确性测试。

1.3JUnit概述:

JUnit是Java中最流行的开源单元测试框架,用于编写和运行可重复的、自动化的单元测试。JUnit极大地简化了测试用例的编写和组织,提供了丰富的断言方法、测试运行控制、测试结果报告等功能,是遵循测试驱动开发(TDD)和持续集成(CI)实践的重要工具。

1.4具体步骤

①将Junit框架的jar包导入到项目中(注意:IDEA集成了Junit框架,不需要我们自己手工导入了)

②编写测试类、测试类方法(注意:测试方法必须是公共的,无参数,无返回值的非静态方法)

③必须在测试方法上使用@Test注解(标注该方法是一个测试方法)

④在测试方法中,编写程序调用被测试的方法即可。

⑤选中测试方法,右键选择“JUnit运行” ,如果测试通过则是绿色;如果测试失败,则是红色

1.5基本概念

•测试类:每个测试类通常对应待测试的类或一组相关功能。测试类通常以Test结尾,且需继承junit.framework.TestCase或使用@RunWith(JUnit4.class)注解。

•测试方法:每个测试方法通常对应待测试类的一个具体功能点。测试方法名以test开头,无返回值,且不接受任何参数。测试方法内部使用JUnit提供的断言方法验证实际结果与预期结果是否相符。

•断言(Assertions):JUnit提供了丰富的断言方法,如assertEquals()、assertTrue()、assertNull()等,用于在测试方法中验证程序行为和结果。

•测试运行:JUnit通过junit.textui.TestRunner、IDE集成或构建工具(如Maven、Gradle)提供的插件运行测试。测试结果通常以通过/失败的形式显示,并提供详细的失败信息。

1.6使用方法

1. 添加JUnit依赖:

        在Maven或Gradle项目中添加JUnit依赖。

Maven:

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>
Gradle:

dependencies {
    testImplementation 'junit:junit:4.13.2'
}
2. 创建测试类:

创建一个继承junit.framework.TestCase或使用@RunWith(JUnit4.class)注解的公共类,类名通常以Test结尾。

import org.junit.Test;
import static org.junit.Assert.assertEquals;
 
public class MyCalculatorTest {
    @Test
    public void testAdd() {
        MyCalculator calculator = new MyCalculator();
        int result = calculator.add(2, 3);
        assertEquals(5, result);
    }
}
3. 运行测试:

在IDE(如IntelliJ IDEA、Eclipse)中右键点击测试类或方法,选择“Run 'XXXTest'”。

也可通过命令行使用mvn test或gradle test运行所有测试。

高级特性

•注解:JUnit 4引入了大量的注解,如@Test、@Before、@After、@BeforeClass、@AfterClass、@Ignore、@Rule、@Parameterized等,用于标记测试方法、设置前置/后置操作、忽略测试、配置规则、参数化测试等。

•断言方法:除了基本的断言方法,JUnit还提供了更复杂的断言,如assertArrayEquals()、assertThat()(配合Hamcrest匹配器)、assertThrows()等,用于验证数组、集合、异常、对象属性等复杂情况。

•测试套件(Test Suite):使用junit.framework.TestSuite或@Suite注解将多个测试类组织成一个测试套件,一次性运行所有相关的测试。

•规则(Rules):JUnit规则(如TemporaryFolder、ExpectedException、ExternalResource等)提供了在测试前后进行资源管理、异常检查、外部资源清理等操作的通用机制。

•参数化测试(Parameterized Tests):使用@RunWith(Parameterized.class)和@Parameters注解,可以创建一组参数化测试用例,每个测试用例使用不同的输入数据运行相同的测试逻辑。

•异常测试:使用@Test(expected = SomeException.class)注解或assertThrows()方法,可以测试特定方法在特定条件下是否抛出预期的异常。

•第三方扩展:JUnit与许多第三方库(如Mockito、PowerMock、AssertJ、TestNG迁移工具等)兼容,可以进一步增强测试功能和易用性。

2.1反射

2.2认识反射、获取类

什么是反射?能够分析类信息的能力叫反射,通过反射能得到一个类对象(存储类的信息)

类信息简单来说就是,类的属性、方法、构造器

类加载器:将类拉到内存里面

2.3获取类成员的成员变量

、获取类对象的方法
三种方法获取类对象:

Class.forNname()——返回一个类对象,类拿不到,对象也拿不到

Class.forName(“类全名”)类全名:包名+类名,多用于配置文件,将类名定义在配置文件中,读取文件,加载类

public class Parent {
    public int age = 30;
    public void sleep(){

    }
}
public class MainTest {
    public static void main(String[] args) throws ClassNotFoundException{
        //通过反射获取Class对象
        Class cla = Class.forName("com.test01.Parent");

        System.out.println(cla);
        Field[] fields = cla.getFields();  //拿到类的属性
        for(Field field:fields){
            System.out.println(field);  //打印类的属性
        }
    }

}
类.class——可以拿到类,通过这种方式获取类的信息

类.class多用于参数的传递

//通过类名.class
Class cla1 = Parent.class;

对象.getClass()——可以拿到对象

对象.getClass()多用于使用对象获取

//通过对象来获取,对象.getClass()
Parent parent = new Parent();
Class cla2 = parent.getClass();

同一个类在一次程序运行过程中只会被加载一次,通过三种方式获取的类对象是同一个

System.out.println(cla == cla1); //true
System.out.println(cla2 == cla1); //true

2.4获取类的构造器

步骤:

通过使用getConstructor()获得构造方法对象(无参),可以根据构造方法参数类型的不同,传入对应类型的类对象,即可获取指定构造方法
使用newInstance()创建对象,如果有参构造方法对象,可以传入实际参数值
说明:

如果使用无参构造方法,此操作可以简化:类对象.newInstance()来创建对象,例如cla.newInstance();

获取所有构造方法对象getConstructors()

getDeclaredConstructors() //获取所有构造方法,公共非公共
getDeclaredConstructor() //获取构造方法,公共非公共
注意:非公共构造方法有效使用需要使用setAccessible(true),忽略安全访问修饰符的安全检查(暴力反射)

Class cla = Class.forName("com.test01.Parent");

Constructor[] constructorArr = cla.getConstructors();

for(Constructor i:constructorArr){
System.out.println(i);
}


获取无参构造方法对象
//通过反射获取Class对象
Class cla = Class.forName("com.test01.Parent");

//获取构造方法对象
Constructor constructor = cla.getConstructor();
//获取对象
Object object = constructor.newInstance();

System.out.println(object);

 获取有参构造方法对象
//获取构造方法对象
Constructor constructor1 = cla.getConstructor(String.class,int.class);
//获取对象
Object object1 = constructor1.newInstance("张三",30);

System.out.println(object1);

输出结果为:

获取类的成员变量
获取指定公共成员变量
getField()获取指定成员变量对象,可以根据指定成员变量名称

设置值set(Object obj,Object value)

获取值get(Object obj)

//通过反射获取Class对象
Class cla = Class.forName("com.test01.Parent");

//获取指定成员变量name
Object obj2 = cla.newInstance();
Field field = cla.getField("name");
field.set(obj2,"李明");
System.out.println(field.get(obj2));


 获取所有公共成员变量
获取所有成员变量对象getFields()

Field[] fields = cla.getFields();
for(Field field1:fields){
    System.out.println(field1);

}

//输出只有public java.lang.String com.test01.Parent.name

获取所有成员变量
getDeclaredFields()

Field[] fields = cla.getDeclaredFields();
for(Field field1:fields){
    System.out.println(field1);

}

//输出:
//public java.lang.String com.test01.Parent.name
//private int com.test01.Parent.age
获取指定成员变量(公共和非公共)
getDeclaredField()

非公共成员变量有效使用需要使用setAccessible(true),忽略安全访问修饰符的安全检查(暴力反射)

//获取指定成员变量age
Object obj2 = cla.newInstance();
Field field = cla.getDeclaredField("age");
field.setAccessible(true);
field.set(obj2,10);
System.out.println(field.get(obj2));


 获取类的方法
获取指定公共方法对象
获取指定公共方法对象getMethod() 可以根据方法名以及参数类型对应的类对象可以指定获取

调用方法 invoke(Object object,Object args…)

获取所有公共方法对象
获取所有公共方法对象getMethods()

获取所有方法对象
getDeclaredMethods()

获取指定成员变量(公共和非公共)
getDeclaredMethod()

非公共方法有效使用需要使用setAccessible(true),忽略安全访问修饰符的安全检查(暴力反射)

//通过反射获取Class对象
Class cla = Class.forName("com.test01.Parent");
Object obj = cla.newInstance();
//获取所有方法对象
Method[] methods = cla.getDeclaredMethods();
for(Method method:methods){
System.out.println(method);
}

//获取指定方法对象
Method set1 = cla.getDeclaredMethod("setName", String.class);
//调用
set1.invoke(obj,"LiMing");

Method get1 = cla.getDeclaredMethod("getName");
System.out.println(get1.invoke(obj));


示例
public class CommonReflect {
    //获取类的名称


    public static void getClassName(Object object){
        String className = object.getClass().getSimpleName();
        System.out.println("类的名称是"+className);
    }

    //获取类的成员变量名称


    public static void getField(Object object){
        Field[] fields = object.getClass().getDeclaredFields();
        for(Field i:fields){
            System.out.println("该类成员变量名"+i.getName());
        }

    }

    //获取类的方法


    public static void getMethod(Object object){
        Method[] methods = object.getClass().getDeclaredMethods();
        for(Method i:methods){
            System.out.println("该类方法名"+i.getName());
        }

    }

    //调用该类的get方法
    public static void getAction(Object object) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Field[] fields = object.getClass().getDeclaredFields();
        for(int i = 0;i < fields.length;i ++){
            String fieldName = fields[i].getName();
            String suff = fieldName.substring(0,1).toUpperCase();
            String methodName = "get" + suff + fieldName.substring(1);
            Method method = object.getClass().getDeclaredMethod(methodName);
            System.out.println(methodName+"方法调用结果"+method.invoke(object));

        }
    }

}
public class MainTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {

        Parent parent = Parent.class.getConstructor(String.class,int.class).newInstance("张三",20);

        CommonReflect.getClassName(parent);
        CommonReflect.getField(parent);
        CommonReflect.getMethod(parent);
        CommonReflect.getAction(parent);

    }

3.1注解

一、注解概述


注解本质上都是一种数据类型,是一种接口类型。到 Java 8 为止 Java SE 提供了 11 个内置注解( 5 个是基本注解,来自于 java.lang 包。 6 个是元注解,它们来自于 java.lang.annotation 包)

注:自定义注解会用到元注解(负责注解其他的注解)。

1、相关说明
1)注解都是 @ 符号开头的,如重写 @Override 注解。
2)同 Class 和 Interface 一样,注解也属于一种类型。
3)默认情况下,注解可以在程序的任何地方使用,通常用于修饰类、接口、方法和变量等。
4)注解并不能改变程序的运行结果,也不会影响程序运行的性能(有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息)
5)注解可以元数据这个词描述(即一种描述数据的数据),因此可以说注解就是源代码的元数据。

2、注解作用
1.生成帮助文档。这是最常见的,也是 Java 最早提供的注解。常用的有 @see、@param 和 @return 等;
2.跟踪代码依赖性,实现替代配置文件功能。比较常见的是 Spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量;
3.在编译时进行格式检查。如把 @Override 注解放在方法前,如果这个方法并不是重写了父类方法,则编译时就能检查出。


二、注解类型


1、基本注解(5个)
1.1 @Override
用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。

1.2 @Deprecated
用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当其他程序使用已过时的元素时,编译器将会给出警告。

1.3 @SuppressWarnings
指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告,且会一直作用于该程序元素的所有子元素。抑制警告的关键字有很多,有兴趣的可自行去搜索查看。
1)抑制单类型的警告

@SuppressWarnings(“unchecked”)

2)抑制多类型的警告

@SuppressWarnings(“unchecked”,“rawtypes”)

3)抑制所有类型的警告

@SuppressWarnings(“unchecked”)

1.4 @SafeVarargs
可用来抑制编译器警告。

注:@SafeVarargs注解不适用于非 static 或非 final 声明的方法,对于未声明为 static 或 final 的方法,如果要抑制 unchecked 警告,可以使用 @SuppressWarnings 注解。

1.5 @FunctionalInterface
用来指定某个接口必须是函数式接口,且@FunInterface 只能修饰接口,不能修饰其它程序元素。

注:函数式接口是为 Java 8 的 Lambda 表达式准备的,Java 8 允许使用 Lambda 表达式创建函数式接口的实例,因此 Java 8 专门增加了 @FunctionalInterface。

1.6 基本注解示例
1)@Override注解

public class AnnotationDemo {
    private String name="";
    private int age;
    //...
    //@Override注解: 告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错。
    @Override
    public String t0String(){ // toString写错,@Override下面会有红色波浪线提示(Method does not override method from its superclass)
        return "Person [name="+name+",age="+age+"]";
    }
}

2)@Deprecated注解
先创建一个Person类

@Deprecated
public class Person {
    @Deprecated
    protected String name;
    private  int age;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Deprecated
    public void setNameAndAge(String name,int age){
        this.name=name;
        this.age=age;
    }
    @Override
    public String toString(){
        return "Person[name"+name+",age="+age+"]";
    }
}


再创建测试类


注:由于类 Person、成员变量 name 和setNameAndAge 方法被 @Deprecated 注解,这些被注解的 API 都会被画上删除线。调用这些 API 代码也会有删除线。

Java 9 @Deprecated 注解新增属性:

forRemoval:指定该 API 在将来是否会被删除。
since:指定该 API 从哪个版本被标记为过时。

如下

class Test {
    // since属性指定从哪个版本开始被标记成过时,forRemoval指定该API将来会被删除
    @Deprecated(since = "9", forRemoval = true)
    public void print() {
        System.out.println("你好,李四!");
    }
}
public class DeprecatedTest {
    public static void main(String[] args) {
        // 下面使用info()方法时将会被编译器警告
        new Test().print();
    }
}

3)@SuppressWarnings 注解
调用2)中的Person类创建如下测试类,由于使用了@Deprecated注解表示API已经过时了,所以会产生删除线警告,但这里因为使用了 @SuppressWarnings 注解,就没有警告了(取消了警告)

public class PersonTest {
    @SuppressWarnings({ "deprecation" })
    public static void main(String[] args) {
        Person p = new Person();
        p.setNameAndAge("zhangsan",22);
        p.name="lishi";
    }
}
 


4)@SafeVarargs 注解

public class Test {
    public static void main(String[] args) {
        // 传递可变参数,参数是泛型集合
        display(10, 20, 30);
        // 传递可变参数,参数是非泛型集合
        display("10", 20, 30); // 会有编译警告
    }
    public static <T> void display(T... array) { // 可变参数形式:方法名(类型...数组名)
    //方法display()提示Annotate as '@SafeVararg'(即要在上一行添加@SafeVararg注解)
        for (T arg : array) {
            System.out.println(arg.getClass().getName() + ":" + arg);
        }
    }
}

5)@FunctionalInterface注解

// 使用@FunctionalInterface修饰函数式接口。
public interface FunInterface {
    static void print() { // 静态方法
        System.out.println("我的个人网站:https://zhengyquan.gitee.io/");
    }
    default void show() {
        System.out.println("我的腾讯云社区首页:https://cloud.tencent.com/developer/user/10491946");
    }
    void test(); // 只定义一个抽象方法
}
// @FunctionalInterface 注解的作用只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错。

注:@FunctionalInterface 注解主要是帮助程序员避免一些低级错误,例如,在上面的 FunInterface 接口中再增加一个抽象方法 abc(),编译程序时将出现如下错误提示:“@FunctionInterface”批注无效;FunInterface不是functional接口。

2、元注解(6个)
元注解负责对其它注解进行说明的注解,自定义注解时可以使用元注解

2.1 @Documented
@Documented 是一个标记注解,没有成员变量。用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。

默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。

2.2 @Target
用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方。@Target 注解有一个成员变量(value)用来设置适用目标,value 是 java.lang.annotation.ElementType 枚举类型的数组, ElementType 常用的枚举常量。

2.3 @Retention
@Retention 用于描述注解的生命周期,也就是该注解被保留的时间长短。@Retention 注解中的成员变量(value)用来设置保留策略,value 是 java.lang.annotation.RetentionPolicy 枚举类型,RetentionPolicy 有 3 个枚举常量。如下

SOURCE:在源文件中有效(即源文件保留)

CLASS:在 class 文件中有效(即 class 保留)

RUNTIME:在运行时有效(即运行时保留)

2.4 @Inherited
@Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。

注:如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。

2.5 @Repeatable
@Repeatable 注解是 Java 8 新增加的,允许在相同的程序元素中重复注解,需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。

了解:Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。

2.6 @Native
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。@Native 注解不常使用,了解即可。

3、自定义注解
3.1 声明
@interface 关键字实现,与定义接口非常类似,如

public @interface Test {}
1
3.2 几点注意
定义注解和定义类相似,注解前面的访问修饰符和类一样有两种,分别是公有访问权限(public)和默认访问权限(默认不写)。

一个源程序文件中可以声明多个注解,但只能有一个是公有访问权限的注解(同定义类,只能有一个public修饰的类,其他类可以有多个)。且源程序文件命名和公有访问权限的注解名一致(同定义类,源文件名和类名相同)

3.3 根据注解是否包含成员变量进行分类
1.标记注解:没有定义成员变量的注解类型(仅利用自身的存在与否来提供信息)
2.元数据注解:包含成员变量的注解,由于可以接受更多的元数据,也被称为元数据注解。

4.1动态代理


JDK的动态代理


JDK动态代理是一种在运行时生成代理类的机制,它允许在代理类中动态地处理方法调用。这种代理机制通过InvocationHandler接口来实现,代理类在运行时通过实现指定接口生成,并且可以在代理类的方法中插入自定义的逻辑。在这个过程中,代理类并不是在编译时就确定的,而是在运行时动态生成的。

InvocationHandler(调用处理器): InvocationHandler 是一个接口,用于定义代理类的具体行为。它包含一个方法 invoke,在代理对象的方法被调用时会被触发。通过实现这个接口,可以在方法调用前后执行自定义的逻辑。
Proxy(代理类): Proxy 类是 JDK 提供的用于创建动态代理类和实例的工具类。通过 newProxyInstance 方法,可以在运行时生成代理对象。这个方法接收一个类加载器、一组接口和一个 InvocationHandler 实现作为参数。
动态代理类: 由 Proxy 类在运行时动态生成的代理类。这个类实现了指定的接口,并将方法调用委托给 InvocationHandler 的 invoke 方法。
JDK动态代理使用步骤:

定义接口: 首先定义一个业务接口,该接口将由代理类和实际业务类共同实现。
实现业务类: 创建一个实际的业务类,实现定义的业务接口。
实现 InvocationHandler: 创建一个实现 InvocationHandler 接口的类,该类将负责在代理对象的方法调用时执行特定逻辑。
创建代理对象: 使用 Proxy.newProxyInstance 方法创建代理对象,该方法接收类加载器、接口数组和 InvocationHandler 实例作为参数。
调用代理对象方法: 通过代理对象调用方法,代理对象将在执行方法前后执行 InvocationHandler 的逻辑。


实现


# 案例
    每次保存用户前 对用户信息进行校验,保存完成后返回用户信息

3.1.2.创建接口
/**
 * 用户服务接口
 * @author 13723
 * @version 1.0
 * 2024/2/1 22:19
 */
public interface UserServiceBi {
    /**
     * 添加用户
     * @param name 用户名
     */
    void addUser(String name);
}
3.1.3.创建实现类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.lang.invoke.MethodHandles;

/**
 * 用户服务实现类
 * @author 13723
 * @version 1.0
 * 2024/2/1 22:30
 */
@Service
public class UserServiceBiImpl implements UserServiceBi {
    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    /**
     * 添加用户
     * @param name 用户名
     */
    @Override
    public void addUser(String name) {
        logger.error("添加用户成功!用户名:{}",name);
    }
}


创建代理类


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 用户服务代理类
 * @author 13723
 * @version 1.0
 * 2024/2/1 22:31
 */
public class UserServiceProxy implements InvocationHandler {
    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    /**
     * 被代理对象
     */
    private UserServiceBiImpl userService;

    /**
     * 将代理对象注入到代理类中
     * 多个线程可能会同时尝试创建代理对象,而代理对象的创建过程是不可变的,因此它本身是线程安全的。
     * 但是,如果 userService 对象的状态会在代理对象创建之后被修改,那么就有可能出现线程安全问题
     * 这里我们将 UserServiceProxy 的构造函数私有化,不允许外部直接创建实例。然后,通过 newInstance 工厂方法来创建代理对象。
     * 在 invoke 方法中,我们使用了同步块来确保在多线程环境下的线程安全性。
     * 这种方式保证了在实例化代理对象时的线程安全,避免了多个线程同时创建代理对象的问题。
     * @param userService 被代理对象
     */
    private UserServiceProxy(UserServiceBiImpl userService) {
        this.userService = userService;
    }


    public static UserServiceBi newInstance(UserServiceBiImpl userService){
        // 生成代理对象,这里的代理对象是在内存中生成的,不是在磁盘上生成的,所以不能直接通过反射获取,需要通过Proxy.newProxyInstance()方法获取
        // 第一个参数:被代理对象的类加载器,这里使用被代理对象的类加载器,因为代理对象和被代理对象在同一个类加载器中,这样可以保证代理对象和被代理对象在同一个类加载器中
        // 第二个参数:被代理对象的接口,这里使用被代理对象的接口,因为代理对象和被代理对象实现了同一个接口,这样可以保证代理对象和被代理对象实现了同一个接口
        // 第三个参数:代理对象,这里使用代理对象,因为代理对象实现了InvocationHandler接口,这样可以保证代理对象实现了InvocationHandler接口
        return (UserServiceBi) Proxy.newProxyInstance(
                                                     userService.getClass().getClassLoader(),
                                                     userService.getClass().getInterfaces(),
                                                     new UserServiceProxy(userService)
                                                   );
    }


    /**
     * 在代理实例上处理方法调用并返回结果
     * @param proxy the proxy instance that the method was invoked on
     *
     * @param method the {@code Method} instance corresponding to
     * the interface method invoked on the proxy instance.  The declaring
     * class of the {@code Method} object will be the interface that
     * the method was declared in, which may be a superinterface of the
     * proxy interface that the proxy class inherits the method through.
     *
     * @param args an array of objects containing the values of the
     * arguments passed in the method invocation on the proxy instance,
     * or {@code null} if interface method takes no arguments.
     * Arguments of primitive types are wrapped in instances of the
     * appropriate primitive wrapper class, such as
     * {@code java.lang.Integer} or {@code java.lang.Boolean}.
     *
     * @return the value to return from the method invocation on the
     * @throws Throwable the exception to throw from the method
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        logger.error("执行保存用户前-校验用户数据!");
        Object result = method.invoke(userService, args);
        logger.error("执行保存用户后-返回用户数据!");
        return result;
    }
}


创建测试类


import com.hrfan.java_se_base.base.proxy.jdk_proxy.UserServiceBi;
import com.hrfan.java_se_base.base.proxy.jdk_proxy.UserServiceBiImpl;
import com.hrfan.java_se_base.base.proxy.jdk_proxy.UserServiceProxy;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.lang.invoke.MethodHandles;

/**
 * @author 13723
 * @version 1.0
 * 2024/2/1 22:42
 */
@SpringBootTest
public class JDKProxyTest1 {
    private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
    @Autowired
    private UserServiceBiImpl userService;


    @Test
    public void test1(){
        UserServiceBi userProxy = UserServiceProxy.newInstance(userService);
        userProxy.addUser("喜羊羊!");
    }
}


优缺点
JDK动态代理的优点:

无需手动编写代理类: 相较于静态代理,JDK动态代理无需手动编写代理类,减少了代码冗余。
支持多个接口代理: 代理类可以实现多个接口,灵活性较高。
运行时动态生成: 代理类的生成是在运行时进行的,使得系统更加灵活和可扩展。
JDK动态代理的缺点:

只能代理接口: JDK动态代理只能代理接口,无法代理类。
运行效率相对较低: 由于代理类的生成和方法调用过程都需要运行时处理,相对于静态代理或者CGLib动态代理,JDK动态代理的效率较低。
应用场景:

AOP(面向切面编程): JDK动态代理常用于实现AOP,通过在方法调用前后添加日志记录、事务管理、性能监控等功能,实现了横切关注点的统一管理。
Spring框架中的事务管理: Spring框架通过动态代理实现了声明式事务管理,对被@Transactional注解修饰的方法进行代理,从而实现了事务管理的功能。
远程方法调用(RPC): JDK动态代理可以在远程方法调用时,通过网络传输代理对象,实现客户端和服务端之间的通信。
安全检查: 在方法调用前后进行安全检查,比如权限验证、参数校验等。
性能监控: 在方法调用前后记录方法执行时间,进行性能监控和分析。
缓存代理: 在方法调用前先检查缓存中是否存在结果,如果存在则直接返回缓存结果,否则执行方法并缓存结果。
3.2.CGLib代理
CGlib(Code Generation Library)是一个强大的,高性能的代码生成类库,它通过动态生成字节码来创建代理对象。相对于 JDK 动态代理而言,CGlib 提供了更多的功能和灵活性。

CGLib使用步骤可以包括

创建Enhancer对象:首先,我们创建一个Enhancer对象,它是CGLIB库的核心类之一,用于设置代理对象的属性和行为。
设置父类:使用setSuperclass()方法设置要代理的对象的类。CGLIB通过继承的方式创建代理对象,因此需要指定一个父类。
设置拦截器:通过setCallback()方法设置代理对象的拦截器。拦截器是一个实现了MethodInterceptor接口的对象,它定义了在方法调用前后进行的额外操作,比如日志记录、性能监控等。
创建代理对象:调用Enhancer对象的create()方法来创建代理对象。在这一步,CGLIB会动态生成一个代理类,并覆盖父类中的方法来添加拦截器的逻辑。
返回代理对象:返回创建的代理对象。这个代理对象实际上是在运行时动态生成的一个子类,它继承自被代理类,并且在方法调用时会先执行拦截器的逻辑,然后再调用被代理类的方法。
创建Enhancer对象
设置要代理的对象的类
设置代理对象的拦截器
动态生成代理类并添加拦截器逻辑
返回创建的代理对象
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值