一、自定义注解的基本使用
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 的子类。
编写规则
- Annotation 型定义为@interface, 所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
- 参数成员只能用public 或默认(default) 这两个访问权修饰
- 参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
- 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法
- 注解也可以没有定义成员,,不过这样注解就没啥用了
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
基本概念:
- 连接点:类中的所有方法
- 切入点:被抽取了共性功能的方法。(理解:共性功能被抽取成独立的模块,原来的方法的业务逻辑就不完整了,引入切入点,将独立的模块放回去执行,使整个功能完整)切入点一定是连接点,连接点不一定是切入点
- 通知:抽取的共性功能组成的代码逻辑(方法),将多个通知方法封装成通知类
- 通知类型:在切入点方法的位置
- @Before前置通知、
- @After后置通知-无论方法是否出异常都会执行、
- @Around环绕通知-在方法执行前后执行 有异常 末尾的通知不会执行、
- @AfterReturning返回后通知-必须方法正常返回后执行、
- @AfterThrowing抛出异常后通知-必须方法出异常后执行
- 目标对象:抽取共性功能后,原始的方法已经不完整了,此时的包含切入点的类对象被称为目标对象
- AOP代理:执行切入点方法时,将目标对象和通知类中的通知方法还原,通过代理的形式创建类对象,并完成共性功能的加入
- 切面:切入点和通知的匹配模式
五、编写实例
定义一个注解类接口
添加四个元注解
定义属性-返回类型类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,否则目标方法端是拿不到返回值的