理解反射、动态代理、AOP(用注解实现方法开关)

一、自定义注解的基本使用

java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented – 注解是否将包含在JavaDoc中
@Retention – 什么时候使用该注解
@Target – 注解用于什么地方
@Inherited – 是否允许子类继承该注解

1.)@Retention – 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括
● ElementType.CONSTRUCTOR: 用于描述构造器
● ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE: 用于描述局部变量
● ElementType.METHOD: 用于描述方法
● ElementType.PACKAGE: 用于描述包
● ElementType.PARAMETER: 用于描述参数
● ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

3.)@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。

4.)@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。

编写规则

  1. Annotation 型定义为@interface, 所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
  2. 参数成员只能用public 或默认(default) 这两个访问权修饰
  3. 参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法
  5. 注解也可以没有定义成员,,不过这样注解就没啥用了
    PS:自定义注解需要使用到元注解

示例

在这里插入图片描述

AOP

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用

在这里插入图片描述

执行结果

在这里插入图片描述

二、Java反射

定义:反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

1. 反射获取类(3种方法)

Class clz = Class.forName("类的路径");
Class clz = 类名.class;
Class clz = 类对象名.getClass();

2. 反射创建类对象(2种方法)

Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();

3. 反射获取属性、方法

Class clz = Apple.class;
Apple appleobj = (Apple)clz.newInstance();
// getDeclaredFields包括私有属性,getFields不包括私有属性
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

Method method = clz.getMethod("setPrice", int.class); 
method.invoke(appleobj, 4);

三、代理模式

对象本身不做实际的操作,通过其他对象得到自己想要的结果
目标对象只关心自己的实现细节,通过代理对象来实现功能的增强,可以扩展目标对象的功能。

1. 编程思想

不能随便修改源码,通过修改代理的方式实现功能的拓展

2. 元素组成

  • 接口:定义行为和规范
  • 被代理类:目标对象
  • 代理类:功能增强

3. 静态代理

缺点:

  • 不利于代码的拓展(新添加一个接口方法时,实现类和代理类都要去重写这个方法,否则报错)
  • 代理对象需要创建很多

4. 动态代理

在不改变原有功能代码的前提下,能够动态的实现方法的增强
即运行时动态生成代理类
在这里插入图片描述

动态代理的应用:spring aop、hibernate 数据查询、测试框架的后端 mock、rpc,Java注解对象获取等。

4.1 JDK动态代理:反射机制

代码结构:

在这里插入图片描述

实现InvocationHandler接口的对象

package com.flora.test.designPattern.structurePattern.proxy.dongtai.JDK.handler;


import com.flora.test.designPattern.structurePattern.proxy.dongtai.JDK.transaction.DaoTransaction;

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

/**
 * @Author qinxiang
 * @Date 2023/6/23-上午1:07
 * 实现InvocationHandler接口
 */
public class TransactionHandler implements InvocationHandler {
    // 增强类对象
    private DaoTransaction transaction;
    // 需要被代理的目标对象
    private Object obj;
    public TransactionHandler(DaoTransaction transaction,Object obj){
        this.transaction = transaction;
        this.obj = obj;
    }
    // proxy 代理实例 通过newProxyInstance()创建代理实例
    // Method 执行目标方法 通过invoke()方法执行
    // args 参数数组
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object ret = null;
        // 判断当前方法是否是save方法,是才做事务拦截
        if ("save".equals(method.getName())){
            transaction.before();
            ret = method.invoke(obj,args);
            transaction.after();
        }else {
            ret = method.invoke(obj,args);
        }
        return ret;
    }
}

测试类:

package com.flora.test.designPattern.structurePattern.proxy.dongtai.JDK;


import com.flora.test.designPattern.structurePattern.proxy.dongtai.JDK.handler.TransactionHandler;
import com.flora.test.designPattern.structurePattern.proxy.dongtai.JDK.service.IStudentService;
import com.flora.test.designPattern.structurePattern.proxy.dongtai.JDK.service.impl.StudentServiceImpl;
import com.flora.test.designPattern.structurePattern.proxy.dongtai.JDK.transaction.DaoTransaction;
import org.junit.Test;

import java.lang.reflect.Proxy;


/**
 * @Author qinxiang
 * @Date 2023/6/23-上午1:24
 */
public class TestStudent {
    @Test
    public void testStudent() throws Throwable {
        // 目标执行类
        IStudentService studentService = new StudentServiceImpl();
        // 增强类对象-对应前置通知和后置通知
        DaoTransaction daoTransaction = new DaoTransaction();
        // 方法拦截处理器
        TransactionHandler transactionHandler = new TransactionHandler(daoTransaction, studentService);
        // 获取代理实例对象-对应静态代理中的ProxyStudent类
        IStudentService proxyInstance = (IStudentService) Proxy.newProxyInstance(StudentServiceImpl.class.getClassLoader(), StudentServiceImpl.class.getInterfaces(), transactionHandler);

        //transactionHandler.invoke(proxyInstance,StudentServiceImpl.class.getMethod("save"),null);
        proxyInstance.save();


    }


}

原理分析:基于接口实现

首先获取代理实例对象
代理实例对象继承接口,通过反射拿到方法,去自定义的方法拦截器,里面有invoke方法,通过代理对象的method去自定义的方法拦截器执行,如果是save方法则会执行增强类对象的before以及after方法,并拿到返回结果

  • 通过实现接口,获取到接口里面的所有方法
  • 通过proxy创建代理类实例,
    在这里插入图片描述

4.2 CGLIB动态代理:

code generator library
动态生成字节码对象-在编译的过程中将字节码对象加入

代码结构:

在这里插入图片描述

实现MethodInterceptor接口的对象

package com.flora.test.designPattern.structurePattern.proxy.dongtai.CGLIB.interceptor;

import com.flora.test.designPattern.structurePattern.proxy.dongtai.CGLIB.transaction.DaoTransaction;
import com.flora.test.designPattern.structurePattern.proxy.dongtai.JDK.service.IStudentService;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
 * @Author qinxiang
 * @Date 2023/6/23-下午10:23
 */
public class CglibInterceptor implements MethodInterceptor {
    private DaoTransaction transaction;
    public CglibInterceptor(DaoTransaction transaction){
        this.transaction = transaction;
    }
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        transaction.before();
        // 执行被拦截的方法 被增强的方法
        Object ret = methodProxy.invokeSuper(o, objects);
        transaction.after();
        return ret;
    }
}

测试类:

package com.flora.test.designPattern.structurePattern.proxy.dongtai.CGLIB;

import com.flora.test.designPattern.structurePattern.proxy.dongtai.CGLIB.interceptor.CglibInterceptor;
import com.flora.test.designPattern.structurePattern.proxy.dongtai.CGLIB.service.IStudentService;
import com.flora.test.designPattern.structurePattern.proxy.dongtai.CGLIB.service.impl.StudentServiceImpl;
import com.flora.test.designPattern.structurePattern.proxy.dongtai.CGLIB.transaction.DaoTransaction;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import org.junit.Test;

/**
 * @Author qinxiang
 * @Date 2023/6/23-下午10:36
 */
public class TestCglib {
    @Test
    public void testCglib(){
        // 生成目标代理类文件-仅用于分析
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/Users/qinxiang/Documents/LeetCode/src/main/java/com/flora/test/designPattern/structurePattern/proxy/dongtai/CGLIB/");
        // 得到方法拦截器
        CglibInterceptor cglibInterceptor = new CglibInterceptor(new DaoTransaction());
        // 使用CGLIB框架生成目标类的子类(代理类)实现增强-
        // 生成空的字节码对象enhancer
        Enhancer enhancer = new Enhancer();
        // 设置父类字节码
        enhancer.setSuperclass(StudentServiceImpl.class);
        // 设置拦截处理
        enhancer.setCallback(cglibInterceptor);
        // 用代理类创建代理对象
        IStudentService proxyStudentService = (IStudentService) enhancer.create();
        proxyStudentService.save();// 执行结果: 开启事务 保存学生信息 关闭事务

    }
}

原理分析:基于继承当前类的子类实现

在这里插入图片描述

  • 通过继承的方式获取目标对象的方法
  • 通过传递方法拦截器MethodInterceptor实现方法拦截,在这里面做具体的增强
  • 调用生成的代理类对象具体执行重写的save方法,直接去调用方法拦截器里面的intercept方法
  • 前后加上了增强操作,实现不修改目标代码实现业务增强
    在这里插入图片描述

4.3 JDK与CGLIB对比

在这里插入图片描述

四、AOP

基本概念:

  1. 连接点:类中的所有方法
  2. 切入点:被抽取了共性功能的方法。(理解:共性功能被抽取成独立的模块,原来的方法的业务逻辑就不完整了,引入切入点,将独立的模块放回去执行,使整个功能完整)切入点一定是连接点,连接点不一定是切入点
  3. 通知:抽取的共性功能组成的代码逻辑(方法),将多个通知方法封装成通知类
  4. 通知类型:在切入点方法的位置
  • @Before前置通知、
  • @After后置通知-无论方法是否出异常都会执行、
  • @Around环绕通知-在方法执行前后执行 有异常 末尾的通知不会执行、
  • @AfterReturning返回后通知-必须方法正常返回后执行、
  • @AfterThrowing抛出异常后通知-必须方法出异常后执行
  1. 目标对象:抽取共性功能后,原始的方法已经不完整了,此时的包含切入点的类对象被称为目标对象
  2. AOP代理:执行切入点方法时,将目标对象和通知类中的通知方法还原,通过代理的形式创建类对象,并完成共性功能的加入
  3. 切面:切入点和通知的匹配模式

五、编写实例

定义一个注解类接口

添加四个元注解
定义属性-返回类型类Class<?>、返回类型是否可为空boolean-可以赋默认值default false

定义一个切面

添加注解@Aspect @Component
定义一个切面方法,返回值为void,添加注解@Pointcut(“@annotation(注解接口的全类名路径)”),
定义一个环绕方法,参数为ProceedingJoinPoint,注解接口类,添加@Around(切面方法() && @annotation(参数注解接口类的自定义名称)),内部实现:获得切点的方法名和类名,通过注入的配置类获取配置的值,如果为Y则return point.proceed(),表示进入方法的执行,如果注解接口类的boolean属性为true,则返回null,获取注解接口类的返回类型类,return clazz.newInstance;

定义一个配置类

添加注解@Component @RefreshScope @Data
定义私有属性 类名 方法名,添加添加注解 @ V a l u e ( " 方法名,添加添加注解@Value(" 方法名,添加添加注解@Value("{类名.方法名}")
提供一个get方法,参数为类名和方法名
return 配置工具类.getValueByKeyAndObj(className,methodName,this)

定义一个配置工具类

提供一个静态的getValueByKeyAndObj方法,参数为(String key,String value,Object objc)

String str = key + "$" + value;
Fields[] declaredFields = objc.getClass().getDeclaredFields();
for (Field declaredField: declaredFields) {
boolean equals = str. equals (declaredField. getName ());
if (equals)
try {
declaredField.setAccessible(true);
return declaredField.get (objc);
} catch (IllegalAccessException e) {
return null;

使用注解

@JudgeCallable(resultClass = ArrayList.class, isNull = false)

注意只能用到control类以及impl实现类上,不能用于接口上
其中resultClass只能用List的实现类ArrayList,不能使用接口类

思考:为什么自定义的注解不能用于接口上?

前置知识点:注解的继承问题
“对于接口,在接口中的注解无论如何都不能被继承,不论是子接口继承父接口的情况还是接口的实现类的情况,不论是对接口上还是接口中的方法上的注解,都不能被继承。

一个类用上了 @Inherited 修饰的注解,那么其子类也会继承这个注解,与方法上的注解的继承性无关

总结:

接口用上个 @Inherited 修饰的注解,其实现类不会继承这个注解
父类的方法用了 @Inherited 修饰的注解,子类也不会继承这个注解

为什么自定义的注解不能用于接口上?
因为该注解结合了切面实现,在Java中,AOP(面向切面编程)通常通过代理来实现,而代理的生成方式又分为两种:基于接口的代理(JDK动态代理)和基于类的代理(CGLIB动态代理)。

  • 基于接口的代理(JDK动态代理),该方式基于反射的机制实现,会生成一个实现相同接口的代理类,然后通过对方法的重写,实现对代码的增强。
    在该方式中接口中的注解无法被实现类继承,AOP 中的切点无法匹配上实现类,所以也就不会为实现类创建代理,所以我们使用的类其实是未被代理的原始类,自然也就不会被增强了。
  • 基于类的代理(CGLIB动态代理),在不存在切点注解继承的情况下(在类上注解,子类可继承),AOP 可进行有效拦截。
    但是还要考虑以下存在注解继承的情况:
    有父类 Parent和子类 Sub ,切点注解在父类方法被重写的方法(不继承父类的注解)将不会被拦截,而未重写的方法(继承父类的注解)则走 Parent 路线,可以被 AOP 感知拦截。

小tips

使用AOP切面时,如果切面的目标方法有返回值,那么环绕方法也必须有返回值Object,对应的point.proceed()需要加return,否则目标方法端是拿不到返回值的

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值