Spring之AOP
- 2019 年 10 月 3 日
- 筆記
一,前言
在上一篇博客中总结了关于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两种实现方式,也要在工作中适当的选择。
以上内容如有不适之处,欢迎留言指正。
感谢阅读!