代理、反射和aop

4 minute read

静态代理 由于静态代理在代码运行之前就已经存在代理类,因此对于每一个代理对象都需要建一个代理类去代理。 当需要代理的对象很多时就需要创建很多的代理类,严重降低程序的可维护性。用动态代理就可以解决这个问题。 静态代理对于代理的角色是固定的,如 dao 层有20个 dao 类,如果要对方法的访问权限进行代理,此时需要创建20个静态代理角色,引起类爆炸,无法满足生产上的需要,于是就催生了动态代理的思想。

动态代理 动态代理是指代理类不是写在代码中,而是在运行过程中产生的。 java提供了两种实现动态代理的方式,分别是基于Jdk的动态代理和基于Cglib的动态代理。

jdk动态代理 实现InvocationHandler接口,然后实现其中的invoke方法,由Proxy.newProxyInstance生成代理对象。 第一个参数指定当前目标对象使用的类加载器,获取加载器的方法是固定的;第二个参数指定目标对象实现的接口的类型;第三个参数指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法 public static void main(String[] args) { Sell sell=new RealSell(); InvocationHandler invocationHandler=new ProxyHandler(sell); Sell proxysell= (Sell) Proxy.newProxyInstance(sell.getClass().getClassLoader(),sell.getClass().getInterfaces(),invocationHandler); proxysell.sellRoom(); }

cglib动态代理 创建cglib的动态代理类,继承MethodInterceptor接口 ,实现其中的intercept方法,由enhance对象来创建代理类 cglib 是针对类来实现代理的,它的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对 final 修饰的类进行代理。

CGLIB 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理

把代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。 public static void main(String[] args) { //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数 Enhancer enhancer=new Enhancer(); //设置目标类的字节码文件 enhancer.setSuperclass(CGRoom.class); //设置回调函数 enhancer.setCallback(new MyMethodInterceptor()); //创建代理对象 CGRoom proxy= (CGRoom) enhancer.create(); proxy.rent(“碧桂园”); }

jdk动态代理和cglib动态代理区别 JDK 动态代理实现接口,Cglib 动态代理继承思想 JDK 动态代理(目标对象存在接口时)执行效率高于 Ciglib 如果目标对象有接口实现,选择 JDK 代理,如果没有接口实现选择 Cglib 代理 CGLIB 创建的动态代理对象比 JDK 创建的动态代理对象的性能更高,但是 CGLIB 创建代理对象时所花费的时间却比 JDK 多得多。 所以对于单例的对象,因为无需频繁创建对象,用 CGLIB 合适,反之使用JDK方式要更为合适一些。同时由于 CGLIB 由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理。

JDK动态代理要比cglib代理执行速度快,但性能不如cglib好

动态代理优点 因为静态代理是在项目运行前就写好的。但是动态代理就不是这样,由于动态代理在运行时才创建代理类,因此只需要写一个动态代理类就好。通过动态代理,我可以通过一个动态代理类,去代理多个对象。

实际应用场景 工作中使用的Spring系列框架中的AOP,以及RPC框架中都用到了动态代理。 以AOP为例,AOP通过动态代理对目标对象进行了增强,比如我们最常用的前置通知、后置通知等。

jdk动态代理确实只能代理接口,JDK动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口。 如果想要代理类的话可以使用CGLib,CGLib动态代理是代理类去继承目标类,然后实现目标类的方法。

注解 Java语言中的类、方法、变量、参数和包都可以用注解标记,程序运行过程中我们可以获取到相应的注解以及注解中定义的内容

自定义注解 通过@interface声明注解 通过元注解修饰注解 @Target 修饰对象范围 @Retention 注解保留的阶段 @Documented 标记注解 是否可以被文档化 @Inherited 注解是否继承 如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

@Target({ElementType.METHOD,ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface Myannotation { String key() default “”; } 使用注解,因为定义Target时定义了MEHTOD和FIELD,因此可以在属性和方法中使用这个注解 public class MyannotationTest { @Myannotation(key = “javayz”) private String username; } 利用反射解析注解 public static void main(String[] args) { Class myclass=MyannotationTest.class; Field[] fields = myclass.getDeclaredFields(); for (Field field :fields){ if (field.isAnnotationPresent(Myannotation.class)){ System.out.println(“配置了自定义注解”); Myannotation annotation = field.getAnnotation(Myannotation.class); System.out.println(“属性:”+field.getName()+”上的注解key为”+annotation.key()); } } }

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性; 这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。

在上面利用反射解析注解中,通过MyannotationTest.class获取到了MyannotationTest的类对象,又用myclass.getDeclaredFields();获取到了所有的属性。这就是反射。

反射使用场景 1.编程工具 IDEA 或 Eclipse 等,在写代码时会有代码(属性或方法名)提示,就是因为使用了反射; 2.很多知名的框架,为了让程序更优雅更简洁,也会使用到反射。

反射获取调用类可以通过 Class.forName(),反射获取类实例要通过 newInstance(),相当于 new 一个新对象, 反射获取方法要通过 getMethod(),获取到类方法之后使用 invoke() 对类方法进行调用。 如果是类方法为私有方法的话,则需要通过 setAccessible(true) 来修改方法的访问限制。

JDK 原生提供的动态代理就是通过反射实现的,但动态代理的实现方式还可以是 ASM(一个短小精悍的字节码操作框架)、cglib(基于 ASM)等,并不局限于反射。

spring aop 如果被代理的目标对象实现了接口,那么Spring会默认使用JDK动态代理。 所有该目标类型实现的接口都将被代理。若该目标对象没有实现任何接口,则创建一个CGLIB代理。

1,降低模块与模块之间的耦合度,提高业务代码的聚合度。(高内聚低耦合) 2,提高了代码的复用性

pointcut advice aspect
连接点:每一个方法 切入点:匹配的方法集合 切面:连接点与切入点的集合决定了切面,横切关注点的抽象 通知:几种通知 目标对象:被代理对象 织入:程序运行期将切面应用到目标对象并生成代理对象的过程 引入:在不修改原始代码情况下,在程序运行期为程序动态引入方法或字段的过程

同一个类中,方法A调用方法B(方法B上加有注解),注解无效 针对所有的Spring AOP注解,Spring在扫描bean的时候如果发现有此类注解,那么会动态构造一个代理对象。 如果你想要通过类X的对象直接调用其中带注解的A方法,此注解是有效的。因为此时,Spring会判断你将要调用的方法上存在AOP注解,那么会使用类X的代理对象调用A方法。 但是假设类X中的A方法会调用带注解的B方法,而你依然想要通过类X对象调用A方法,那么B方法上的注解是无效的。因为此时Spring判断你调用的A并无注解,所以使用的还是原对象而非代理对象。接下来A再调用B时,在原对象内B方法的注解当然无效了。

AOP注解方法里使用@Autowired对象为null 在之前的使用中,出现过在加上注解的方法中,使用其他注入的对象时,发现对象并没有被注入进来,为null。 最终发现,导致这种情况的原因是因为方法为private。因为Spring不管使用的是JDK动态代理还是CGLIB动态代理,一个是针对实现接口的类,一个是通过子类实现。无论是接口还是父类,显然都不能出现private方法,否则子类或实现类都不能覆盖到。 如果方法为private,那么在代理过程中,根本找不到这个方法,引起代理对象创建出现问题,也导致了有的对象没有注入进去。 所以如果方法需要使用AOP注解,请把它设置为非private方法。

cglib 可以代理任何类这句话对吗?为什么? 答:不完全对,因为 cglib 只能代理可以有子类的普通类,对于像最终类(final),cglib 是不能实现动态代理的, 因为 cglib 的底层是通过继承代理类的子类来实现动态代理的,所以不能被继承类无法使用 cglib。

Updated:

Leave a comment