美文网首页
Spring代理失效以及解决方案

Spring代理失效以及解决方案

作者: 知止9528 | 来源:发表于2021-04-12 22:40 被阅读0次

    示例

    public class TxTest {
        @Transactional(propagation = Propagation.NEVER)
        public void a() {
            UserInfoVo infoVo = new UserInfoVo();
            infoVo.setAge(1000);
            infoVo.setUserName("a");
            userInfoDao.save2(infoVo);
        }
    
        public void b() {
            UserInfoVo infoVo = new UserInfoVo();
            infoVo.setAge(1000);
            infoVo.setUserName("b");
            userInfoDao.save2(infoVo);
        }
    
    
        @Transactional
        public void start() {
            a();
            b();
        }
    }
    

    如果当前存在事务则抛出异常。而我这个测试例子中,start存在了事务,再次调用a,正常情况下应该会抛出异常的,但是结果却是没有异常,顺利执行了。为什么呢?

    代理模拟

    基于jdk代理的分析

    1. 定义接口
    public interface ReadableInterface {
    
        void read();
    
        void write();
    
    }
    

    2.实现接口类

    public class StudentImpl implements ReadableInterface {
    
    
    
        @Override
        public void read() {
            System.out.println("read........");
            write();
        }
    
        @Override
        public void write() {
            System.out.println("write ..........");
        }
    
    }
    
    1. 创建代理
    public class ProxyObjectImpl implements InvocationHandler {
    
        private Object proxied = null;
    
        public ProxyObjectImpl() {
        }
    
        public ProxyObjectImpl(Object proxied) {
            this.proxied = proxied;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("代理做事前。。。。。。。");
    
            Object obj = method.invoke(proxied, args);
    
            System.out.println("代理做事后。。。。。。");
            return obj;
        }
    }
    
    1. 模拟客户端调用
    public class DynamicProxyDemo {
        public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
            proxy2();
        }
    
        private static void proxy2() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
            StudentImpl student = new StudentImpl();
            //获取委托方
            ReadableInterface readableInterface = (ReadableInterface) Proxy
                    .newProxyInstance(StudentImpl.class.getClassLoader(),
                    StudentImpl.class.getInterfaces(),
                    new ProxyObjectImpl(student));
            readableInterface.read();
        }
    }
    

    输出结果为:

    代理做事前。。。。。。。
    read........
    write ..........
    代理做事后。。。。。。

    查看JDK代理生成对象

    public class JdkProxyUtil {
        public static void writeClassToDisk(String path){
    
            byte[] classFile = ProxyGenerator.generateProxyClass("$proxy4", new Class[]{ReadableInterface.class});
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(path);
                fos.write(classFile);
                fos.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                if(fos != null){
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            JdkProxyUtil.writeClassToDisk("G:/$Proxy4.class");
        }
    }
    

    生成的代理对象如下

    public final class $Proxy0 extends Proxy implements ReadableInterface {
        private static Method m1;
        private static Method m4;
        private static Method m2;
        private static Method m3;
        private static Method m0;
    
        public $Proxy0(InvocationHandler var1) throws  {
            super(var1);
        }
    
        public final boolean equals(Object var1) throws  {
            try {
                return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
            } catch (RuntimeException | Error var3) {
                throw var3;
            } catch (Throwable var4) {
                throw new UndeclaredThrowableException(var4);
            }
        }
    
        public final void read() throws  {
            try {
                super.h.invoke(this, m4, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final String toString() throws  {
            try {
                return (String)super.h.invoke(this, m2, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final void write() throws  {
            try {
                super.h.invoke(this, m3, (Object[])null);
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        public final int hashCode() throws  {
            try {
                return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
            } catch (RuntimeException | Error var2) {
                throw var2;
            } catch (Throwable var3) {
                throw new UndeclaredThrowableException(var3);
            }
        }
    
        static {
            try {
                m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
                m4 = Class.forName("代理.ReadableInterface").getMethod("read", new Class[0]);
                m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
                m3 = Class.forName("代理.ReadableInterface").getMethod("write", new Class[0]);
                m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            } catch (NoSuchMethodException var2) {
                throw new NoSuchMethodError(var2.getMessage());
            } catch (ClassNotFoundException var3) {
                throw new NoClassDefFoundError(var3.getMessage());
            }
        }
    }
    

    说明:

    我们可以看到是基于接口的代理,会调用ReadableInterface.read()方法
    客户端调用read的时候走的是代理,所以打印出了“代理前,代理后”字样;
    但是write则是直接输出的,因为write()是在read()内部调用的。
    整个过程相当于

    //客户端如下:
    main(){
          proxy.read();
    }
    
    //被代理的接口类最终的样子如下:proxy.class
    
    proxy.read(){
          print("调用前...");
          read();
          print("调用后...");
    }
    
    proxy.write(){
          print("调用前...");
          write();
          print("调用后...");
    }
    
    read(){
       this.write();
    }
    
    write(){
    }
    

    Client里面调用的是proxy.read();是走了代理的,然后read()里面则是直接调用了write,而不是proxy.write();
    这个不是Spring AOP的问题,而是我们使用代理他本身存在的一种问题。


    基于CGlib的代理

    CGLib是基于类的代理,而不是接口代理。

    1. 基础类
    public class Person {
    
        public void play(){
            System.out.println("person is play ....");
        }
    
        public void getMoney(){
            play();
            System.out.println("give me 100$");
        }
    }
    
    1. 编写代理
    public class CglibProxyHandler implements MethodInterceptor {
    
    
        public CglibProxyHandler() {
        }
    
        /**
         * 1、代理对象;2、委托类方法;3、方法参数;4、代理方法的MethodProxy对象。
         * @param o
         * @param method
         * @param objects
         * @param methodProxy
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("proxy before......");
            Object o1 = methodProxy.invokeSuper(o, objects);
            System.out.println("proxy after .......");
            return o1;
        }
    }
    
    1. 客户端调用
    public class CglibProxyDome {
    
        public static void main(String[] args) {
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "target/cglib");
            CglibProxyHandler cglibProxy = new CglibProxyHandler();
    
            //jdk需要提供接口,cglib需要是非私有类,且不能处理final关键字修饰的方法
            Enhancer enhancer = new Enhancer();
            //设置父类
            enhancer.setSuperclass(Person.class);
            //设置回调对象
            enhancer.setCallback(cglibProxy); 
    
            Person person = (Person) enhancer.create(); 
            person.getMoney();
        }
    }
    

    5.打印结果:

    proxy before......
    proxy before......
    person is play ....
    proxy after .......
    give me 100$
    proxy after .......

    发现内部方法调用也走了代理哦!为什呢?

    基于类的代理模式为生成一个子类,来继承父类的方式

    由CGLib生成的子类最终形态如下:

    public class Person$$EnhasncerBy....... extends Person{
    
        public final void play(){
           print("代理前");
            super.play();
            print("代理后");
        }
    
        public final void getMoney(){
           print("代理前");
            super.getMoney();
            print("代理后");
        }
    
    }
    
    public class Demo(){
            Person p=new Person$$Enhasncer.....()
            p.getMoney();
    }
    

    说明:
    由于new出来的是子类的对象,而不是父类的对象,所以在调用了父类的money之后,父类的money中调用了this.play();
    但是这个this.play()不是父类中的play,而是子类Person$$Enhasncer中的play(); 因为new的对象内部存储的是父类的,而不是子类中的对象。


    Spring AOP使用CGLib解决内部调用失效的问题

    1. 内部调用不走代理解决方法:<aop:aspectj-autoproxy expose-proxy="true" />
    2. 使用((Service)AopContext.currentProxy()).doSomething;

    基于AspectJ编译期织入

    AspectJ是在java文件编译期间,织入字节码,改变原有的类。
    静态编译织入和动态代理还是很不一样,动态代理是生成一个代理对象(运行期),然后代理你去做事情;
    但是静态编译织入,虽然也是代替你执行一些事情,但是他不是生成一个动态代理类,而是在你原有的类中在编译期间去改变你原有的类,在原有的类中加入一些事情。


    代理比较一下

    JDK动态代理和CGLIB字节码生成的区别?

    • JDK动态代理只能对实现了接口的类生成代理,而不能针对类
    • CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明成final,static,private会导致代理失效。

    相关文章

      网友评论

          本文标题:Spring代理失效以及解决方案

          本文链接:https://www.haomeiwen.com/subject/onmflltx.html