天天看點

注解的這些進階技巧你會嗎?快來學吧提高你的程式擴充性

注解的進階使用

自定義注解是Java語言的一項特性,可以為程式元素(類、方法、字段等)添加中繼資料,用于配置、編譯檢查、運作時處理等方面。在本篇部落格中,我們将介紹自定義注解的進階應用,包括注解和泛型的結合使用、注解和反射的結合使用、注解和動态代理的結合使用。

注解和泛型的結合使用

自定義注解可以與泛型結合使用,以實作更加靈活、高效的程式設計。例如,我們可以在自定義注解中使用泛型類型參數,表示注解的屬性類型。例如:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    Class<?> value();
}
           

在上面的代碼中,我們定義了一個名為MyAnnotation的注解,使用Class<?>類型參數表示注解的屬性類型。這樣,我們就可以在使用該注解時,指定不同的屬性類型,實作對不同類型的字段進行注解。

注解和反射的結合使用

自定義注解可以與反射機制結合使用,以實作動态擷取和處理注解資訊。例如,我們可以使用Java反射機制擷取類、方法或字段上的注解資訊,并對注解進行處理。例如:

public class MyClass {
    @MyAnnotation(String.class)
    private String myField;
    
    @MyAnnotation(Integer.class)
    public void myMethod() {
        // do something
    }
}

MyClass obj = new MyClass();
Field field = obj.getClass().getDeclaredField("myField");
MyAnnotation myAnnotation = field.getAnnotation(MyAnnotation.class);
Class<?> fieldType = myAnnotation.value();
           

在上面的代碼中,我們定義了一個名為MyClass的類,并在其中聲明了一個名為myField的私有字段和一個名為myMethod的公共方法。在myField和myMethod上,我們使用了不同類型的MyAnnotation注解,并使用Java反射機制擷取了字段上的注解資訊,并擷取了注解的屬性類型。

注解和動态代理的結合使用

自定義注解可以與動态代理結合使用,以實作對程式的動态處理和修改。例如,我們可以使用Java動态代理機制,在運作時根據注解資訊動态生成代理類,實作對程式的動态修改。例如:

public interface MyInterface {
    void myMethod();
}

public class MyImpl implements MyInterface {
    @Override
    public void myMethod() {
        System.out.println("Hello, world!");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    private final Object target;
    
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
        if (myAnnotation != null && myAnnotation.value() == Integer.class) {
            System.out.println("Before method invocation...");
        }
        Object result = method.invoke(target, args);
        return result;
    }
}

MyImpl obj = new MyImpl();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
    MyInterface.class.getClassLoader(),
    new Class<?>[] { MyInterface.class },
    new MyInvocationHandler(obj)
);
proxy.myMethod();
           

在上面的代碼中,我們定義了一個名為MyInterface的接口和一個名為MyImpl的實作類。在MyImpl的myMethod方法上,我們使用了MyAnnotation注解,并在動态代理中使用了該注解資訊,實作對程式的動态修改。

利用Java注解和反射機制實作ORM架構

ORM(Object Relational Mapping)架構是一種将對象模型和關系資料庫模型映射起來的技術。通過ORM架構,可以将Java對象直接映射到關系資料庫中的表中,進而省去了手動編寫SQL語句的繁瑣過程。在本篇部落格中,我們将介紹如何利用Java注解和反射機制實作一個簡單的ORM架構。

ORM架構的基本原理和概念

ORM架構的基本原理是将Java對象和關系資料庫中的表進行映射。在ORM架構中,Java對象被稱為實體類,而關系資料庫中的表被稱為實體表。ORM架構通過将實體類的屬性映射到實體表的字段中,進而實作了Java對象和關系資料庫表之間的映射。

利用注解标記實體類和資料庫表

為了将Java對象和關系資料庫表進行映射,我們需要使用注解來标記實體類和資料庫表。在本例中,我們使用@Table注解來标記實體類對應的資料庫表,使用@Column注解來标記實體類中的屬性對應的表中的字段。例如:

@Table("user")
public class User {
    @Column("id")
    private Long id;
    @Column("name")
    private String name;
    @Column("age")
    private Integer age;
    // getters and setters
}
           

在上面的代碼中,我們使用@Table注解标記User類對應的資料庫表為user,使用@Column注解标記id、name和age屬性對應的表中的字段。

利用反射機制生成SQL語句

為了将Java對象的屬性映射到資料庫表中的字段,我們需要使用反射機制來擷取實體類中的屬性和值,并生成SQL語句。在本例中,我們使用PreparedStatement來執行SQL語句。例如:

public <T> void insert(T entity) throws SQLException {
    Class<?> clazz = entity.getClass();
    Table table = clazz.getAnnotation(Table.class);
    String tableName = table.value();
    StringBuilder sql = new StringBuilder("INSERT INTO " + tableName + " (");
    StringBuilder values = new StringBuilder("VALUES (");
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        Column column = field.getAnnotation(Column.class);
        if (column != null) {
            String columnName = column.value();
            sql.append(columnName).append(", ");
            values.append("?, ");
            field.setAccessible(true);
            Object value = field.get(entity);
            parameters.add(value);
        }
    }
    sql.delete(sql.length() - 2, sql.length());
    values.delete(values.length() - 2, values.length());
    sql.append(") ").append(values).append(");");
    PreparedStatement statement = connection.prepareStatement(sql.toString());
    for (int i = 0; i < parameters.size(); i++) {
        statement.setObject(i + 1, parameters.get(i));
    }
    statement.executeUpdate();
}
           

在上面的代碼中,我們首先使用反射機制擷取實體類中的屬性和注解資訊,并生成SQL語句。然後,我們使用PreparedStatement執行SQL語句,并将屬性值作為參數傳遞給PreparedStatement。

利用泛型實作通用的CURD操作

為了實作通用的CURD(Create、Retrieve、Update、Delete)操作,我們可以使用泛型來實作。例如:

public <T> T selectOne(Class<T> clazz, Long id) throws SQLException {
    Table table = clazz.getAnnotation(Table.class);
    String tableName = table.value();
    StringBuilder sql = new StringBuilder("SELECT * FROM " + tableName + " WHERE id = ?");
    PreparedStatement statement = connection.prepareStatement(sql.toString());
    statement.setLong(1, id);
    ResultSet resultSet = statement.executeQuery();
    if (resultSet.next()) {
        T entity = clazz.newInstance();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            Column column = field.getAnnotation(Column.class);
            if (column != null) {
                String columnName = column.value();
                field.setAccessible(true);
                Object value = resultSet.getObject(columnName);
                field.set(entity, value);
            }
        }
        return entity;
    } else {
        return null;
    }
}
           

在上面的代碼中,我們首先使用反射機制擷取實體類中的屬性和注解資訊,并生成SQL語句。然後,我們使用PreparedStatement執行SQL語句,并将結果集中的資料映射到實體類中。

Java注解的進階使用技巧

Java注解是一種中繼資料,可以為代碼添加額外的資訊,例如配置、限制、文檔等。在本篇部落格中,我們将介紹Java注解的進階使用技巧,包括注解和AOP的結合使用、注解和代碼生成器的結合使用、注解和測試架構的結合使用。

注解和AOP的結合使用

AOP(Aspect-Oriented Programming)是一種程式設計範式,用于解耦程式中的橫切關注點。通過使用AOP,可以将程式中的橫切關注點(例如日志、事務、安全等)與核心業務邏輯分離,進而提高程式的可維護性和可擴充性。Java注解可以與AOP結合使用,以實作更加靈活、高效的程式設計。例如,我們可以使用Java注解來标記需要進行AOP處理的方法,并在AOP架構中根據注解資訊動态生成切面類。例如:

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

@Aspect
public class LoggingAspect {
    @Around("@annotation(Loggable)")
    public Object logMethodExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = joinPoint.proceed();
        long end = System.currentTimeMillis();
        System.out.println("Method " + joinPoint.getSignature().getName() + " execution time: " + (end - start) + "ms");
        return result;
    }
}

@Service
public class MyService {
    @Loggable
    public void myMethod() {
        // do something
    }
}

ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
MyService service = context.getBean(MyService.class);
service.myMethod();
           

在上面的代碼中,我們定義了一個名為Loggable的注解,在MyService類中使用該注解标記了myMethod方法。然後,我們定義了一個名為LoggingAspect的切面類,在該類中使用@Around注解标記了需要進行AOP處理的方法,并根據注解資訊在方法執行前後輸出方法的執行時間。最後,我們在AppConfig類中使用@EnableAspectJAutoProxy注解啟用AOP功能,并使用ApplicationContext擷取MyService執行個體,并調用myMethod方法。

注解和代碼生成器的結合使用

代碼生成器是一種自動生成代碼的工具,可以根據配置或模闆生成Java類、接口、枚舉等代碼。Java注解可以與代碼生成器結合使用,以實作更加高效、精确的代碼生成。例如,我們可以使用Java注解來标記需要生成的代碼資訊(例如類名、字段、方法等),然後在代碼生成器中根據注解資訊生成代碼。例如:

@GenerateClass(name = "MyClass")
public class MyClass {
    @GenerateField(name = "myField", type = "String")
    private String myField;
    
    @GenerateMethod(name = "myMethod")
    public void myMethod() {
        // do something
    }
}

public class CodeGenerator {
    public static void generate(Class<?> clazz) {
        GenerateClass classAnnotation = clazz.getAnnotation(GenerateClass.class);
        String className = classAnnotation.name();
        StringBuilder code = new StringBuilder("public class " + className + " {\n");
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            GenerateField fieldAnnotation = field.getAnnotation(GenerateField.class);
            if (fieldAnnotation != null) {
                String fieldName = fieldAnnotation.name();
                String fieldType = fieldAnnotation.type();
                code.append("private ").append(fieldType).append(" ").append(fieldName).append(";\n");
            }
        }
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            GenerateMethod methodAnnotation = method.getAnnotation(GenerateMethod.class);
            if (methodAnnotation != null) {
                String methodName = methodAnnotation.name();
                code.append("public void ").append(methodName).append("() {\n");
                code.append("// do something\n");
                code.append("}\n");
            }
        }
        code.append("}");
        System.out.println(code.toString());
    }
}

CodeGenerator.generate(MyClass.class);
           

在上面的代碼中,我們定義了一個名為GenerateClass的注解,在MyClass類中使用該注解标記了需要生成的類名。然後,我們定義了一個名為GenerateField的注解,在MyClass類中使用該注解标記了需要生成的字段資訊。最後,我們定義了一個名為GenerateMethod的注解,在MyClass類中使用該注解标記了需要生成的方法資訊。在CodeGenerator類中,我們使用反射機制擷取注解資訊,并根據注解資訊生成Java代碼。

注解和測試架構的結合使用

測試架構是一種用于編寫和運作自動化測試的工具,可以幫助開發人員快速、準确地發現和修複代碼中的缺陷。Java注解可以與測試架構結合使用,以實作更加簡潔、可讀的測試代碼。例如,我們可以使用Java注解來标記測試方法、測試類、測試資料等資訊,并在測試架構中根據注解資訊自動生成測試代碼。例如:

@TestClass
public class MyTest {
    @Test
    @DisplayName("Test add method of calculator")
    @TestWithData({ "1, 2, 3", "2, 3, 5", "3, 4, 7" })
    public void testAdd(int a, int b, int expected) {
        Calculator calculator = new Calculator();
        int actual = calculator.add(a, b);
        assertEquals(expected, actual);
    }
}

public class TestRunner {
    public static void main(String[] args) {
        List<Class<?>> classes = Arrays.asList(MyTest.class);
        for (Class<?> clazz : classes) {
            TestClass testClassAnnotation = clazz.getAnnotation(TestClass.class);
            if (testClassAnnotation != null) {
                String className = clazz.getSimpleName();
                System.out.println("Running test class: " + className);
                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    Test testAnnotation = method.getAnnotation(Test.class);
                    if (testAnnotation != null) {
                        String methodName = method.getName();
                        DisplayName displayNameAnnotation = method.getAnnotation(DisplayName.class);
                        String displayName = displayNameAnnotation != null ? displayNameAnnotation.value() : methodName;
                        System.out.println("Running test method: " + displayName);
                        TestWithData testWithDataAnnotation = method.getAnnotation(TestWithData.class);
                        if (testWithDataAnnotation != null) {
                            String[] data = testWithDataAnnotation.value();
                            for (String datum : data) {
                                String[] params = datum.split(", ");
                                int a = Integer.parseInt(params[0]);
                                int b = Integer.parseInt(params[1]);
                                int expected = Integer.parseInt(params[2]);
                                try {
                                    method.invoke(clazz.newInstance(), a, b, expected);
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        } else {
                            try {
                                method.invoke(clazz.newInstance());
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
}
           

在上面的代碼中,我們定義了一個名為TestClass的注解,在MyTest類中使用該注解标記了需要進行測試的類。然後,我們定義了一個名為Test的注解,在testAdd方法中使用該注解标記了需要進行測試的方法。使用@DisplayName注解可以為測試方法定義一個可讀的名稱,使用@TestWithData注解可以為測試方法定義多組測試資料。在TestRunner類中,我們使用反射機制擷取注解資訊,并根據注解資訊自動生成測試代碼。在測試方法中,我們使用JUnit提供的斷言方法(例如assertEquals)進行斷言。