8.5.java 反射/注解/动态代理

275 阅读6分钟

1.反射

  • 通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。

1.1 应用场景

  • 注解
  • 动态代理

1.2 获取 Class 对象

// 1.知道具体类的情况下可以使用:
Class alunbarClass = TargetObject.class
// 2.通过 `Class.forName()`传入类的路径获取:
Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
// 3.通过对象实例`instance.getClass()`获取:
TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();
// 4.通过类加载器`xxxClassLoader.loadClass()`传入类路径获取:
Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");
    //(通过类加载器获取 Class 对象不会进行初始化)

1.3 反射基本操作

package cn.javaguide;

public class TargetObject {
    private String value;

    public TargetObject() {
        value = "JavaGuide";
    }

    public void publicMethod(String s) {
        System.out.println("I love " + s);
    }

    private void privateMethod() {
        System.out.println("value is " + value);
    }
}


// 使用反射操作 TargetObject 类的方法以及参数

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
    
        /** 获取TargetObject类的Class对象并且创建TargetObject类实例 **/
        Class<?> tagetClass = Class.forName("cn.javaguide.TargetObject");
        TargetObject targetObject = (TargetObject) tagetClass.newInstance();
        
        /** 获取所有类中所有定义的方法 **/
        Method[] methods = tagetClass.getDeclaredMethods();
        for (Method method : methods) {
            System.out.println(method.getName());
        }
        
        /** 获取指定方法并调用 **/
        Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",
                String.class);
        publicMethod.invoke(targetObject, "JavaGuide");
        
        /*** 获取指定参数并对参数进行修改*/
        Field field = tagetClass.getDeclaredField("value");
        
        //为了对类中的参数进行修改我们取消安全检查
        field.setAccessible(true);
        field.set(targetObject, "JavaGuide");
        
        /*** 调用 private 方法*/
        Method privateMethod = tagetClass.getDeclaredMethod("privateMethod");
        
        //为了调用private方法我们取消安全检查
        privateMethod.setAccessible(true);
        privateMethod.invoke(targetObject);
    }
}

JAVA controller实现原理

474e580501158a00867d4e9db7f08e5.jpg

2. 注解

注释是应用于类、接口、方法或字段的元数据,以便向编译器提供有关程序的附加信息
注释可用于将元数据添加到代码中,通常出现在方法或变量定义之前。注释不会修改程序的结构或其行为,相反,它们允许你轻松地在源代码中添加有关元素的上下文信息

2.1 注解定义

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    public String info() default "MyAnnotation info"; //default后的是默认值
}
  • @Target用来定义注解可用在什么地方,可选值有(同时使用多各值用逗号分隔):
    ElementType.Type            类型声明(类,接口,注解,枚举)
    ElementType.METHOD          方法声明;
    ElementType.FIELD           属性,包括枚举的值;
    ElementType.PARAMETER       参数(形参);
    ElementType.CONSTRUCTOR     构造器;
    ElementType.ANNOTATION_TYPE 注解定义;
    ElementType.PACKAGE         包;
    ElementType.LOCAL_VARIABLEnd;局部变量声明;
    ElementType.TYPE_PARAMETER; 类型参数声明;java 1.8引入
    ElementType.TYPE_USE;       类型使用;java 1.8引入
  • @Retention 定义在哪一个级别可用,值有:
  RetentionPolicy.SOURCE      注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃
  RetentionPolicy.CLASS       注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期
  RetentionPolicy.RUNTIME     注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
  • @Documented 将此注解包含在javadoc中
  • @Inherited 允许子类继承父类的注解

同时Java还内置了三种标准注解

 @Override           定义重写超类中的方法,如果拼写错了,或者方法签名对不上被覆盖的方法,编译器会报错
 @Depreceted         弃用,如果使用了带有此注解的元素,编译器会发出警告;
 @SuppressWarnings   关闭警告信息

2.2 Spring注解处理器

@Aspect
@Component
public class AopAnnotationHandler {
    //要处理的切面,此处为要处理的注解MyAnnotation
    @Pointcut("@annotation(mypackage.springboot.MyAnnotation)")
    public void pointname(){ }

    //处理切面前的操作
    @Before("pointname()")
    public void beforeAno(JoinPoint joinPoint){
        System.out.println("befroe pointcut");
        Class<?> clazz = joinPoint.getTarget().getClass();
        System.out.println(clazz.getAnnotation(MyAnnotation.class).info());
    }
    //处理切面后的操作
    @After("pointname()")
    public void afterAno(JoinPoint joinPoint){
        try {
            System.out.println("after pointcut");
            //获取切面方法
            Method method = joinPoint.getTarget().getClass()
                    .getMethod(joinPoint.getSignature().getName(), ((MethodSignature)joinPoint.getSignature()).getParameterTypes());
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }}
@Aspect         切面声明,标注在类、接口(包括注解类型)或枚举上,把当前类标识为一个切面供容器读取     
@Pointcut       切入点声明,即切入到哪些目标类的目标方法,Pointcut是植入Advice的触发条件。每个Pointcut的定义包括2部分,一是表达式,二是方法签名。方法签名必须是 public及void型。可以将Pointcut中的方法看作是一个被Advice引用的助记符,因为表达式不直观,因此我们可以通过方法签名的方式为 此表达式命名。因此Pointcut中的方法只需要方法签名,而不需要在方法体内编写实际代码。
@Before         前置通知, 在目标方法(切入点)执行之前执行。value 属性绑定通知的切入点表达式,可以关联切入点声明,也可以直接设置切入点表达式。注意:如果在此回调方法中抛出异常,则目标方法不会再执行,会继续执行后置通知 -> 异常通知
@Around         环绕增强,相当于MethodInterceptor,目标方法执行前后分别执行一些代码,发生异常的时候执行另外一些代码
@AfterReturning 后置增强,相当于AfterReturningAdvice,方法正常退出时执行
@AfterThrowing  异常抛出增强,相当于ThrowsAdvice
@After          final增强,不管是抛出异常或者正常退出都会执行

3. 动态代理

代理对象来代替对真实对象(real object)的访问,这样就可以在不修改原目标对象的前提下,提供额外的功能操作,扩展目标对象的功能

静态代理 vs 动态代理

  • 灵活性 :动态代理更加灵活,不需要必须实现接口,可以直接代理实现类,并且可以不需要针对每个目标类都创建一个代理类。另外,静态代理中,接口一旦新增加方法,目标对象和代理对象都要进行修改,这是非常麻烦的!

  • JVM 层面 :静态代理在编译时就将接口、实现类、代理类这些都变成了一个个实际的 class 文件。而动态代理是在运行时动态生成类字节码,并加载到 JVM 中的

3.1. JDK 动态代理机制

1.定义发送短信的接口

public interface SmsService {
    String send(String message);
}

2.实现发送短信的接口

public class SmsServiceImpl implements SmsService {
    public String send(String message) {
        System.out.println("send message:" + message);
        return message;
    }
}

3.定义一个 JDK 动态代理类

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class DebugInvocationHandler implements InvocationHandler {
    /**
     * 代理类中的真实对象
     */
    private final Object target;

    public DebugInvocationHandler(Object target) {
        this.target = target;
    }


    public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object result = method.invoke(target, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return result;
    }
}

4.获取代理对象的工厂类

public class JdkProxyFactory {
    public static Object getProxy(Object target) {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(), // 目标类的类加载
                target.getClass().getInterfaces(),  // 代理需要实现的接口,可指定多个
                new DebugInvocationHandler(target)   // 代理对象对应的自定义 InvocationHandler
        );
    }
}

5.实际使用

SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl());
smsService.send("java");

3.2 CGLIB 动态代理

JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免

1.实现一个使用阿里云发送短信的类

public interface SmsService {
    String send(String message);
}

2.自定义 MethodInterceptor(方法拦截器)

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class DebugMethodInterceptor implements MethodInterceptor {
    /**
     * @param o           代理对象(增强的对象)
     * @param method      被拦截的方法(需要增强的方法)
     * @param args        方法入参
     * @param methodProxy 用于调用原始方法
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //调用方法之前,我们可以添加自己的操作
        System.out.println("before method " + method.getName());
        Object object = methodProxy.invokeSuper(o, args);
        //调用方法之后,我们同样可以添加自己的操作
        System.out.println("after method " + method.getName());
        return object;
    }

}

3.获取代理类

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyFactory {

    public static Object getProxy(Class<?> clazz) {
        // 创建动态代理增强类
        Enhancer enhancer = new Enhancer();
        // 设置类加载器
        enhancer.setClassLoader(clazz.getClassLoader());
        // 设置被代理类
        enhancer.setSuperclass(clazz);
        // 设置方法拦截器
        enhancer.setCallback(new DebugMethodInterceptor());
        // 创建代理类
        return enhancer.create();
    }
}

4.实际使用

AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class);
aliSmsService.send("java");

参考: javaguide.cn/java/basis/…