使用注解-包含案例junit和jpa

注解概述

什么是注解

注解最常见的就是在 Spring 框架中,例如 @RequestMapping @Controller 等注解,注解是起什么作用?为什么要使用注解?

注解就像一个标签可以标记类、属性和方法,被标记的内容可以使用对应对象 (Class/Method/Field)getAnnotation(Class<T>) 方法来获取到当前内容上标记的注解,从而做出对应的操作。

为什么使用注解,这就要说到 AOP(Aspect-Oriented Programming) 面向切面编程, AOP 其实是 OOP(Object-Oriented Programing) 面向对象编程的补充和完善,而面向对象编程是一种完整事物的自上而下的关系,当某一些具体的业务需要一个统一的前置或者后置处理时,例如权限控制、日志打印、接口访问量统计等等,面向对象往往会导致大量代码重复,项目复杂度和耦合度也会大大增加。在此情况下面向切面编程就应运而生, AOP 使用一种名为“横切”的概念。 需要添加AOP笔记链接

格式

基础格式

public @interface AnnotationName {

}

添加属性

也可以在注解类中添加属性(看起来是方法,其实相当于属性)

String value() default "";

使用注解

使用注解并向属性赋值,当只给一个值且这个值是value时,可以省略属性名。

@AnnotationName(value = "value")
// 等于
@AnnotationName("value")

限定使用位置和作用域

限定注解的使用位置,在注解类上添加 @Target() 注解,此注解接受单个 ElementType 属性或数组。

  • TYPE :可使用在类,接口或枚举类上
  • FIELD :可使用在属性
  • METHOD :可使用在方法
  • PARAMETER :可使用在方法参数
  • CONSTRUCTOR :可使用在构造参数
  • LOCAL_VARIABLE :可使用在局部变量
  • ANNOTATION_TYPE :可使用在注解类
@Target({ElementType.TYPE, ElementType.Field})

限定注解的生效范围,在注解类上添加 @Retention() 注解,此注解接受单个 RetentionPolicy 属性。

  • SOURCE :注解只在源码层面存在,编译后将会被忽略删除
  • CLASS :注解在编译后存在,但在类加载到内存中后注解将会被忽略删除
  • RUNTIME :注解在类加载后程序运行时依旧存在,只有此状态的注解会被反射读取到
@Retention(RetentionPolicy.RUNTIME)

注解案例

junit 单元测试

单元测试的主要功能是存在前置后置方法,以下代码简单实现了 junit 的主要功能。

注解类代码

  • MoBefore :前置执行方法上标记
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MoBefore {

    String value() default "";

}
  • MoAfter :后置执行方法标记
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MoAfter {

    String value() default "";
}
  • MoTest :测试方法标记
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MoTest {

    String value() default "";

}
  • MoJunit :包含测试方法类标记
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MoJunit {

    String value() default "";

}

测试类

  • NoCanRunException :自定义异常
public class NoCanRunException extends RuntimeException {
    public NoCanRunException(String msg){
        super(msg);
    }
}
  • AnnoTargetClass :要运行的目标测试类
@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 方法

  • 主类
public class MoJunitTestFrameWork {

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

    public static void main(String[] args) {
        try {
            // 扫描指定的包
            String packageName = "com.mochen.advance.annotation.junit.test";
            // 获取到包下的所有class名称
            List<String> classNames = getClassNames(packageName);
            for (String className : classNames) {
                Class<?> clazz = Class.forName(packageName + "." +className);
                // 获取到使用了 MoJunit 注解的类
                MoJunit annotation = clazz.getAnnotation(MoJunit.class);
                if (annotation == null) continue;
                Object o = clazz.newInstance(); // 实例化
                // 获取到所有指定注解的方法
                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 (runMethodList.size() <= 0){
                    throw new NoCanRunException("this class is @MoJunit class, but no @MoTest annotation");
                }

                for (Map<String, Object> runMap : runMethodList) {
                    // 执行前置方法
                    for (Map<String, Object> beforeMap : beforeMethodList) {
                        log.info("before run value => {}", beforeMap.get("value"));
                        ((Method) beforeMap.get("method")).invoke(o);
                    }
                    // 执行测试方法
                    log.info("test run value => {}", runMap.get("value"));
                    ((Method) runMap.get("method")).invoke(o);
                    // 执行后置方法
                    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<>();
        // 包相对路径
        String packagePath = packageName.replace(".", "/");
        // 资源URL
        URL url = ClassLoader.getSystemResource("");
        // 问价协议一般为 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) {
                // 文件夹,可递归遍历,此处省略
                if (file.isDirectory()) {
                    continue;
                }
                // 输出类名称
                classNameList.add(file.getName().replace(".class", ""));
            }
        } else if ("jar".equals(url.getProtocol())) {
            // 获取jar包
            JarFile jarFile = ((JarURLConnection) url.openConnection()).getJarFile();
            // 拿到jar包下所有文件
            Enumeration<JarEntry> entries = jarFile.entries();
            // 根据包名过滤文件
            while (entries.hasMoreElements()) {
                JarEntry entry = entries.nextElement();
                if (entry.getName().startsWith(packagePath) || entry.getName().endsWith(".class")) {
                    String name = entry.getName().replace(packagePath, "").replace(".class", "");
                    // 包下文件夹,此处不遍历
                    if (name.contains("/")) {
                        continue;
                    }
                    classNameList.add(name);
                }
            }
        }
        return classNameList;
    }
}

jpa

JPA (Java Persistence API) 本身不是一个工具或框架; 相反,它定义了一组可以由任何工具或框架实现的概念, JPA 的对象关系映射 (ORM) 模型最初基于 Hibernate

ORM (Object Relational Mapping) 对象关系映射是为了解决数据库数据类型和编程语言数据类型之间不匹配的问题, ORM 可以通过两种数据类型之间的映射关系,自动的将编程语言中的数据持久化到数据库中。其中,当前主流的框架是 HibernateMyBatis

数据类型映射,例如

MySql Java
bigint Long
int Integer
datetime Date

等等

注解类

  • Table :标记类为 DAO (Data Access Objects ) 数据库存储对象
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
    String value() default "";
}
  • Field :标记属性对应的数据库字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Field {
    // 数据库字段名称
    String value() default "";
    // 数据格式化,例如格式化时间
    String format() default "";
}
  • Key :标记属性为主键
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Key {
    // 数据库字段名称
    String value() default "";
    // 是否是自增
    boolean autoInc() default true;
}

POJO

  • User :持久化数据库类
@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;

}

持久化 Mapper

  • BaseMapper :连接数据库并反射获取注解进行 sql 生成
public class BaseMapper<T> extends BaseLogger {

    private static BasicDataSource datasource;

    static {
        // 连接数据库
        datasource = new BasicDataSource();
        datasource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        datasource.setUrl("jdbc:mysql://xxx:3306/java_advance");
        datasource.setUsername("xxx");
        datasource.setPassword("xxx");
    }

    // 得到jdbcTemplate
    private JdbcTemplate jdbcTemplate = new JdbcTemplate(datasource);
    // DAO操作的对象
    private Class<T> beanClass;

    /**
     * 构造器
     * 初始化时完成对实际类型参数的获取,比如BaseDao<User>插入User,那么beanClass就是user.class
     */
    public BaseMapper() {
        // 获取到当前类中的泛型类的真实类名
        beanClass = (Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    public void add(T bean) {
        // 当前 `POJO` 是否标记为 `DAO` 类
        Table annoTable = beanClass.getAnnotation(Table.class);
        if (annoTable == null) {
            throw new NoTableClassException("this class without table annotation");
        }
        // 得到User对象的所有字段
        Field[] declaredFields = beanClass.getDeclaredFields();
        // 字段部分
        List<String> fieldStr = new ArrayList<>();
        List<String> valueStr = new ArrayList<>();
        for (int i = 0; i < declaredFields.length; i++) {
            Field field = declaredFields[i];
            // 排除自增字段与没有key和field注解的字段
            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;

            // 如果没有手动在注解中设置名称就直接计算字段名
            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("?");
        }

        // 表名部分,此处需要将list转为字符串 例如 [1,2,3] 且要将方括号转为圆括号
        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("\\]", ")"));

        // 获得bean字段的值(要插入的记录)
        ArrayList<Object> paramList = new ArrayList<>();
        try {
            for (Field declaredField : declaredFields) {
                // 过滤掉为自增字段的key
                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]);

        // 传入sql语句模板和模板所需的参数,插入User
        int num = jdbcTemplate.update(sql.toString(), params);
        log.info(sql.toString());
        log.info(Arrays.toString(params));
    }

    /**
     * 获取字段名称,将驼峰命名法转为下划线命名 例如: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 :指定 POJOMapper
public class UserMapper extends BaseMapper<User> {}

自定义异常

  • NoTableClassException :指定的类如果没有标记 @Table 注解会抛出此异常
public class NoTableClassException extends RuntimeException{
    public NoTableClassException(String msg){
        super(msg);
    }
}

测试类

  • Test
public class Test {
    public static void main(String[] args) {
        User user = new User(1L, "lxc", "13", "1");
        UserMapper userMapper = new UserMapper();
        // 调用继承自BaseMapper的方法
        userMapper.add(user);
    }
}

image

posted @ 2021-12-28 17:43  陌尘Ya  阅读(114)  评论(0)    收藏  举报