• Spring之AOP


    一,前言

    ​ 在上一篇博客中总结了关于IOC和DI的知识点,而对于Spring来说还有另一个核心机制就是AOP。

    AOP:,Aspect-OrientedProgramming,面向切面编程。

    ​ AOP是一种面向切面的编程思想,那么何为切面。

    ​ 举例,现在有一张用户表,现要对其中某一个用户进行信息更新。通常的做法是先查询出该用户,然后更新最后再保存到数据库,但这种编程方式并不完善,如果在信息更新之前要添加其他的业务逻辑处理呢。比如日志通知,开启事务等,而AOP便可以很好的处理,在更新用户之前进行日志打印,事务开启。这一点就是切点,而日志和事务相对于该方法来说就是切面,因此切面也可以理解为一个执行过程。

    AOP实现者

    AspectJ

    ​ AspectJ是语言级的AOP实现,2001发布,扩展了Java语言,定义了AOP语法,能够在编译期通过提供横切代码的织入,所以它有一个专门的编译器用来生成遵守Java字节码规范的class文件

    SpringAOP

    ​ SpringAOP使用纯Java实现,在运行期通过代理的方式向目标类织入增强代码,目标类.

    ​ 对于AOP的概念就总结这些,下面说说它的实现原理-动态代理。

    • JDK代理
    • cglib

    二,JDK代理

    ​ 先来说说JDK代理的实现,该方式是实现InvocationHandler接口重写invoke方法来完成。请看如下代码,定义实现InvocationHandler的实现类。

    public class JDKProxy implements InvocationHandler {
        // 目标方法
        private Object target;
    
        public JDKProxy(Object target){
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("动态代理前。。。");
            System.out.println("method:" + method);
            System.out.println("method名称:" + method.getName());
            // 调用真实的业务主体
            Object invoke = method.invoke(this.target, args);
            System.out.println("动态代理之后。。。");
            return invoke;
        }
    }
    

    ​ 编写客户端,创建代理类。

    public class JDKApplication {
        public static void main(String[] args) {
    
            IUserService service = new UserServiceImpl();
            // 1,通过类加载器获取目标对象
            ClassLoader classLoader = service.getClass().getClassLoader();
            // 2,目标对象的父类接口
            Class<?>[] interfaces = service.getClass().getInterfaces();
            // 3,创建动态代理类
            JDKProxy handler = new JDKProxy(service);
            IUserService userService =(IUserService) Proxy.newProxyInstance(classLoader, interfaces, handler);
            // 调用方法
            userService.addUser();
        }
    }
    

    ​ 运行结果为:

    分析:

    ​ 通过代理类去调用方法,明显看出在方法执行前后,都可以进行其他代码的编写。通过这个案例是否可以更贴切的体会到切面的思想,也就是将我们要执行的方法传递给代理类,让代理类去帮我们实现,同时这个代理类再做点“小手脚”。在方法前后添油加醋,来达到更好的效果。

    ​ 原理分析:

    ​ 在创建代理类时,调用了newProxyInstance方法,那就先来看看该方法的实现。

     public static Object newProxyInstance(ClassLoader loader,
                                              Class<?>[] interfaces,
                                              InvocationHandler h)
            throws IllegalArgumentException
        {
        // 判断对象是否为空
            Objects.requireNonNull(h);
    
            final Class<?>[] intfs = interfaces.clone();
            final SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
            }
    
            /*
             * 查找或生成指定的代理类
             */
            Class<?> cl = getProxyClass0(loader, intfs);
    
            /*
             * Invoke its constructor with the designated invocation handler.
             */
            try {
                if (sm != null) {
                    checkNewProxyPermission(Reflection.getCallerClass(), cl);
                }
    	// 调用构造方法创建代理类
                final Constructor<?> cons = cl.getConstructor(constructorParams);
                final InvocationHandler ih = h;
                if (!Modifier.isPublic(cl.getModifiers())) {
                    AccessController.doPrivileged(new PrivilegedAction<Void>() {
                        public Void run() {
                            cons.setAccessible(true);
                            return null;
                        }
                    });
                }
                return cons.newInstance(new Object[]{h});
            } catch (IllegalAccessException|InstantiationException e) {
                throw new InternalError(e.toString(), e);
            } catch (InvocationTargetException e) {
                Throwable t = e.getCause();
                if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else {
                    throw new InternalError(t.toString(), t);
                }
            } catch (NoSuchMethodException e) {
                throw new InternalError(e.toString(), e);
            }
        }
    

    ​ 1,源码很多但是不需要都看,其中有一行代码Class<?> cl = getProxyClass0(loader, intfs);,表示生成二进制字节码,并且在缓存中去获取数据(反射)。

    private static Class<?> getProxyClass0(ClassLoader loader,
                                               Class<?>... interfaces) {
            if (interfaces.length > 65535) {
                throw new IllegalArgumentException("interface limit exceeded");
            }
    
            // If the proxy class defined by the given loader implementing
            // the given interfaces exists, this will simply return the cached copy;
            // otherwise, it will create the proxy class via the ProxyClassFactory
            return proxyClassCache.get(loader, interfaces);
        }
    

    ​ 2,在return中又调用了get(),这个方法是在WeakCache缓存中,也就是说在创建代理类时,先从缓存中获取目标对象的属性。

    ​ 3,再看下一行final Constructor<?> cons = cl.getConstructor(constructorParams);

    private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
                                            int which) throws NoSuchMethodException
        {
            Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
            for (Constructor<T> constructor : constructors) {
                if (arrayContentsEq(parameterTypes,
                                    constructor.getParameterTypes())) {
                    return getReflectionFactory().copyConstructor(constructor);
                }
            }
            throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
        }
    

    ​ 4,调用构造器创出代理对象,通过代理对象执行目标方法。

    三,CgLib

    ​ 1,原理:动态给目标对象创建子类,子类中复写了父类中的方法,在子类的基础之上进行拦截。

    ​ 2,解决了jdk实现动态代理必须要有接口问题。

    ​ 3,也是给目对象中的所有方法添加了增强,要通过硬编码的方式解决。

    ​ 4,目标的不能用final修饰。

    public class CglibProxy implements MethodInterceptor {
        /**
         * obj:创建子类的代理对象
         * method:业务逻辑的方法
         * objects:可变参数
         * methodProxy:代理方法
         */
        @Override
        public Object intercept(Object obj, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("方法拦截前。。。");
            Object invokeSuper = methodProxy.invokeSuper(obj, objects);
            System.out.println("方法拦截后。。。");
            return invokeSuper;
        }
    }
    
    public class CglibClient {
        public static void main(String[] args) {
            // 1,实例化目标对象
            UserServiceImpl service = new UserServiceImpl();
            // 2,创建目标对象的子类
            Enhancer enhancer = new Enhancer();
            // 3,设置目标对象
            enhancer.setSuperclass(service.getClass());
            // 4,设置Handler
            enhancer.setCallback(new CglibProxy());
            // 5,返回子类对象
            UserServiceImpl user = (UserServiceImpl) enhancer.create();
            user.addUser();
        }
    }
    

    ​ 关于Cglib的实现这里就不再总结了,但大致思路就是继承父类,然后重写父类方法,在子类的基础上对方法进行拦截。

    四,Aspectj

    ​ 1)注解

    ​ a)@Asepctj:用该注解修饰的类就代表一个切面

    ​ b)@Pointcut:表达式

    execution(public * com.cglib.service.*.*(..))

    ​ c)@Before:前置通知

    ​ d)@After:后置通知

    ​ e)@Around:环绕通知

    ProceedingJoinPoint

    ​ f)@AfterThrowing:抛出异常通知

    ​ g)@AfterReturning:后置通知,但是出现异常不执行

    ​ 定义一个切面类:

    @Aspect    //Aspect修饰的类就代表一个切面
    public class AspectJDemo {
    	
    	@Pointcut(value="execution(* add(..))")
    	public void p1() {}
    	
    	@Pointcut(value="execution(* update(..))")
    	public void p2() {}
    	
    	@Before(value="p1() || p2()")
    	public void begin() {
    		tr.beign();
    	}
    	
    	@After(value="p1() || p2()")
    	public void after() {
    		tr.commit();
    	}
    	
    	@Around(value="p1() || p2()")
    	public void round(ProceedingJoinPoint point ) {
    		System.out.println("开始环绕");
    		try {
    			//调用目标对象
    			Object proceed = point.proceed();
    			System.out.println("round:"+proceed);
    			
    		} catch (Throwable e) {
    			e.printStackTrace();
    		}  
    		System.out.println("结束环绕");
    	}
    	
    	@AfterThrowing(value="p1() || p2()")
    	public void afterThrow() {
    		System.out.println("出现异常");
    	}
    	
    	@AfterReturning(value="p1() || p2()")
    	public void returning() {
    		System.out.println("出现异常的时候不执行");
    	}
    }
    
    
    public void testAdd() {
    		//1,目标对象
    		UserServiceImpl serviceImpl = new UserServiceImpl();
    		//2,切面
    		AspectJDemo aspect = new AspectJDemo(tr);
    		//3,创建代理类
    		AspectJProxyFactory factory = new AspectJProxyFactory();
    		factory.setTarget(serviceImpl);   //设置目标对象
    		factory.addAspect(aspect);		  //设置切面
    		
    		IUserService userService = (IUserService)factory.getProxy();
    		//4,调用方法
    		userService.add();
    }
    

    五,总结

    ​ 关于Spring两个核心概念IOC和AOP就总结完了,总结的并不深入。而对于Spring的AOP两种实现方式,也要在工作中适当的选择。

    ​ 以上内容如有不适之处,欢迎留言指正。

    感谢阅读!

  • 相关阅读:
    linux下载和解压命令
    检测是否存在post类型的注入
    snort的学习
    解决ERROR: D:Snortetcsnort.conf(511) Unknown preprocessor: "sip". Fatal Error, Quitting..
    代理服务器
    Settimer及回调函数的用法
    vm安装ubantu的详细过程(转载)
    linux 常用指令汇总
    linux 用户及文件权限管理
    linux 基础知识
  • 原文地址:https://www.cnblogs.com/fenjyang/p/11523053.html
Copyright © 2020-2023  润新知