Using annotations - contains cases junit and jpa

Posted by dolphinsnot on Sun, 02 Jan 2022 17:45:20 +0100

Annotation overview

What is annotation

The most common annotations are those in the Spring framework, such as @ RequestMapping @Controller. What is the role of annotations? Why use annotations?

Annotation is like a label, which can mark classes, attributes and methods. The marked content can use the getannotation (class < T >) method of the corresponding object (Class/Method/Field) to obtain the annotation marked on the current content, so as to make corresponding operations.

Why do you use annotations? This is about AOP (aspect oriented programming). AOP is actually the supplement and improvement of OOP (object oriented programming), and object-oriented programming is a top-down relationship of complete things. When some specific businesses need a unified pre-processing or post-processing, such as permission control Log printing, interface access statistics and so on. Object-oriented will often lead to a large number of code duplication, and the project complexity and coupling will increase greatly. In this case, tangential programming came into being. AOP uses a concept called "crosscutting". AOP note links need to be added

format

Basic format

public @interface AnnotationName {

}

Add attribute

You can also add attributes in the annotation class (it looks like a method, but it is actually equivalent to attributes)

String value() default "";

Using annotations

Use annotations and assign values to attributes. When only one value is given and the value is value, the attribute name can be omitted.

@AnnotationName(value = "value")
// be equal to
@AnnotationName("value")

Limit usage location and scope

Limit the use position of the annotation, and add the @ Target() annotation on the annotation class. This annotation accepts a single ElementType attribute or array.

  • TYPE: can be used on classes, interfaces or enumeration classes
  • FIELD: can be used in attribute
  • METHOD: can be used in the METHOD
  • PARAMETER: can be used in method parameters
  • CONSTRUCTOR: can be used in construction parameters
  • LOCAL_VARIABLE: can be used in local variables
  • ANNOTATION_TYPE: can be used in annotation class
@Target({ElementType.TYPE, ElementType.Field})

Limit the effective scope of the annotation, add @ Retention() annotation on the annotation class, and this annotation accepts a single RetentionPolicy attribute.

  • SOURCE: annotations exist only at the SOURCE level and will be ignored and deleted after compilation
  • CLASS: annotations exist after compilation, but they will be ignored and deleted after the CLASS is loaded into memory
  • RUNTIME: annotations still exist when the program runs after the class is loaded. Only annotations in this state will be reflected and read
@Retention(RetentionPolicy.RUNTIME)

Annotation case

junit unit test

The main function of unit test is to have pre post methods. The following code simply implements the main functions of junit.

Annotation class code

  • MoBefore: mark on pre execution method
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MoBefore {

    String value() default "";

}
  • MoAfter: Post execution method tag
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MoAfter {

    String value() default "";
}
  • MoTest: Test Method tag
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MoTest {

    String value() default "";

}
  • MoJunit: contains test method class tags
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MoJunit {

    String value() default "";

}

Test class

  • NoCanRunException: custom exception
public class NoCanRunException extends RuntimeException {
    public NoCanRunException(String msg){
        super(msg);
    }
}
  • AnnoTargetClass: the target test class to run
@MoJunit
public class AnnoTargetClass {

    @MoBefore("is before")
    public void MoRunBefore(){
        System.out.println("run mo run before");
    }

    @MoTest("this is MoRunTest method")
    public void MoRunTest(){
        System.out.println("run mo run test");
    }

    @MoAfter("is after")
    public void MoRunAfter(){
        System.out.println("run mo run after");
    }

}

main method

  • Main class
public class MoJunitTestFrameWork {

    public static final Logger log = LoggerFactory.getLogger(MoJunitTestFrameWork.class);

    public static void main(String[] args) {
        try {
            // Scan the specified package
            String packageName = "com.mochen.advance.annotation.junit.test";
            // Get all class names under the package
            List<String> classNames = getClassNames(packageName);
            for (String className : classNames) {
                Class<?> clazz = Class.forName(packageName + "." +className);
                // Get the class with MoJunit annotation
                MoJunit annotation = clazz.getAnnotation(MoJunit.class);
                if (annotation == null) continue;
                Object o = clazz.newInstance(); // instantiation 
                // Gets the method of all specified annotations
                List<Map<String, Object>> runMethodList = new ArrayList<>();
                List<Map<String, Object>> beforeMethodList = new ArrayList<>();
                List<Map<String, Object>> afterMethodList = new ArrayList<>();
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    Map<String, Object> map = new HashMap<>();
                    MoBefore annotationMoBefore = method.getAnnotation(MoBefore.class);
                    if (annotationMoBefore != null){
                        map.put("value", annotationMoBefore.value());
                        map.put("method", method);
                        beforeMethodList.add(map);
                        continue;
                    }
                    MoTest annotationMoTest = method.getAnnotation(MoTest.class);
                    if (annotationMoTest != null){
                        map.put("value", annotationMoTest.value());
                        map.put("method", method);
                        runMethodList.add(map);
                        continue;
                    }
                    MoAfter annotationMoAfter = method.getAnnotation(MoAfter.class);
                    if (annotationMoAfter != null){
                        map.put("value", annotationMoAfter.value());
                        map.put("method", method);
                        afterMethodList.add(map);
                    }
                }

                // If the obtained test method is empty, an exception is thrown
                if (runMethodList.size() <= 0){
                    throw new NoCanRunException("this class is @MoJunit class, but no @MoTest annotation");
                }

                for (Map<String, Object> runMap : runMethodList) {
                    // Execute pre method
                    for (Map<String, Object> beforeMap : beforeMethodList) {
                        log.info("before run value => {}", beforeMap.get("value"));
                        ((Method) beforeMap.get("method")).invoke(o);
                    }
                    // Execution test method
                    log.info("test run value => {}", runMap.get("value"));
                    ((Method) runMap.get("method")).invoke(o);
                    // Execute post method
                    for (Map<String, Object> afterMap : afterMethodList) {
                        log.info("after run value => {}", afterMap.get("value"));
                        ((Method) afterMap.get("method")).invoke(o);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static List<String> getClassNames(String packageName) throws IOException {
        List<String> classNameList = new ArrayList<>();
        // Package relative path
        String packagePath = packageName.replace(".", "/");
        // Resource URL
        URL url = ClassLoader.getSystemResource("");
        // The asking price agreement is generally file or jar
        if ("file".equals(url.getProtocol())) {
            File[] files = new File(url.getPath() + packagePath)
                    .listFiles(file -> file.getName().endsWith(".class") || file.isDirectory());
            for (File file : files) {
                // Folder, recursive traversal, omitted here
                if (file.isDirectory()) {
                    continue;
                }
                // Output class name
                classNameList.add(file.getName().replace(".class", ""));
            }
        } else if ("jar".equals(url.getProtocol())) {
            // Get jar package
            JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
            // Get all the files under the jar package
            Enumeration<JarEntry> entries = jarFile.entries();
            // Filter files by package name
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.getName().startsWith(packagePath) || entry.getName().endsWith(".class")) {
                    String name = entry.getName().replace(packagePath, "").replace(".class", "");
                    // The folder under the package is not traversed here
                    if (name.contains("/")) {
                        continue;
                    }
                    classNameList.add(name);
                }
            }
        }
        return classNameList;
    }
}

jpa

JPA (Java Persistence API) itself is not a tool or framework; Instead, it defines a set of concepts that can be implemented by any tool or framework. JPA's object relational mapping (ORM) model was originally based on Hibernate.

ORM (Object Relational Mapping) object relational mapping is to solve the problem of mismatch between database data types and programming language data types. ORM can automatically persist the data in the programming language to the database through the mapping relationship between the two data types. Among them, the current mainstream frameworks are Hibernate and MyBatis.

Data type mapping, for example

MySql Java
bigint Long
int Integer
datetime Date

wait

Annotation

  • Table: tag classes are DAO (Data Access Objects) database storage objects
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value() default "";
}
  • Field: the database field corresponding to the tag attribute
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Field {
    // Database field name
    String value() default "";
    // Data formatting, such as formatting time
    String format() default "";
}
  • Key: mark the attribute as the primary key
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Key {
    // Database field name
    String value() default "";
    // Is it self increasing
    boolean autoInc() default true;
}

POJO class

  • User: persistent database class
@Data
@AllArgsConstructor
@NoArgsConstructor
@Table("t_user")
public class User {

    @Key("user_id")
    private Long id;

    @Field("user_name")
    private String name;

//    @Field("user_age")
    private String age;

    @Field("user_sex")
    private String sex;

}

Persistent Mapper

  • BaseMapper: connect to the database and get annotations for sql generation
public class BaseMapper<T> extends BaseLogger {

    private static BasicDataSource datasource;

    static {
        // Connect to database
        datasource = new BasicDataSource();
        datasource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://xxx:3306/java_advance");
        datasource.setUsername("xxx");
        datasource.setPassword("xxx");
    }

    // Get JDBC template
    private JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);
    // Objects for DAO operations
    private Class<T> beanClass;

    /**
     * constructor 
     * Complete the acquisition of actual type parameters during initialization. For example, insert BaseDao < User > into User, and beanClass is User class
     */
    public BaseMapper() {
        // Gets the real class name of the generic class in the current class
        beanClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public void add(T bean) {
        // Is the current 'POJO' marked as a 'DAO' class
        Table annoTable = beanClass.getAnnotation(Table.class);
        if (annoTable == null) {
            throw new NoTableClassException("this class without table annotation");
        }
        // Get all the fields of the User object
        Field[] declaredFields = beanClass.getDeclaredFields();
        // Field part
        List<String> fieldStr = new ArrayList<>();
        List<String> valueStr = new ArrayList<>();
        for (int i = 0; i < declaredFields.length; i++) {
            Field field = declaredFields[i];
            // Exclude auto increment fields and fields without key and field annotations
            Key annoKey = field.getAnnotation(Key.class);
            com.mochen.advance.annotation.jpa.anno.Field annoField = field.getAnnotation(com.mochen.advance.annotation.jpa.anno.Field.class);
            if ((annoKey != null && annoKey.autoInc()) || (annoKey == null && annoField == null)) continue;

            // If the name is not manually set in the annotation, the field name is calculated directly
            String columnName = "";
            if (annoKey != null && StrUtil.isNotBlank(annoKey.value())){
                columnName = annoKey.value();
            }
            if (annoField != null && StrUtil.isNotBlank(annoField.value())){
                columnName = annoField.value();
            }
            if (StrUtil.isBlank(columnName)){
                columnName = getColumnName(field.getName());
            }
            fieldStr.add(columnName);
            valueStr.add("?");
        }

        // In the table name part, you need to convert the list into a string, such as [1,2,3], and convert square brackets into parentheses
        StringBuilder sql = new StringBuilder()
                .append("insert into ")
                .append(StrUtil.isBlank(annoTable.value()) ? getColumnName(beanClass.getSimpleName()) : annoTable.value())
                .append(" " + fieldStr.toString().replaceAll("\\[", "(").replaceAll("\\]", ")"))
                .append(" values ")
                .append(valueStr.toString().replaceAll("\\[", "(").replaceAll("\\]", ")"));

        // Get the value of the bean field (the record to insert)
        ArrayList<Object> paramList = new ArrayList<>();
        try {
            for (Field declaredField : declaredFields) {
                // Filter out the key s that are self incrementing fields
                Key annoKey = declaredField.getAnnotation(Key.class);
                if (annoKey != null && annoKey.autoInc()) continue;
                com.mochen.advance.annotation.jpa.anno.Field annoField = declaredField.getAnnotation(com.mochen.advance.annotation.jpa.anno.Field.class);
                if (annoField == null && annoKey == null) continue;
                declaredField.setAccessible(true);
                Object o = declaredField.get(bean);
                paramList.add(o);
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        int size = paramList.size();
        Object[] params = paramList.toArray(new Object[size]);

        // Pass in the sql statement template and the parameters required by the template, and insert the User
        int num = jdbcTemplate.update(sql.toString(), params);
        log.info(sql.toString());
        log.info(Arrays.toString(params));
    }

    /**
     * Get the field name and change the hump naming method to underline naming, for example: classname -- > class_ name
     */
    private static String getColumnName(String className) {
        String tableName = className;
        tableName = tableName.replaceAll("[A-Z]", "_$0").toLowerCase();
        if (tableName.charAt(0) == '_') {
            return tableName.substring(1);
        }
        return tableName;
    }

}
  • UserMapper: Specifies the Mapper of the POJO
public class UserMapper extends BaseMapper<User> {}

Custom exception

  • NoTableClassException: if the specified class is not marked with @ Table annotation, this exception will be thrown
public class NoTableClassException extends RuntimeException{
    public NoTableClassException(String msg){
        super(msg);
    }
}

Test class

  • Test
public class Test {
    public static void main(String[] args) {
        User user = new User(1L, "lxc", "13", "1");
        UserMapper userMapper = new UserMapper();
        // Call a method inherited from BaseMapper
        userMapper.add(user);
    }
}

Topics: Java Annotation