美文网首页
java的反射运用之二注解

java的反射运用之二注解

作者: 程er狗 | 来源:发表于2018-10-21 18:59 被阅读883次

    注解Annontation(也叫元数据)分类

    1.元注解(用于定义注解的注解)
    2.内置注解,即java自带的标准注解
    3.自定义注解

    元注解

    说明
    @Target 表示注解的应用范围,其中ElementType的取值为TYPE(表示该注解只能用在类、接口(注释声明)、enum实例)、FIELD(表示该注解只能用在字段上)、METHOD(方法)、PARAMETER(参数)、CONSTRUCTOR(构造器)、LOCAL_VARIABLE(本地变量)、ANNOTATION_TYPE(注解类型)、PACKAGE(包)、TYPE_PARAMETER(类型参数)
    @Retention 表示需要在什么级别保存注解的信息,其中RetentionPolicy保留策略有SOURCE(源码中保留,在编译期间别编译器丢弃)、CLASS(在class文件中使用,会被JVM丢弃)、RUNTIME(程序运行期间也保留注解信息,这也是反射能读取的阶段,一般都是这个策略)
    @Documented 将此注解包含于Javadoc中
    @Inherited 允许子类继承父类中的注解

    内置注解

    @Override子类对父类方法的重写
    @Deprecated表示已经过时
    @SuppressWarnings用来抑制编译时的警告信息==
    想要了解java内置注解是如何解析的,可以参看这篇文章

    重点:如何运用自定义注解

    业务场景:在之前呢,我们用动态代理实现了业务功能的日志记录,现在我们来改进一下之前的代码,用spring AOP的知识结合注解来实现日志功能的记录。

    spring AOP 相关知识

    AOP概念

    AOP(Aspect Oriented Programming),即面向切面编程,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。业务流程是一个纵向功能,而权限认证、日志、事物==是一个横向流程,没有横向的流程也不会影响业务功能的处理,但是为了系统的完善,这些横向的功能又是必须的。

    AOP功能

    让关注点代码与业务逻辑代码分离

    AOP常用术语

    连接点(Joinpoint)
      增强程序执行的某个特定位置(要在哪个地方做增强操作)。Spring仅支持方法的连接点。

    切点(Pointcut):执行目标对象方法,动态植入切面代码
      切点是一组连接点的集合。AOP通过“切点”定位特定的连接点。通过数据库查询的概念来理解切点和连接点的关系再适合不过了:连接点相当于数据库中的记录,而切点相当于查询条件。
    可以通过切入点表达式,指定拦截那些类的那些方法,给指定的类在运行的时候植入切面类代码。

    增强(Advice):就是需要横向加入的重复代码
      增强是织入到目标类连接点上的一段程序代码。表示要在连接点上做的操作。主要有如下几种增强方式:
    前置增强(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。
    后置增强(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
    返回后增强(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。
    环绕增强(Around advice) :包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。

    切面(Aspect): 切点形成的类,就叫做切面(类),面向切面编程,就是指对很多功能都有的重复代码抽取,再在运行的时候往业务方法上动态植入"切面类代码"
      切面由切点和增强(引介)组成(可以包含多个切点和多个增强),它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的链接点中。

    本项目是在springBoot项目上进行整合的(很简单的,别被springboot吓到了)

    第一步:引入依赖

    第二步:创建出必要的类

    2.1自定义注解

    /**
     * 自定义注解
     * @create by 程二狗 on 2018/10/21 0021
     **/
    
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    public @interface LogRecord {
        String[] value() default {};
    }
    

    2.2定义业务功能接口

    /**定义业务功能
     * @create by 程二狗 on 2018/10/21 0021
     **/
    public interface UserService {
        void  add();
         User  get();
    }
    

    2.3实现业务接口(目标类)

    /**
     * 目标对象
     * @create by 程二狗 on 2018/10/21 0021
     **/
    @Service //把该类加载Ioc容器中
    @LogRecord(value = "user对象的操作")
    public class UserServiceImpl implements UserService {
        @Override
        @LogRecord({"程二狗","18"})
        public void add() {
            System.out.println("user添加方法执行了----");
        }
        @Override
        @LogRecord({"陈雪峰","20"})
        public User get() {
            System.out.println("user查找方法执行了----");
            return null;
        }
    }
    

    2.4核心:切面类(注解解析也在这儿)

    package com.springboot.aop.aspect;
    
    import com.springboot.aop.entity.User;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;
    import java.lang.reflect.Method;
    
    /**切面类:业务无关的代码在这儿抽取封装成一个类
     * 当业务执行时,动态的织入无关的业务代码
     * 同时我也在这儿解析了自定义注解,动态地拼接了数据
     * @create by 程二狗 on 2018/10/21 0021
     **/
    @Component//加入到IOC容器
    @Aspect//指定当前类为切面类
    public class SpringAop {
    //-------------------第一种方式:前置后置方法,有局限性,共用类变量会出现线程安全问题,用的不多-----------------
        //指定切入点表达式,拦截那些方法,即为那些类生成代理对象
    //    @Pointcut("execution(* com.springboot.aop.aspect.UserService.*(..))")
    //    public void pointCut() {
    //    }
    //    @Before("pointCut()")
    //    public void begin() {
    //        System.out.println("前置增强:log日志开始记录了------>");
    //    }
    //    @After("pointCut()")
    //    public void close() {
    //        System.out.println("后置增强:log日志完成记录了<------");
    //    }
    
    //-------------------第二种方式:环绕织入,最常见,开发中用的最多-----------------
        //@within(M)的匹配规则
        //@within(注解类型全限定名)匹配所有持有指定注解的类里面的方法, 即要把注解加在类上.
        // 在接口上声明不起作用,子孙类经测试匹配不到
    
        //@annotation(M)的匹配规则
        //当前执行方法上持有注解 cn.javass.spring.chapter6.Secure将被匹配
        @Around(value = "@annotation(LogRecord) || @within(LogRecord)")
        public Object logRecord(ProceedingJoinPoint pjp) throws Throwable {
    
            //1.获取类上的注解
            LogRecord annoOnClass = (LogRecord) pjp.getSignature().getDeclaringType().getAnnotation(LogRecord.class);
            //获取注解的信息value数组,即自定义注解中的String[] value() default {};
            String[] megOnClass = null;
            if (annoOnClass != null && annoOnClass.value().length > 0) {
                megOnClass = annoOnClass.value();
            }
            //2.获取方法上定义的注解
            Method mth = ((MethodSignature) pjp.getSignature()).getMethod();
            LogRecord annoOnMehtod = mth.getAnnotation(LogRecord.class);
            String[] megOnMethod = null;
            if (annoOnMehtod != null && annoOnMehtod.value().length > 0) {
                megOnMethod = annoOnMehtod.value();
            }
    
            //得到类上的注解信息
            StringBuilder msg = new StringBuilder();
            if (null != megOnClass) {
                for (String onClass : megOnClass) {
                    msg.append(onClass + "===》");
                }
            }
            //得到方法上的注解信息
            if (null != megOnMethod) {
                msg.append(":user的姓名是:" + megOnMethod[0] + ",年龄是" + megOnMethod[1]);
            }
            //相当于增强方法,和业务无关的代码(比如日志记录、权限校验)
            System.out.println("(业务无关代码:)log日志开始记录了====》" +"(获取的注解信息:)"+ msg.toString() + "");
            //执行目标对象的方法,即业务代码
            Object obj = pjp.proceed();
            //相当于增强方法,和业务无关的代码(比如日志记录、权限校验)
            System.out.println("(业务无关代码)log日志完成了记录《====" +"(获取的注解信息:)"+ msg.toString() + "");
            return obj;
        }
    }
    

    小tips:关于spring Aop切点指示符更多详解,请点我

    2.4测试

        @Test
        public void springAopTest() {
            userService.add();
            System.out.println("======================程二狗===========================");
            userService.get();
    //(业务无关代码:)log日志开始记录了====》(获取的注解信息:)user对象的操作===》:user的姓名是:程二狗,年龄是18
    //      user添加方法执行了----
    //(业务无关代码)log日志完成了记录《====(获取的注解信息:)user对象的操作===》:user的姓名是:程二狗,年龄是18
    //              ======================程二狗===========================
    //(业务无关代码:)log日志开始记录了====》(获取的注解信息:)user对象的操作===》:user的姓名是:陈雪峰,年龄是20
    //      user查找方法执行了----
    //(业务无关代码)log日志完成了记录《====(获取的注解信息:)user对象的操作===》:user的姓名是:陈雪峰,年龄是20
        }
    }
    

    总结:现在我们只要在业务功能的类或方法上加上一个小小的注解@logReord,在调用业务功能时,就会得到想要的注解信息


    另一方面:定义好自己的切面类,程序在运行时,自动为业务功能添加增强的方法(日志记录、权限校验==)

    这样一来是不是很方便呢?前端传递的参数,后台接受到后,进行校验的原理就是这个哦,你也可以自己去制定自己想要校验的规则。

    java反射的另一个重要作用,请参看我写的另一片文章

    相关文章

      网友评论

          本文标题:java的反射运用之二注解

          本文链接:https://www.haomeiwen.com/subject/raemzftx.html