前言
花了两个星期处理了毕业、论文以及答辩相关事项,今天回顾了一下动态代理,看了一些文章,写文章记录一下,顺便记录一下之前给项目做的时间线操作记录的aop优化。
静态代理
准备工作
先实现一个接口IHello和一个实现类HelloImpl
/**
* @author zhn
*/
public interface IHello {
/**
* sayHello
*/
void sayHello();
}
复制代码
/**
* @author zhn
*/
public class HelloImpl implements IHello {
/**
* sayHello
*/
@Override
public void sayHello() {
System.out.println("Hello!!");
}
}
复制代码
实现Handler
静态代理主要实现Handler实现IHello接口,在Handler方法中定义一个代理对象。
在调用接口方法的时候调用代理对象中的方法,同时其中可以在调用代理对象前增加其他操作。
/**
* @author zhn
*/
public class Handler implements IHello {
//代理的目标
private IHello target;
public Handler(IHello target){
this.target = target;
}
/**
* sayHello
*/
@Override
public void sayHello() {
//前
System.out.println("代理开始");
//调用代理对象中的方法
target.sayHello();
//后
System.out.println("代理完了");
}
}
复制代码
使用静态代理
用main方法来示例,首先创建需要代理的对象,然后把该代理对象传入Handler(即代理)中,调用Handler(代理)中的方法。
/**
* @author zhn
*/
public class Main {
public static void main(String[] args) {
HelloImpl hello = new HelloImpl();
Handler handler = new Handler(hello);
handler.sayHello();
}
}
复制代码
结果和总结
静态代理的实现很简单,但也很容易看出缺点,即当需要代理的类很多事就会需要很多个代理类。
动态代理
准备工作
还是创建一个接口和一个实现类,这里使用静态代理中的准备工作中的类。
创建MyInvocationHandler
实现InvocationHandler接口,实现自己的Handler
/**
* @author zhn
*/
public class MyInvocationHandler implements InvocationHandler {
//代理目标
private Object target;
//构造传入
public MyInvocationHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置");
//反射调用
Object res = method.invoke(target, args);
System.out.println("后置");
return res;
}
}
复制代码
和静态代理很像,核心都是在实现代理对象的方法基础上扩展实现其他方法,这里使用的是反射。
使用动态代理
同样用main方法示例,一共有两种方式
方式一:
/**
* @author zhn
*/
public class MyProxyTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//第一种
//将JDK动态代理生成的class文件保存到本地 生成$proxy0的class文件
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//1.获取动态代理类
Class<?> proxyClass = Proxy.getProxyClass(IHello.class.getClassLoader(), IHello.class);
//2.获取代理类的构造函数,并传入参数类型InvocationHandler
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
//3.通过构造函数创建动态代理类对象,再把自定义的MyInvocationHandler传入
IHello iHello1 = (IHello)constructor.newInstance(new MyInvocationHandler(new HelloImpl()));
//4.通过代理对象调用目标方法
iHello1.sayHello();
}
}
复制代码
方法二:Proxy中有集成了以上1~3步骤方法 直接使用更方便
/**
* @author zhn
*/
public class MyProxyTest {
public static void main(String[] args){
//第二种方法
IHello iHello2 = (IHello)Proxy.newProxyInstance(IHello.class.getClassLoader(),
new Class[]{IHello.class},
new MyInvocationHandler(new HelloImpl()));
//调用方法
iHello2.sayHello();
}
}
复制代码
点进去看一下newProxyInstance方法
h就是我们传入的MyInvocationHandler
结果和总结
稍微总结一下,动态代理是运行时通过反射机制创建的代理。核心主要实现InvocationHandler接口,从而完成我们自己想要的操作。
CGLIB
准备工作
实例实现一个HelloService类,这里使用了Spring的cglib实现,大同小异
/**
* @author zhn
*/
public class HelloService {
public HelloService(){
System.out.println("HelloService构造器");
}
/**
* 该方法不能被子类覆盖,cglib无法代理final修饰的方法
*/
final public String sayOthers(){
System.out.println("HelloService:sayOthers");
return null;
}
public void sayHello(){
System.out.println("HelloService:sayHello");
}
}
复制代码
实现MyMethodInterceptor
实现cglib包中的MethodInterceptor接口
/**
* @author zhn
*/
public class MyMethodInterceptor implements MethodInterceptor {
/**
* o:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("前置");
//调用方法 注意是调用父类的方法
Object res = methodProxy.invokeSuper(o, objects);
System.out.println("后置");
return res;
}
}
复制代码
使用代理
这里用Test示例,主要使用Enhancer类根据其给定的父类创建子类除了被 final定义外,其中在设置回调时传入我们自己的MyMethodInterceptor,从而实现功能扩展。
@SpringBootTest
public class testMyProxy {
@Test
public void test(){
// 代理类class文件存入本地磁盘方便我们反编译查看源码
// System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 通过CGLib动态代理获取代理对象
/**
* Enhancer是cglib中使用频率很高的一个类,它是一个字节码增强器,
* 可以用来为无接口的类创建代理。它的功能与java自带的Proxy类挺相似的。
* 它会根据某个给定的类创建子类,并且所有非final的方法都带有回调钩子。
*/
Enhancer enhancer = new Enhancer();
//设置enhancer父类
enhancer.setSuperclass(HelloService.class);
//设置回调
enhancer.setCallback(new MyMethodInterceptor());
//创建代理对象
HelloService proxyObject = (HelloService) enhancer.create();
//调用方法
proxyObject.sayHello();
}
}
复制代码
结果和总结
CGLIB主要用字节码技术,通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,进行扩展。
JDK动态代理与CGLIB动态代理均是实现Spring AOP的基础。
字节码还没有深入研究过,看到了一篇美团文章贴一下,美团字节码文章,有空回过头来深入学习一下。
CGLIB中还有一个Fastclass机制,即上面提到的方法拦截技术,暂时还没有仔细研究,回头也需要更深了解一下。
注解+AOP
之前在项目中使用了注解+aop方式试着对一个类似于日志操作的时间轴功能做了优化,这边也记录一下。
准备工作
使用SpringBoot项目
导入aop依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
复制代码
创建一个注解TimeLine
/**
* @author zhn
*/
@Target({ElementType.METHOD,ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimeLine {
/** 时间轴标题 */
String subject();
/** 时间轴描述 */
String description() default "";
}
复制代码
@Target说明了 Annotation(java注解) 所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。
————————————————
版权声明:本文为CSDN博主「ZRHZRHH」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:blog.csdn.net/ZRHZRHH/art…
Retention(保留) 注解说明,这种类型的注解会被保留到那个阶段. 有三个值:
RetentionPolicy.SOURCE —— 这种类型的Annotations只在源代码级别保留,编译时就会被忽略
RetentionPolicy.CLASS —— 这种类型的Annotations编译时被保留,在class文件中存在,但JVM将会忽略
RetentionPolicy.RUNTIME —— 这种类型的Annotations将被JVM保留,所以他们能在运行时被JVM或其他使用反射机制的代码所读取和使用.
范围级别: source < class < runtime
————————————————
版权声明:本文为CSDN博主「ZRHZRHH」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:blog.csdn.net/ZRHZRHH/art…
创建TimeLineAspect
加上@Component和@Aspect两个注解
其中实现撇开其他,主要为
- 配置切点
- 选择通知方法
/**
* 配置时间轴切点
*/
@Pointcut("@annotation(com.example.test.annotation.TimeLine)")
public void pointCut(){}
/**
* 后置通知
* @param joinPoint 连接点
*/
@AfterReturning(pointcut = "pointCut()")
public void doAfter(JoinPoint joinPoint){
//处理方法
handleTimeLine(joinPoint);
}
复制代码
完整代码
/**
* @author zhn
*/
@Component
@Aspect
public class TimeLineAspect {
/**
* 配置时间轴切点
*/
@Pointcut("@annotation(com.example.test.annotation.TimeLine)")
public void pointCut(){}
/**
* 后置通知
* @param joinPoint 连接点
*/
@AfterReturning(pointcut = "pointCut()")
public void doAfter(JoinPoint joinPoint){
//处理方法
handleTimeLine(joinPoint);
}
/**
* 获取当前执行的方法
*
* @param joinPoint 连接点
* @param methodName 方法名称
* @return 方法
*/
private Method currentMethod(JoinPoint joinPoint, String methodName) {
//获取目标类的所有方法,找到当前要执行的方法
Method[] methods = joinPoint.getTarget().getClass().getMethods();
Method resultMethod = null;
for (Method method : methods) {
if (method.getName().equals(methodName)) {
resultMethod = method;
break;
}
}
return resultMethod;
}
/**
* 是否存在注解,如果存在就获取
*/
private TimeLine getAnnotationLog(Method method)
{
if (method != null)
{
return method.getAnnotation(TimeLine.class);
}
return null;
}
private void handleTimeLine(JoinPoint joinPoint){
try {
String name = joinPoint.getSignature().getName();
//获取当前方法
Method method = currentMethod(joinPoint, name);
//获取注解
TimeLine timeLine = getAnnotationLog(method);
//处理...
System.out.println("aop后置通知处理了...");
System.out.println("方法名:"+name);
System.out.println("注解信息: Subject=" + timeLine.Subject() +" ; description="+timeLine.description());
}catch (Exception e){
// 记录异常 可以加入日志记录
e.printStackTrace();
}
}
}
复制代码
使用AOP
在对应的方法中添加TimeLine注解
例如:
/**
* @author zhn
*/
@Service
public class AopTestService {
@TimeLine(Subject = "测试aop",description = "在AopTestService类中使用testAop")
public void testAop(){
System.out.println("AopTestService:testAop");
}
}
复制代码
随后只要调用该方法就会被使用TimeLineAspect中配置好的方法
结果和总结
可以看出aop的底层还是使用的代理模式,然后进行包装,最后提出了AOP面向切面编程的概念。
其中aop主要几个概念就是切点、切面、通知等。具体就不细说了。
总结
总结一下特点
静态代理最大的特点就是简单好理解
动态代理的特点就是使用反射机制
CGLIB的特点是字节码
aop的特点就是包装以上代理方法,提出aop的概念。
过几天把ElasticSearch6.8.x的实战文章写出来记录一下,然后还要忙学校的琐事(迫不及待想要毕业了)以及准备面试。