java使用bytebuddy動態生成帶泛型的DTO

ValSong發表於2018-12-14

我這人文筆很low,喜歡直接貼程式碼,大家將就著看

//TODO 文字描述,回頭有空再補上

package com.valsong.bytebuddy;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.valsong.dto.User;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.annotation.AnnotationDescription;
import net.bytebuddy.description.annotation.AnnotationSource;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.Argument;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * byte buddy 建立簡單的class
 */
public class ByteBuddyTest {
    /**
     * 構建
     *
     * package net.bytebuddy.renamed.java.lang.Object$com.valsong.dynamicdto;
     *
     * public class User$zulHzyXk {
     *     private String name;
     *     private Integer age;
     *
     *     public void setName(String var1) {
     *         this.name = var1;
     *     }
     *
     *     public String getName() {
     *         return this.name;
     *     }
     *
     *     public void setAge(Integer var1) {
     *         this.age = var1;
     *     }
     *
     *     public Integer getAge() {
     *         return this.age;
     *     }
     *
     *     public User$zulHzyXk() {
     *     }
     * }
     * 
     */
    @Test
    public void makeSimpleDto() {
        DynamicType.Builder dynamicBuilder = new ByteBuddy()
                //指定字首
                .with(new NamingStrategy.SuffixingRandom("com.valsong.dynamicdto.User"))
                .subclass(Object.class)
                //.name("com.valsong.dynamicdto.User")
                .defineProperty("name", String.class)
                .defineProperty("age", Integer.class);


        DynamicType.Unloaded<?> dynamicType = dynamicBuilder.make();

        saveAndLoad(dynamicType);

    }

    /**
     * 構建
     * <p>
     * package com.valsong.dynamicdto;
     * <p>
     * import com.valsong.bytebuddy.MyAnnotation;
     *
     * @MyAnnotation( name = "House",
     * value = "House",
     * required = false
     * )
     * public class House {
     * @MyAnnotation( name = "address",
     * value = "china shanghai",
     * required = true
     * )
     * private String address;
     * @MyAnnotation( name = "area",
     * value = "100",
     * required = true
     * )
     * private Double area;
     * <p>
     * public void setAddress(String var1) {
     * this.address = var1;
     * }
     * <p>
     * public String getAddress() {
     * return this.address;
     * }
     * <p>
     * public void setArea(Double var1) {
     * this.area = var1;
     * }
     * <p>
     * public Double getArea() {
     * return this.area;
     * }
     * <p>
     * public House() {
     * }
     * }
     */
    @Test
    public void makeSimpleDtoWithAnnotation() {

        MyAnnotation myAnnotation = new MyAnnotation() {

            @Override
            public Class<? extends Annotation> annotationType() {
                return MyAnnotation.class;
            }

            @Override
            public String value() {
                return "House";
            }

            @Override
            public String name() {
                return "House";
            }

            @Override
            public boolean required() {
                return false;
            }
        };


        AnnotationDescription apiModelPropertyDescription1 =
                AnnotationDescription.Builder.ofType(MyAnnotation.class)
                        .define("value", "china shanghai")
                        .define("name", "address")
                        .define("required", true)
                        .build();


        //將MyAnnotation註解轉化成apiModelPropertyDescription
        AnnotationDescription apiModelPropertyDescription2 =
                AnnotationDescription.Builder.ofType(MyAnnotation.class)
                        .define("value", "100")
                        .define("name", "area")
                        .define("required", true)
                        .build();


        DynamicType.Builder dynamicBuilder = new ByteBuddy()
                .subclass(Object.class)
                .name("com.valsong.dynamicdto.House")
                .annotateType(myAnnotation)
                .defineProperty("address", String.class)
                .annotateField(apiModelPropertyDescription1)
                .defineProperty("area", Double.class)
                .annotateField(apiModelPropertyDescription2);


        DynamicType.Unloaded<?> dynamicType = dynamicBuilder.make();

        saveAndLoad(dynamicType);

    }

    /**
     * 構建
     * <p>
     * package com.valsong.dynamicdto;
     * <p>
     * import java.util.Map;
     * <p>
     * public class MapContainer {
     * private Map<String, Object> map;
     * <p>
     * public void setMap(Map<String, Object> var1) {
     * this.map = var1;
     * }
     * <p>
     * public Map<String, Object> getMap() {
     * return this.map;
     * }
     * <p>
     * public MapContainer() {
     * }
     * }
     *
     * @throws ClassNotFoundException
     */
    @Test
    public void makeGenericMapDto() throws ClassNotFoundException {


        Class<?> rawType = Map.class;


        List<TypeDefinition> typeArgumentsDefinitions = new ArrayList<>(2);

        typeArgumentsDefinitions.add(new TypeDescription.ForLoadedType(String.class));
        typeArgumentsDefinitions.add(new TypeDescription.ForLoadedType(Object.class));

        //ownerType泛型
        TypeDescription.Generic ownerTypeGeneric = Optional.ofNullable(rawType.getDeclaringClass())
                .map(TypeDefinition.Sort::describe)
                .orElse(null);

        //Map<String,Object>
        TypeDefinition mapField = TypeDescription.Generic.Builder.parameterizedType(
                //TypeDescription.ForLoadedType.of(rawType)
                //為了解決版本衝突將bytebuddy版本改成了1.7.11
                new TypeDescription.ForLoadedType(rawType)
                , ownerTypeGeneric, typeArgumentsDefinitions).build();


        DynamicType.Builder dynamicBuilder = new ByteBuddy()
                .subclass(Object.class)
                .name("com.valsong.dynamicdto.MapContainer")
                .defineProperty("map", mapField);


        DynamicType.Unloaded<?> dynamicType = dynamicBuilder.make();

        saveAndLoad(dynamicType);


    }


    /**
     * 構建
     * <p>
     * package com.valsong.dynamicdto;
     * <p>
     * import java.util.List;
     * <p>
     * public class ListArrayContainer {
     * private List<String>[] listArray;
     * <p>
     * public void setListArray(List<String>[] var1) {
     * this.listArray = var1;
     * }
     * <p>
     * public List<String>[] getListArray() {
     * return this.listArray;
     * }
     * <p>
     * public ListArrayContainer() {
     * }
     * }
     *
     * @throws ClassNotFoundException
     */
    @Test
    public void makeGenericArrayType() throws ClassNotFoundException {


        Class<?> rawType = List.class;


        List<TypeDefinition> typeArgumentsDefinitions = new ArrayList<>(2);

        typeArgumentsDefinitions.add(new TypeDescription.ForLoadedType(String.class));

        //ownerType泛型
        TypeDescription.Generic ownerTypeGeneric = Optional.ofNullable(rawType.getDeclaringClass())
                .map(TypeDefinition.Sort::describe)
                .orElse(null);

        //List<String> []
        TypeDefinition listArrayField = TypeDescription.Generic.Builder.parameterizedType(
                //TypeDescription.ForLoadedType.of(rawType)
                //為了解決版本衝突將bytebuddy版本改成了1.7.11
                new TypeDescription.ForLoadedType(rawType)
                , ownerTypeGeneric, typeArgumentsDefinitions)
                .asArray()
                .build();


        DynamicType.Builder dynamicBuilder = new ByteBuddy()
                .subclass(Object.class)
                .name("com.valsong.dynamicdto.ListArrayContainer")
                .defineProperty("listArray", listArrayField);


        DynamicType.Unloaded<?> dynamicType = dynamicBuilder.make();

        saveAndLoad(dynamicType);


    }


    /**
     * 構建
     * <p>
     * package com.valsong.dynamicdto;
     * <p>
     * import java.util.LinkedHashMap;
     * import java.util.Map;
     * <p>
     * public class WildcardTypeContainer {
     * private Map<? extends Number, ? super LinkedHashMap>[] map;
     * <p>
     * public void setMap(Map<? extends Number, ? super LinkedHashMap>[] var1) {
     * this.map = var1;
     * }
     * <p>
     * public Map<? extends Number, ? super LinkedHashMap>[] getMap() {
     * return this.map;
     * }
     * <p>
     * public WildContainer() {
     * }
     * }
     *
     * @throws ClassNotFoundException
     */
    @Test
    public void makeWildcardType() throws ClassNotFoundException {


        Class<?> rawType = Map.class;


        List<TypeDefinition> type1ArgumentsDefinitions = new ArrayList<>(2);


        // ? extends Number
        type1ArgumentsDefinitions.add(TypeDescription.Generic.OfWildcardType.Latent
                .boundedAbove(new TypeDescription.ForLoadedType(Number.class).asGenericType(),
                        AnnotationSource.Empty.INSTANCE));


        // ? super LinkedHashMap
        type1ArgumentsDefinitions.add(TypeDescription.Generic.OfWildcardType.Latent
                .boundedBelow(new TypeDescription.ForLoadedType(LinkedHashMap.class).asGenericType(),
                        AnnotationSource.Empty.INSTANCE));

        //ownerType泛型
        TypeDescription.Generic ownerTypeGeneric = Optional.ofNullable(rawType.getDeclaringClass())
                .map(TypeDefinition.Sort::describe)
                .orElse(null);

        //Map<? extends Number, ? super LinkedHashMap>
        TypeDefinition mapField = TypeDescription.Generic.Builder.parameterizedType(
                //TypeDescription.ForLoadedType.of(rawType)
                //為了解決版本衝突將bytebuddy版本改成了1.7.11
                new TypeDescription.ForLoadedType(rawType)
                , ownerTypeGeneric, type1ArgumentsDefinitions)
                .asArray()
                .build();


        DynamicType.Builder dynamicBuilder = new ByteBuddy()
                .subclass(Object.class)
                .name("com.valsong.dynamicdto.WildcardTypeContainer")
                .defineProperty("map", mapField);


        DynamicType.Unloaded<?> dynamicType = dynamicBuilder.make();

        saveAndLoad(dynamicType);


    }

    /**
     * 構建
     * <p>
     * import java.util.List;
     * <p>
     * public class Container<T, K extends Number> {
     * private List<T>[] ts;
     * private K k;
     * <p>
     * public void setTs(List<T>[] var1) {
     * this.ts = var1;
     * }
     * <p>
     * public List<T>[] getTs() {
     * return this.ts;
     * }
     * <p>
     * public void setK(K var1) {
     * this.k = var1;
     * }
     * <p>
     * public K getK() {
     * return this.k;
     * }
     * <p>
     * public Container() {
     * }
     * }
     */
    @Test
    public void makeTypeVariable() {

        Class<?> rawType = List.class;

        //T
        TypeDefinition tGeneric = TypeDescription.Generic.Builder.typeVariable("T").build();


        List<TypeDefinition> typeArgumentsDefinitions = new ArrayList<>(2);

        typeArgumentsDefinitions.add(tGeneric);

        //ownerType泛型
        TypeDescription.Generic ownerTypeGeneric = Optional.ofNullable(rawType.getDeclaringClass())
                .map(TypeDefinition.Sort::describe)
                .orElse(null);

        //List<T>
        TypeDefinition tsField = TypeDescription.Generic.Builder.parameterizedType(
                //TypeDescription.ForLoadedType.of(rawType)
                //為了解決版本衝突將bytebuddy版本改成了1.7.11
                new TypeDescription.ForLoadedType(rawType)
                , ownerTypeGeneric, typeArgumentsDefinitions)
                .asArray()
                .build();

        //K
        TypeDefinition kGeneric = TypeDescription.Generic.Builder.typeVariable("K").build();

        DynamicType.Builder dynamicBuilder = new ByteBuddy()
                .subclass(Object.class)
                .name("com.valsong.dynamicdto.Container")

                .typeVariable("T")
                .typeVariable("K", Number.class)

                .defineProperty("ts", tsField)
                .defineProperty("k", kGeneric);


        DynamicType.Unloaded<?> dynamicType = dynamicBuilder.make();

        saveAndLoad(dynamicType);

    }


    /**
     * 構建
     * <p>
     * package com.valsong.dynamicdto;
     * <p>
     * import java.util.LinkedHashMap;
     * import java.util.List;
     * import java.util.Map;
     * <p>
     * public class GenericCollectionContainer {
     * private List<? extends Map<String, Object>> list;
     * private Map<String, ? super LinkedHashMap<String, Integer>>[] mapArray;
     * <p>
     * public void setList(List<? extends Map<String, Object>> var1) {
     * this.list = var1;
     * }
     * <p>
     * public List<? extends Map<String, Object>> getList() {
     * return this.list;
     * }
     * <p>
     * public void setMapArray(Map<String, ? super LinkedHashMap<String, Integer>>[] var1) {
     * this.mapArray = var1;
     * }
     * <p>
     * public Map<String, ? super LinkedHashMap<String, Integer>>[] getMapArray() {
     * return this.mapArray;
     * }
     * <p>
     * public GenericCollectionContainer() {
     * }
     * }
     *
     * @throws ClassNotFoundException
     */
    @Test
    public void makeGenericDtoUseType() throws ClassNotFoundException {


        Type listType = new TypeReference<List<? extends Map<String, Object>>>() {
        }.getType();

        Type mapArrayType = new TypeReference<Map<String, ? super LinkedHashMap<String, Integer>>[]>() {
        }.getType();


        DynamicType.Builder dynamicBuilder = new ByteBuddy()
                .subclass(Object.class)
                .name("com.valsong.dynamicdto.GenericCollectionContainer")
                .defineProperty("list", listType)
                .defineProperty("mapArray", mapArrayType);


        DynamicType.Unloaded<?> dynamicType = dynamicBuilder.make();

        saveAndLoad(dynamicType);


    }

    /**
     * package com.valsong.dynamicdto;
     * <p>
     * import com.valsong.bytebuddy.ByteBuddyTest.DataConverterExecutor;
     * import java.lang.reflect.Type;
     * <p>
     * public class DataConverter {
     * public static String toJson(Object obj) {
     * return DataConverterExecutor.toJson(var0);
     * }
     * <p>
     * public static Object fromJson(String json, Type type) {
     * return DataConverterExecutor.fromJson(var0, var1);
     * }
     * <p>
     * public DataConverter() {
     * }
     * }
     */
    @Test
    public void makeMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        DynamicType.Builder dynamicBuilder = new ByteBuddy()
                .subclass(Object.class)
                .name("com.valsong.dynamicdto.DataConverter");

        dynamicBuilder = dynamicBuilder.defineMethod("toJson", String.class, Modifier.PUBLIC + Modifier.STATIC)
                .withParameter(Object.class, "obj")
                .intercept(MethodDelegation.to(DataConverterExecutor.class))

                .defineMethod("fromJson", Object.class, Modifier.PUBLIC + Modifier.STATIC)
                .withParameter(String.class, "json")
                .withParameter(Type.class, "type")
                .intercept(MethodDelegation.to(DataConverterExecutor.class));

        DynamicType.Unloaded<?> dynamicType = dynamicBuilder.make();

        Class<?> clazz = saveAndLoad(dynamicType);
        User user = User.newBuilder()
                .name("val")
                .age(27)
                .height(172.0)
                .email("valsong@163.com")
                .build();

        Method toJsonMethod = clazz.getMethod("toJson", Object.class);
        String json = (String) toJsonMethod.invoke(null, user);
        System.out.println(json);

        Assertions.assertNotNull(json);

        Method fromJsonMethod = clazz.getMethod("fromJson", String.class, Type.class);
        User user2 = (User) fromJsonMethod.invoke(null, json, User.class);
        Assertions.assertEquals(user, user2);

    }


    /**
     * 儲存到本地目錄,並且載入到類載入器中
     *
     * @param dynamicType
     */
    private Class<?> saveAndLoad(DynamicType.Unloaded<?> dynamicType) {
        //寫入到本地目錄
        try {
            dynamicType.saveIn(new File("target/classes"));
        } catch (IOException e) {
            e.printStackTrace();
        }

        return dynamicType.load(Thread.currentThread().getContextClassLoader(),
                ClassLoadingStrategy.Default.INJECTION).getLoaded();
    }


    public static class DataConverterExecutor {


        public static String toJson(Object obj) {
            return JSON.toJSONString(obj);
        }


        public static <T> T fromJson(@Argument(0) String json, @Argument(1) Type type) {
            return JSON.parseObject(json, type);
        }

    }

}

複製程式碼

上述測試用例用到的DTO和自定義註解

package com.valsong.dto;

import java.util.Objects;

public class User {

    private String name;

    private Integer age;

    private Double height;

    private String email;

    public User() {
    }

    private User(Builder builder) {
        setName(builder.name);
        setAge(builder.age);
        setHeight(builder.height);
        setEmail(builder.email);
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Double getHeight() {
        return height;
    }

    public void setHeight(Double height) {
        this.height = height;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }


    public static final class Builder {
        private String name;
        private Integer age;
        private Double height;
        private String email;

        private Builder() {
        }

        public Builder name(String val) {
            name = val;
            return this;
        }

        public Builder age(Integer val) {
            age = val;
            return this;
        }

        public Builder height(Double val) {
            height = val;
            return this;
        }

        public Builder email(String val) {
            email = val;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                ", email='" + email + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(name, user.name) &&
                Objects.equals(age, user.age) &&
                Objects.equals(height, user.height) &&
                Objects.equals(email, user.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age, height, email);
    }
}

複製程式碼
package com.valsong.bytebuddy;


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

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE,ElementType.TYPE_PARAMETER,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAnnotation {

    String value() default "";

    String name() default "";

    boolean required() default false;
}

複製程式碼

相關文章