Spring系列(11-15)

烟雨 3年前 (2022-09-15) 阅读数 423 #Spring
文章标签 Spring

十一、lazy-init:bean延迟初始化

<bean lazy-init="是否是延迟初始化" />

十二、使用继承简化bean配置

<bean id="serviceA" class="com.javacode2018.lesson001.demo12.ServiceA"/>
<bean id="baseService" abstract="true">
	<property name="name" value="路人甲Java"/>
	<property name="serviceA" ref="serviceA"/>
</bean>
<bean id="serviceB" class="com.javacode2018.lesson001.demo12.ServiceB" parent="baseService"/>
<bean id="serviceC" class="com.javacode2018.lesson001.demo12.ServiceC" parent="baseService"/>

十三、单例bean中使用多例bean

1、ApplicationContext接口的方式

<bean id="serviceA" class="com.javacode2018.lesson001.demo13.applicationcontextaware.ServiceA" scope="prototype"/>
public class ServiceA {
}

public class ServiceB implements ApplicationContextAware {
    private ApplicationContext context;
    public void say(){
    	ServiceA serviceA = this.getServiceA();
    	System.out.println("this:"+this+",serviceA:"+ serviceA);
    }
    
    public ServiceA getServiceA() {
    	return this.context.getBean(ServiceA.class);
    }
	
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    	this.context = applicationContext;
    }
}

2、lookup-method方式实现

<bean id="serviceA" class="com.javacode2018.lesson001.demo13.lookupmethod.ServiceA" scope="prototype"/>

<bean id="serviceB" class="com.javacode2018.lesson001.demo13.lookupmethod.ServiceB">
	<lookup-method name="getServiceA" bean="serviceA"/>
</bean>
lookup-method:看其名字,就知道意思:方法查找。
调用name属性指定的方法的时候,Spring会对这个方法进行拦截,然后去容器中查找lookup-method元素中bean属性指定的bean,然后将找到的bean作为方法的返回值返回。

3、replaced-method:方法替换

replaced-method:方法替换,比如我们要调用serviceB中的getServiceA的时候,我们可以对serviceB这个bean中的getServiceA方法进行拦截,把这个调用请求转发到一个替换者处理。这就是replaced-method可以实现的功能,比lookup-method更强大更灵活。

3.1、步骤一:定义替换者

自定义一个替换者,替换者需要实现Spring中的MethodReplacer接口,看一下这个接口的定义:
public interface MethodReplacer {
    /**
    * @param obj 被替换方法的目标对象
    * @param method 目标对象的方法
    * @param args 方法的参数
    * @return return value for the method
    */
    Object reimplement(Object obj, Method method, Object[] args) throws Throwable;
}

servieB的方法替换者

public class ServiceBMethodReplacer implements MethodReplacer, ApplicationContextAware {
    @Override
    public Object reimplement(Object obj, Method method, Object[] args) throws Throwable {
        return this.context.getBean(ServiceA.class);
    }
    
    private ApplicationContext context;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}

3.2、步骤二:定义替换者bean

<!-- 定义替换者bean -->
<bean id="serviceBMethodReplacer" class="com.javacode2018.lesson001.demo14.ServiceBMethodReplacer" />

通过replaced-method元素配置目标bean需要被替换的方法

<bean id="serviceB" class="com.javacode2018.lesson001.demo14.ServiceB">
	<replaced-method name="getServiceA" replacer="serviceAMethodReplacer"/>
</bean>
  • name:用于指定当前bean需要被替换的方法。

  • replacer:替换者,即实现了MethodReplacer接口的类对应的bean。

上面配置中当调用 serviceB 的getServiceA的时候,会自动调用 serviceAMethodReplacer 这个bean中的 reimplement 方法进行处理。

十四、动态代理

1、jdk动态代理

jdk中为实现代理提供了支持,主要用到2个:
java.lang.reflect.Proxy
java.lang.reflect.InvocationHandler
jdk自带的代理使用上面有个限制,只能为接口创建代理类

1.1、java.lang.reflect.Proxy

这是jdk动态代理中主要的一个类,里面有一些静态方法会经常用到,我们来熟悉一下:

getProxyClass()

为指定的接口创建代理类,返回代理类的Class对象。
public static Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces);
  • loader:定义代理类的类加载器。

  • interfaces:指定需要实现的接口列表,创建的代理默认会按顺序实现interfaces指定的接口。

newProxyInstance()

创建代理类的实例对象
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)

这个方法先为指定的接口创建代理类,然后会生成代理类的一个实例,最后一个参数比较特殊,是InvocationHandler类型的,这个是个接口如下:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
上面方法会返回一个代理对象,当调用代理对象的任何方法的时候,会就被 InvocationHandler 接口的 invoke 方法处理,所以主要代码需要写在 invoke 方法中。

isProxy()

判断指定的类是否是一个代理类
public static boolean isProxyClass(Class<?> cl)

getInvocationHandler()

获取代理对象的 InvocationHandler 对象
public static InvocationHandler getInvocationHandler(Object proxy) throws IllegalArgumentException

案例1

public interface IService {
    void m1();
    void m2();
    void m3();
}

创建IService接口的代理对象

@Test
public void m1() throws Exception {
    // 1. 获取接口对应的代理类
    Class<IService> proxyClass = (Class<IService>)Proxy.getProxyClass(IService.class.getClassLoader(), IService.class);
    // 2. 创建代理类的处理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是InvocationHandler,被调用的方法是:" + method.getName());
            return null;
        }
    };
    // 3. 创建代理实例
    IService proxyService = proxyClass.getConstructor(InvocationHandler.class).newInstance(invocationHandler);
    // 4. 调用代理的方法
    proxyService.m1();
    proxyService.m2();
    proxyService.m3();
}

案例2

@Test
public void m2() throws Exception {
    // 1. 创建代理类的处理器
    InvocationHandler invocationHandler = new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是InvocationHandler,被调用的方法是:" + method.getName());
            return null;
        }
    };
    // 2. 创建代理实例
    IService proxyService = (IService)  Proxy.newProxyInstance(IService.class.getClassLoader(), new Class[] {IService.class}, invocationHandler);
    // 3. 调用代理的方法
    proxyService.m1();
    proxyService.m2();
    proxyService.m3();
}

案例3:任意接口中的方法耗时统计

下面我们通过jdk动态代理实现一个通用的代理,解决统计所有接口方法耗时的问题。
主要的代码在代理处理器 InvocationHandler 实现上面,如下:
public class CostTimeInvocationHandler implements InvocationHandler {
    private Object target;
    public CostTimeInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws
        Throwable {
        long starTime = System.nanoTime();
        Object result = method.invoke(this.target, args);
        long endTime = System.nanoTime();
        System.out.println(this.target.getClass() + ".m1()方法耗时(纳秒):" + (endTime - starTime));
        return result;
    }
    /**
     * 用来创建targetInterface接口的代理对象
     *
     * @param target 需要被代理的对象
     * @param targetInterface 被代理的接口
     * @param <T>
     * @return
     */
    public static<T> T createProxy(Object target, Class<T> targetInterface) {
        if (!targetInterface.isInterface()) {
            throw new IllegalStateException("targetInterface必须是接口类型!");
        } else if (!targetInterface.isAssignableFrom(target.getClass())) {
            throw new IllegalStateException("target必须是targetInterface接口的实现类!");
        }
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
                                          target.getClass().getInterfaces(), 
                                          new CostTimeInvocationHandler(target));
    }
}
上面主要是 createProxy 方法用来创建代理对象,2个参数:
  1. target:目标对象,需要实现targetInterface接口。

  2. targetInterface:需要创建代理的接口。

  3. invoke方法中通过 method.invoke(this.target, args) 调用目标方法,然后统计方法的耗时。

我们再来个接口,也需要统计耗时的功能,此时我们无需去创建新的代理类即可实现同样的功能,如下:
public interface IUserService {
    /**
    * 插入用户信息
    * @param name
    */
    void insert(String name);
}
public class UserService implements IUserService {
    @Override
    public void insert(String name) {
    	System.out.println(String.format("用户[name:%s]插入成功!", name));
    }
}

直接使用CostTimeInvocationHandler来创建代理类

@Test
public void costTimeProxy() {
    IService serviceA = CostTimeInvocationHandler.createProxy(new ServiceA(), IService.class);
    IService serviceB = CostTimeInvocationHandler.createProxy(new ServiceB(), IService.class);
    
    serviceA.m1();
    serviceA.m2();
    serviceA.m3();
    
    serviceB.m1();
    serviceB.m2();
    serviceB.m3();
}

2、cglib动态代理

cglib是一个强大、高性能的字节码生成库,它用于在运行时扩展Java类和实现接口。
本质上它是通过动态的生成一个子类去覆盖所要代理的类(非final修饰的类和方法)。
Enhancer可能是CGLIB中最常用的一个类,和jdk中的Proxy不同的是,Enhancer既能代理普通的class,也能代理接口。
Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。
Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对final类进行代理操作

案例1:拦截所有方法(MethodInterceptor)

public class Service1 {
    public void m1() {
    	System.out.println("我是m1方法");
    }
    public void m2() {
    	System.out.println("我是m2方法");
    }
}

使用Enhancer来给某个类创建代理类,步骤

@Test
public void test1() {
    //1.创建Enhancer对象
    Enhancer enhancer = new Enhancer();
    //2.通过setSuperclass来设置父类型,即需要给哪个类创建代理类
    enhancer.setSuperclass(Service1.class);
    /*3.设置回调,需实现org.springframework.cglib.proxy.Callback接口,
            此处我们使用的是org.springframework.cglib.proxy.MethodInterceptor,也是一个接
            口,实现了Callback接口,当调用代理对象的任何方法的时候,都会被MethodInterceptor接口的invoke方法处理*/
    enhancer.setCallback(new MethodInterceptor() {
        /**
             * 代理对象方法拦截器
             * @param o 代理对象
             * @param method 被代理的类的方法,即Service1中的方法
             * @param objects 调用方法传递的参数
             * @param methodProxy 方法代理对象
             */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("调用方法:" + method);
            //可以调用MethodProxy的invokeSuper调用被代理类的方法
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    });
    //4.获取代理对象,调用enhancer.create方法获取代理对象,这个方法返回的是Object类型的,需要强转一下
    Service1 proxy = (Service1) enhancer.create();
    //5.调用代理对象的方法
    proxy.m1();
    proxy.m2();
}

案例2:拦截所有方法(MethodInterceptor)

public class Service2 {
    public void m1() {
    	System.out.println("我是m1方法");
    	this.m2();
    }
    public void m2() {
    	System.out.println("我是m2方法");
    }
}
这个类和上面的Service1类似,有点不同是在m1方法中调用了m2方法。
下面来采用案例1中同样的方式来给Service2创建代理,如下:
@Test
public void test2() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Service2.class);
    enhancer.setCallback(new MethodInterceptor() {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("调用方法:" + method);
            Object result = methodProxy.invokeSuper(o, objects);
            return result;
        }
    });
    Service2 proxy = (Service2) enhancer.create();
    proxy.m1();
}

注意上面的代码,只调用了m1方法,看一下输出效果:

调用方法:public void com.javacode2018.lesson001.demo17.Service2.m1()
我是m1方法
调用方法:public void com.javacode2018.lesson001.demo17.Service2.m2()
我是m2方法
从输出中可以看出m1和m2方法都被拦截器处理了,而m2方法是在Service1的m1方法中调用的,也被拦截处理了。

案例3:拦截所有方法并返回固定值(FixedValue)

当调用某个类的任何方法的时候,都希望返回一个固定的值,此时可以使用 FixedValue 接口,如下:
enhancer.setCallback(new FixedValue() {
    @Override
    public Object loadObject() throws Exception {
    	return "路人甲";
    }
});

例如

public class Service3 {
    public String m1() {
    	System.out.println("我是m1方法");
    	return "hello:m1";
    }
    public String m2() {
    	System.out.println("我是m2方法");
    	return "hello:m2";
    }
}

对用的测试用例:

@Test
public void test3() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Service3.class);
    enhancer.setCallback(new FixedValue() {
        @Override
        public Object loadObject() throws Exception {
            return "路人甲";
        }
    });
    Service3 proxy = (Service3) enhancer.create();
    System.out.println(proxy.m1());
    System.out.println(proxy.m2());
    System.out.println(proxy.toString());
}

运行输出

路人甲
路人甲
路人甲

案例4:直接放行,不做任何操作(NoOp.INSTANCE)

Callback 接口下面有个子接口 org.springframework.cglib.proxy.NoOp ,将这个作为Callback的时候,被调用的方法会直接放行,像没有任何代理一样,感受一下效果:
@Test
public void test6() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Service3.class);
    enhancer.setCallback(NoOp.INSTANCE);
    Service3 proxy = (Service3) enhancer.create();
    System.out.println(proxy.m1());
    System.out.println(proxy.m2());
}

运行结果

我是m1方法
hello:m1
我是m2方法
hello:m2

从输出中可以看出,被调用的方法没有被代理做任何处理,直接进到目标类Service3的方法中

案例5:不同的方法使用不同的拦截器(CallbackFilter)

public class Service4 {
    public void insert1() {
        System.out.println("我是insert1");
    }
    public void insert2() {
        System.out.println("我是insert2");
    }
    
    public String get1() {
        System.out.println("我是get1");
        return "get1";
    }
    public String get2() {
        System.out.println("我是get2");
        return "get2";
    }
}
需求,给这个类创建一个代理需要实现下面的功能:
1. 以insert开头的方法需要统计方法耗时
2. 以get开头的的方法直接返回固定字符串
下来看代码,然后再解释:
@Test
public void test4() {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(Service4.class);
    //创建2个Callback
    Callback[] callbacks = {
        //这个用来拦截所有insert开头的方法
        new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[]
                                    objects, MethodProxy methodProxy) throws Throwable {
                long starTime = System.nanoTime();
                Object result = methodProxy.invokeSuper(o, objects);
                long endTime = System.nanoTime();
                System.out.println(method + ",耗时(纳秒):" + (endTime -  starTime));
                return result;
            }
        },
        //下面这个用来拦截所有get开头的方法,返回固定值的
        new FixedValue() {
            @Override
            public Object loadObject() throws Exception {
                return "路人甲Java";
            }
        }
    };
    enhancer.setCallbackFilter(new CallbackFilter() {
        @Override
        public int accept(Method method) {
            return 0;
        }
    });
    //调用enhancer的setCallbacks传递Callback数组
    enhancer.setCallbacks(callbacks);
    /**
     * 设置过滤器CallbackFilter
     * CallbackFilter用来判断调用方法的时候使用callbacks数组中的哪个Callback来处理当前方法
     * 返回的是callbacks数组的下标
     */
    enhancer.setCallbackFilter(new CallbackFilter() {
        @Override
        public int accept(Method method) {
            //获取当前调用的方法的名称
            String methodName = method.getName();
            /**
             * 方法名称以insert开头,
             * 返回callbacks中的第1个Callback对象来处理当前方法,
             * 否则使用第二个Callback处理被调用的方法
             */
            return methodName.startsWith("insert") ? 0 : 1;
        }
    });
    Service4 proxy = (Service4) enhancer.create();
    System.out.println("---------------");
    proxy.insert1();
    System.out.println("---------------");
    proxy.insert2();
    System.out.println("---------------");
    System.out.println(proxy.get1());
    System.out.println("---------------");
    System.out.println(proxy.get2());
}

运行结果

---------------
我是insert1
public void com.javacode2018.lesson001.demo17.Service4.insert1(),耗时(纳秒):15396100
---------------
我是insert2
public void com.javacode2018.lesson001.demo17.Service4.insert2(),耗时(纳秒):66200
---------------
路人甲Java
---------------
路人甲Java
由于需求中要对不同的方法做不同的处理,所以需要有2个Callback对象,当调用代理对象的方法的时候,具体会走哪个Callback呢,此时会通过 CallbackFilter 中的 accept 来判断,这个方法返回 callbacks数组的索引 。
上面这个案例还有一种简单的实现,见案例6

案例6:对案例5的优化(CallbackHelper)

cglib中有个CallbackHelper类,可以对案例5的代码进行有环,CallbackHelper类相当于对一些代码进行了封装,方便实现案例5的需求,实现如下:
@Test
public void test5() {
    Enhancer enhancer = new Enhancer();
    //创建2个Callback
    Callback costTimeCallback = (MethodInterceptor) (Object o, Method method, Object[] objects, MethodProxy methodProxy) -> {
        long starTime = System.nanoTime();
        Object result = methodProxy.invokeSuper(o, objects);
        long endTime = System.nanoTime();
        System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
        return result;
    };
    
    //下面这个用来拦截所有get开头的方法,返回固定值的
    Callback fixdValueCallback = (FixedValue) () -> "路人甲Java";
    CallbackHelper callbackHelper = new CallbackHelper(Service4.class, null) {
        @Override
        protected Object getCallback(Method method) {
            return method.getName().startsWith("insert") ? costTimeCallback :
            fixdValueCallback;
        }
    };
    
    enhancer.setSuperclass(Service4.class);
    //调用enhancer的setCallbacks传递Callback数组
    enhancer.setCallbacks(callbackHelper.getCallbacks());
    //设置CallbackFilter,用来判断某个方法具体走哪个Callback
    enhancer.setCallbackFilter(callbackHelper);
    Service4 proxy = (Service4) enhancer.create();
    System.out.println("---------------");
    proxy.insert1();
    System.out.println("---------------");
    proxy.insert2();
    System.out.println("---------------");
    System.out.println(proxy.get1());
    System.out.println("---------------");
    System.out.println(proxy.get2());
}

运行结果

---------------
我是insert1
public void com.javacode2018.lesson001.demo17.Service4.insert1(),耗时(纳秒):9777500
---------------
我是insert2
public void com.javacode2018.lesson001.demo17.Service4.insert2(),耗时(纳秒):50600
---------------
路人甲Java
---------------
路人甲Java

案例7:实现通用的统计任意类方法耗时代理类

public class CostTimeProxy implements MethodInterceptor {
    //目标对象
    private Object target;
    public CostTimeProxy(Object target) {
        this.target = target;
    }
    
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long starTime = System.nanoTime();
        //调用被代理对象(即target)的方法,获取结果
        Object result = method.invoke(target, objects);
        long endTime = System.nanoTime();
        System.out.println(method + ",耗时(纳秒):" + (endTime - starTime));
        return result;
    }
    
    /**
     * 创建任意类的代理对象
     *
     * @param target
     * @param <T>
     * @return
     */
    public static <T> T createProxy(T target) {
        CostTimeProxy costTimeProxy = new CostTimeProxy(target);
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(costTimeProxy);
        enhancer.setSuperclass(target.getClass());
        return (T) enhancer.create();
    }
}

使用非常简单,来个测试用例,如下:

@Test
public void test7() {
    //创建Service1代理
    Service1 service1 = CostTimeProxy.createProxy(new Service1());
    service1.m1();
    //创建Service3代理
    Service3 service3 = CostTimeProxy.createProxy(new Service3());
    System.out.println(service3.m1());
}

运行结果

我是m1方法
public void com.javacode2018.lesson001.demo17.Service1.m1(),耗时(纳秒):53200
我是m1方法
public java.lang.String com.javacode2018.lesson001.demo17.Service3.m1(),耗时(纳秒):49200
hello:m1

十五、深入理解java注解

1、定义注解

public @interface MyAnnotation {}

注解中定义参数

public @interface 注解名称{
	[public] 参数类型 参数名称1() [default 参数默认值];
	[public] 参数类型 参数名称2() [default 参数默认值];
	[public] 参数类型 参数名称n() [default 参数默认值];
}
注解中可以定义多个参数,参数的定义有以下特点:
  1. 访问修饰符必须为public,不写默认为public。

  2. 元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组。

  3. 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作)。

  4. 参数名称后面的 () 不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法。

  5. default 代表默认值,值必须和第2点定义的类型一致。

  6. 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。

2、指定注解的使用范围:@Target

@Target(value = {ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {
}

看一下 @Target 源码

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
	ElementType[] value();
}

有一个参数value,是ElementType类型的一个数组,再来看一下 ElementType ,是个枚举,源码如下:

package java.lang.annotation;
/*注解的使用范围*/
public enum ElementType {
    /*类、接口、枚举、注解上面*/
    TYPE,
    /*字段上*/
    FIELD,
    /*方法上*/
    METHOD,
    /*方法的参数上*/
    PARAMETER,
    /*构造函数上*/
    CONSTRUCTOR,
    /*本地变量上*/
    LOCAL_VARIABLE,
    /*注解上*/
    ANNOTATION_TYPE,
    /*包上*/
    PACKAGE,
    /*类型参数上*/
    TYPE_PARAMETER,
    /*类型名称上*/
    TYPE_USE
}

3、指定注解的保留策略:@Retention

我们先来看一下java程序的3个过程
  1. 源码阶段。

  2. 源码被编译为字节码之后变成class文件。

  3. 字节码被虚拟机加载然后运行。

那么自定义注解会保留在上面哪个阶段呢?可以通过 @Retention 注解来指定,如:
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnnotation {
}

上面指定了 MyAnnotation 只存在于源码阶段,后面的2个阶段都会丢失。

来看一下@Retention源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
	RetentionPolicy value();
}

有一个value参数,类型为RetentionPolicy枚举,如下:

public enum RetentionPolicy {
    /*注解只保留在源码中,编译为字节码之后就丢失了,也就是class文件中就不存在了*/
    SOURCE,
    /*注解只保留在源码和字节码中,运行阶段会丢失*/
    CLASS,
    /*源码、字节码、运行期间都存在*/
    RUNTIME
}

4、为参数指定默认值

通过default为参数指定默认值,用的时候如果没有设置值,则取默认值,没有指定默认值的参数,使用的时候必须为参数设置值,如
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Ann5 {
    String[] name() default {"路人甲java", "spring系列"};
    int[] score() default 1; 
    int age() default 30; 
    String address(); 
}

5、注解信息的获取:AnnotatedElement

为了运行时能准确获取到注解的相关信息,Java在 java.lang.reflect 反射包下新增了AnnotatedElement 接口,它主要用于表示目前正在虚拟机中运行的程序中已使用注解的元素,通过该接口提供的方法可以利用反射技术地读取注解的信息,看一下UML图:

image.png

  1. Package:用来表示包的信息。

  2. Class:用来表示类的信息。

  3. Constructor:用来表示构造方法信息。

  4. Field:用来表示类中属性信息。

  5. Method:用来表示方法信息。

  6. Parameter:用来表示方法参数信息。

  7. TypeVariable:用来表示类型变量信息,如:类上定义的泛型类型变量,方法上面定义的泛型类型变量。

6、AnnotatedElement常用方法

// 该元素如果存在指定类型的注解,则返回这些注解,否则返回 null。
<A extends Annotation> getAnnotation(Class<A> annotationClass);

// 返回此元素上存在的所有注解,包括从父类继承的注解
Annotation[] getAnnotations();

// 如果指定类型的注解存在于此元素上,则返回true,否则返回 false
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass);

// 返回直接存在于此元素上的所有注解,注意,不包括父类的注解,调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响,没有则返回长度为0的数组
Annotation[] getDeclaredAnnotations();

7、案例

@Target({ElementType.PACKAGE,
        ElementType.TYPE,
        ElementType.FIELD,
        ElementType.CONSTRUCTOR,
        ElementType.METHOD,
        ElementType.PARAMETER,
        ElementType.TYPE_PARAMETER,
        ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Ann11 {
    String value();
}

@Target({ElementType.PACKAGE,
        ElementType.TYPE,
        ElementType.FIELD,
        ElementType.CONSTRUCTOR,
        ElementType.METHOD,
        ElementType.PARAMETER,
        ElementType.TYPE_PARAMETER,
        ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Ann11_0 {
    int value();
}


@Ann11("用在了类上")
@Ann11_0(0)
public class UseAnnotation11<@Ann11("用在了类变量类型V1上") @Ann11_0(1) V1, @Ann11("用在了类变量类型V2上") @Ann11_0(2) V2> {

    @Ann11("用在了字段上")
    @Ann11_0(3)
    private String name;

    private Map<@Ann11("用在了泛型类型上,String") @Ann11_0(4) String, @Ann11("用在了泛型类型上,Integer") @Ann11_0(5) Integer> map;

    @Ann11("用在了构造方法上")
    @Ann11_0(6)
    public UseAnnotation11() {
        this.name = name;
    }

    @Ann11("用在了返回值上")
    @Ann11_0(7)
    public String m1(@Ann11("用在了参数上") @Ann11_0(8) String name) {
        return null;
    }
}

解析类上的注解

// 解析类上的注解
for (Annotation annotation : UseAnnotation11.class.getAnnotations()) {
	System.out.println(annotation);
}

解析类上的类型变量

解析类名后面的尖括号的部分,即下面的部分:
UseAnnotation11<@Ann11("用在了类变量类型V1上") @Ann11_0(1) V1, @Ann11("用在了类变量类型V2上") @Ann11_0(2) V2>
// 解析类上的类型变量
TypeVariable<Class<UseAnnotation11>>[] typeParameters = UseAnnotation11.class.getTypeParameters();

for (TypeVariable<Class<UseAnnotation11>> typeParameter : typeParameters) {
    System.out.println(typeParameter.getName() + "变量类型注解信息:");

    Annotation[] annotations = typeParameter.getAnnotations();
    for (Annotation annotation : annotations) {
        System.out.println(annotation);
    }
}

解析字段name上的注解

// 解析字段name上的注解
Field nameField = UseAnnotation11.class.getDeclaredField("name");
for (Annotation annotation : nameField.getAnnotations()) {
    System.out.println(annotation);
}

解析泛型字段map上的注解

// 解析泛型字段map上的注解
Field field = UseAnnotation11.class.getDeclaredField("map");
Type genericType = field.getGenericType();
Type[] actualTypeArguments = ((ParameterizedType) genericType).getActualTypeArguments();
AnnotatedType annotatedType = field.getAnnotatedType();
AnnotatedType[] annotatedActualTypeArguments = ((AnnotatedParameterizedType) annotatedType).getAnnotatedActualTypeArguments();
int i = 0;
for (AnnotatedType actualTypeArgument : annotatedActualTypeArguments) {
    Type actualTypeArgument1 = actualTypeArguments[i++];
    System.out.println(actualTypeArgument1.getTypeName() + "类型上的注解如下:");
    for (Annotation annotation : actualTypeArgument.getAnnotations()) {
        System.out.println(annotation);
    }
}

解析构造函数上的注解

// 解析构造函数上的注解
Constructor<?> constructor = UseAnnotation11.class.getConstructors()[0];
for (Annotation annotation : constructor.getAnnotations()) {
    System.out.println(annotation);
}

解析m1方法上的注解

// 解析m1函数上的注解
Method method = UseAnnotation11.class.getMethod("m1", String.class);
for (Annotation annotation : method.getAnnotations()) {
    System.out.println(annotation);
}

解析m1方法参数注解

// 解析m1方法参数注解
method = UseAnnotation11.class.getMethod("m1", String.class);
for (Parameter parameter : method.getParameters()) {
    System.out.println(String.format("参数%s上的注解如下:", parameter.getName()));
    for (Annotation annotation : parameter.getAnnotations()) {
        System.out.println(annotation);
    }
}

上面参数名称为arg0,如果想让参数名称和源码中真实名称一致,操作如下:

如果你编译这个class的时候没有添加参数–parameters,运行的时候你会得到这个结果:Parameter: arg0
编译的时候添加了–parameters参数的话,运行结果会不一样:Parameter: args
对于有经验的Maven使用者,–parameters参数可以添加到maven-compiler-plugin的配置部分:
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
        <compilerArgument>-parameters</compilerArgument>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>

8、@Inherit:实现类之间的注解继承

来看一下这个注解的源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
我们通过@Target元注解的属性值可以看出,这个@Inherited 是专门修饰注解的。
作用:让子类可以继承父类中被@Inherited修饰的注解,注意是继承父类中的,如果接口中的注解也使用@Inherited修饰了,那么接口的实现类是无法继承这个注解的。
public class InheritAnnotationTest {
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @interface A1{ //@1
    }
    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @interface A2{ //@2
    }
    
    @A1 //@3
    interface I1{}
    
    @A2 //@4
    static class C1{}
    
    static class C2 extends C1 implements I1{} //@5
    
    public static void main(String[] args) {
        for (Annotation annotation : C2.class.getAnnotations()) { //@6
       		System.out.println(annotation);
        }
    }
}
@1:定义了一个注解A1,上面使用了@Inherited,表示这个具有继承功能
@2:定义了一个注解A2,上面使用了@Inherited,表示这个具有继承功能
@3:定义接口I1,上面使用了@A1注解
@4:定义了一个C1类,使用了A2注解
@5:C2继承了C1并且实现了I1接口
@6:获取C2上以及从父类继承过来的所有注解,然后输出
运行输出:
@com.javacode2018.lesson001.demo18.InheritAnnotationTest$A2()
从输出中可以看出类可以继承父类上被@Inherited修饰的注解,而不能继承接口上被@Inherited修饰的注解,这个一定要注意。

9、@Repeatable重复使用注解

先定义容器注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@interface Ann12s {
	Ann12[] value(); //@1
}
容器注解中必须有个value类型的参数,且参数类型为子注解类型的数组。

为注解指定容器

要让一个注解可以重复使用,需要在注解上加上@Repeatable注解,@Repeatable中value的值为容器注解,如下代码中的@2
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.FIELD})
@Repeatable(Ann12s.class)//@2
@interface Ann12 {
	String name();
}

使用注解

重复使用相同的注解有2种方式,如下面代码
  1. 重复使用注解,如下面的类上重复使用@Ann12注解。

  2. 通过容器注解来使用更多个注解,如下面的字段v1上使用@Ann12s容器注解。

@Ann12(name = "路人甲Java")
@Ann12(name = "Spring系列")
public class UseAnnotation12 {
    @Ann12s({@Ann12(name = "Java高并发系列,见公众号"),@Ann12(name = "mysql高手系列,见公众号")})
    private String v1;
}
Annotation[] annotations = UseAnnotation12.class.getAnnotations();
for (Annotation annotation : annotations) {
	System.out.println(annotation);
}

System.out.println("-------------");
Field v1 = UseAnnotation12.class.getDeclaredField("v1");
Annotation[] declaredAnnotations = v1.getDeclaredAnnotations();
for (Annotation declaredAnnotation : declaredAnnotations) {
	System.out.println(declaredAnnotation);
}


参考:路人甲-Spring系列

版权声明

非特殊说明,本文由Zender原创或收集发布,欢迎转载。

上一篇:Spring系列(1-10) 下一篇:Spring系列(16-19)

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

作者文章
热门