网站全景图怎么做,长沙网站设计服务商,网站制作前言公司,cms系统主要功能点击上方“Java专栏”#xff0c;选择“置顶或者星标”第一时间阅读精彩文章#xff01;1、☞ 程序员进阶必备资源免费送「21种技术方向#xff01;」 点击查看☜2、☞ 《Java面试手册》.PDF 点击查看作者#xff1a;马佩juejin.im/post/5bf4fc84f265da611b57f906概述选择“置顶或者星标”第一时间阅读精彩文章1、☞ 程序员进阶必备资源免费送「21种技术方向」 点击查看☜2、☞ 《Java面试手册》.PDF 点击查看作者马佩juejin.im/post/5bf4fc84f265da611b57f906概述最近在开发中遇到了一个刚好可以用AOP实现的例子就顺便研究了AOP的实现原理把学习到的东西进行一个总结。文章中用到的编程语言为kotlin需要的可以在IDEA中直接转为java。这篇文章将会按照如下目录展开AOP简介代码中实现举例AOP实现原理部分源码解析1. AOP简介相信大家或多或少的了解过AOP都知道它是面向切面编程在网上搜索可以找到很多的解释。这里我用一句话来总结AOP是能够让我们在不影响原有功能的前提下为软件横向扩展功能。那么横向扩展怎么理解呢我们在WEB项目开发中通常都遵守三层原则包括控制层(Controller)-业务层(Service)-数据层(dao),那么从这个结构下来的为纵向它具体的某一层就是我们所说的横向。我们的AOP就是可以作用于这某一个横向模块当中的所有方法。我们在来看一下AOP和OOP的区别AOP是OOP的补充当我们需要为多个对象引入一个公共行为比如日志操作记录等就需要在每个对象中引用公共行为这样程序就产生了大量的重复代码使用AOP可以完美解决这个问题。接下来介绍一下提到AOP就必须要了解的知识点切面拦截器类其中会定义切点以及通知切点具体拦截的某个业务点。通知切面当中的方法声明通知方法在目标业务层的执行位置通知类型如下前置通知Before 在目标业务方法执行之前执行后置通知After 在目标业务方法执行之后执行返回通知AfterReturning 在目标业务方法返回结果之后执行异常通知AfterThrowing 在目标业务方法抛出异常之后环绕通知Around 功能强大可代替以上四种通知还可以控制目标业务方法是否执行以及何时执行2. 代码中实现举例上面已经大概的介绍了AOP中需要了解的基本知识也知道了AOP的好处那怎么在代码中实现呢给大家举个例子我们现在有个学校管理系统已经实现了对老师和学生的增删改又新来个需求说是对老师和学生的每次增删改做一个记录到时候校长可以查看记录的列表。那么问题来了怎么样处理是最好的解决办法呢这里我罗列了三种解决办法我们来看下他的优缺点。最简单的就是第一种方法我们直接在每次的增删改的函数当中直接实现这个记录的方法这样代码的重复度太高耦合性太强不建议使用。其次就是我们最长使用的将记录这个方法抽离出来其他的增删改调用这个记录函数即可显然代码重复度降低但是这样的调用还是没有降低耦合性。这个时候我们想一下AOP的定义再想想我们的场景其实我们就是要在不改变原来增删改的方法给这个系统增加记录的方法而且作用的也是一个层面的方法。这个时候我们就可以采用AOP来实现了。我们来看下代码的具体实现1,首先我定义了一个自定义注解作为切点Target(AnnotationTarget.FUNCTION) //注解作用的范围这里声明为函数Order(Ordered.HIGHEST_PRECEDENCE) //声明注解的优先级为最高假设有多个注解先执行这个annotation class Hanler(val handler: HandlerType) //自定义注解类HandlerType是一个枚举类型里面定义的就是学生和老师的增删改操作在这里就不展示具体内容了2,接下来就是要定义切面类了Aspect //该注解声明这个类为一个切面类Componentclass HandlerAspect{ Autowired private lateinit var handlerService: HandlerServiceAfterReturning(annotation(handler)) //当有函数注释了注解将会在函数正常返回后在执行我们定义的方法fun hanler(hanler: Hanler) { handlerService.add(handler.operate.value) //这里是真正执行记录的方法}}3,最后就是我们本来的业务方法了/*** 删除学生方法*/Handler(operate Handler.STUDENT_DELETE) //当执行到删除学生方法时切面类就会起作用了,当学生正常删除后就会执行记录方法我们就可以看到记录方法生成的数据fun delete(idString) { studentService.delete(id)}3. AOP实现原理我们现在了解了代码中如何实现那么AOP实现的原理是什么呢之前看了一个博客说到提到AOP大家都知道他的实现原理是动态代理显然我之前就是不知道的哈哈但是相信阅读文章的你们一定是知道的。讲到动态代理就不得不说代理模式了代理模式的定义给某一个对象提供一个代理并由代理对象控制对原对象的引用。代理模式包含如下角色subject抽象主题角色是一个接口。该接口是对象和它的代理共用的接口;RealSubject真实主题角色是实现抽象主题接口的类;Proxy:代理角色内部含有对真实对象RealSubject的引用从而可以操作真实对象。代理对象提供与真实对象相同的接口以便代替真实对象。同时代理对象可以在执行真实对象操作时附加其他的操作相当于对真实对象进行封装。如下图所示那么代理又分为静态代理和动态代理这里写两个小的demo动态代理采用的就是JDK代理。举个例子就是现在一个班上的学生需要交作业现在由班长代理交作业那么班长就是代理学生就是被代理的对象。3.1 静态代理首先我们创建一个Person接口。这个接口就是学生(被代理类)和班长(代理类)的公共接口他们都有交作业的行为。这样学生交作业就可以让班长来代理执行。/** * 创建person接口 */public interface Person { //交作业 void giveTask();}Student类实现Person接口Student可以具体实施交作业这个行为。public class Student implements Person { private String name; public Student(String name) { this.name name; } public void giveTask() { System.out.println(name 交语文作业); }}StudentsProxy类这个类也实现了Person接口但是还另外持有一个学生类对象那么他可以代理学生类对象执行交作业的行为。/** * 学生代理类也实现了Person接口保存一个学生实体这样就可以代理学生产生行为 */public class StudentsProxy implements Person{ //被代理的学生 Student stu; public StudentsProxy(Person stu) { // 只代理学生对象 if(stu.getClass() Student.class) { this.stu (Student)stu; } } //代理交作业调用被代理学生的交作业的行为 public void giveTask() { stu.giveTask(); }}下面测试一下看代理模式如何使用public class StaticProxyTest { public static void main(String[] args) { //被代理的学生林浅他的作业上交有代理对象monitor完成 Person linqian new Student(林浅); //生成代理对象并将林浅传给代理对象 Person monitor new StudentsProxy(linqian); //班长代理交作业 monitor.giveTask(); }}运行结果这里并没有直接通过林浅(被代理对象)来执行交作业的行为而是通过班长(代理对象)来代理执行了。这就是代理模式。代理模式就是在访问实际对象时引入一定程度的间接性这里的间接性就是指不直接调用实际对象的方法那么我们在代理过程中就可以加上一些其他用途。比如班长在帮林浅交作业的时候想告诉老师最近林浅的进步很大就可以轻松的通过代理模式办到。在代理类的交作业之前加入方法即可。这个优点就可以运用在spring中的AOP我们能在一个切点之前执行一些操作在一个切点之后执行一些操作这个切点就是一个个方法。这些方法所在类肯定就是被代理了在代理过程中切入了一些其他操作。3.2 动态代理动态代理和静态代理的区别是静态代理的的代理类是我们自己定义好的在程序运行之前就已经变异完成但是动态代理的代理类是在程序运行时创建的。相比于静态代理动态代理的优势在于可以很方便的对代理类的函数进行统一的处理而不用修改每个代理类中的方法。比如我们想在每个代理方法之前都加一个处理方法我们上面的例子中只有一个代理方法如果还有很多的代理方法就太麻烦了我们来看下动态代理是怎么去实现的。首先还是定义一个Person接口:/** * 创建person接口 */public interface Person { //交作业 void giveTask();}接下来是创建需要被代理的实际类也就是学生类public class Student implements Person { private String name; public Student(String name) { this.name name; } public void giveTask() { System.out.println(name 交语文作业); }}创建StuInvocationHandler类实现InvocationHandler接口这个类中持有一个被代理对象的实例target。InvocationHandler中有一个invoke方法所有执行代理对象的方法都会被替换成执行invoke方法。public class StuInvocationHandler implements InvocationHandler { //invocationHandler持有的被代理对象 T target; public StuInvocationHandler(T target) { this.target target; } /** * proxy:代表动态代理对象 * method代表正在执行的方法 * args代表调用目标方法时传入的实参 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(代理执行 method.getName() 方法); Object result method.invoke(target, args); return result; }}那么接下来我们就可以具体的创建代理对象了。/** * 代理类 */public class ProxyTest { public static void main(String[] args) { //创建一个实例对象这个对象是被代理的对象 Person linqian new Student(林浅); //创建一个与代理对象相关联的InvocationHandler InvocationHandler stuHandler new StuInvocationHandler(linqian);//创建一个代理对象stuProxy来代理linqian代理对象的每个执行方法都会替换执行Invocation中的invoke方法 Person stuProxy (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, stuHandler);//代理执行交作业的方法 stuProxy.giveTask(); }}我们执行代理测试类首先我们创建了一个需要被代理的学生林浅将林浅传入stuHandler中我们在创建代理对象stuProxy时将stuHandler作为参数那么所有执行代理对象的方法都会被替换成执行invoke方法也就是说最后执行的是StuInvocationHandler中的invoke方法。所以在看到下面的运行结果也就理所当然了。那么到这里问题就来了为什么代理对象执行的方法都会通过InvocationHandler中的invoke方法来执行带着这个问题我们需要看一下动态代理的源码对他进行简单的分析。更多动态代理解析上面我们使用Proxy类的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); } /* * Look up or generate the designated proxy class. */ 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() {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); } }然后我们需要重点关注Class cl getProxyClass0(loader, intfs)这句代码这里产生了代理类这个类就是动态代理的关键由于是动态生成的类文件我们将这个类文件打印到文件中。 byte[] classFile ProxyGenerator.generateProxyClass($Proxy0, Student.class.getInterfaces()); String path /Users/mapei/Desktop/okay/65707.class; try{ FileOutputStream fos new FileOutputStream(path); fos.write(classFile); fos.flush(); System.out.println(代理类class文件写入成功); }catch (Exception e) { System.out.println(写文件错误); }对这个class文件进行反编译我们看看jdk为我们生成了什么样的内容import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.lang.reflect.UndeclaredThrowableException;import proxy.Person;public final class $Proxy0 extends Proxy implements Person{ private static Method m1; private static Method m2; private static Method m3; private static Method m0; /** *注意这里是生成代理类的构造方法方法参数为InvocationHandler类型看到这是不是就有点明白 *为何代理对象调用方法都是执行InvocationHandler中的invoke方法而InvocationHandler又持有一个 *被代理对象的实例就可以去调用真正的对象实例。 */ public $Proxy0(InvocationHandler paramInvocationHandler) throws { super(paramInvocationHandler); } //这个静态块本来是在最后的我把它拿到前面来方便描述 static { try { //看看这儿静态块儿里面的住giveTask通过反射得到的名字m3其他的先不管 m1 Class.forName(java.lang.Object).getMethod(equals, new Class[] { Class.forName(java.lang.Object) }); m2 Class.forName(java.lang.Object).getMethod(toString, new Class[0]); m3 Class.forName(proxy.Person).getMethod(giveTask, new Class[0]); m0 Class.forName(java.lang.Object).getMethod(hashCode, new Class[0]); return; } catch (NoSuchMethodException localNoSuchMethodException) { throw new NoSuchMethodError(localNoSuchMethodException.getMessage()); } catch (ClassNotFoundException localClassNotFoundException) { throw new NoClassDefFoundError(localClassNotFoundException.getMessage()); } } /** * *这里调用代理对象的giveMoney方法直接就调用了InvocationHandler中的invoke方法并把m3传了进去。 *this.h.invoke(this, m3, null);我们可以对将InvocationHandler看做一个中介类中介类持有一个被代理对象在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用把外部对invoke的调用最终都转为对被代理对象的调用。 */ public final void giveTask()throws { try { this.h.invoke(this, m3, null); return; } catch (Error|RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } }}看完了动态代理的源码我们接下来就要看一下Spring中AOP实现的源码是怎样的4. 部分源码解析aop创建代理的源码分析1,看一下bean如何被包装为proxy protected Object createProxy( Class beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } // 1.创建proxyFactoryproxy的生产主要就是在proxyFactory做的 ProxyFactory proxyFactory new ProxyFactory(); proxyFactory.copyFrom(this); if (!proxyFactory.isProxyTargetClass()) { if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { evaluateProxyInterfaces(beanClass, proxyFactory); } } // 2.将当前bean适合的advice重新封装下封装为Advisor类然后添加到ProxyFactory中 Advisor[] advisors buildAdvisors(beanName, specificInterceptors); for (Advisor advisor : advisors) { proxyFactory.addAdvisor(advisor); } proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } // 3.调用getProxy获取bean对应的proxy return proxyFactory.getProxy(getProxyClassLoader()); }2,创建何种类型的ProxyJDKProxy还是CGLIBProxy public Object getProxy(ClassLoader classLoader) { return createAopProxy().getProxy(classLoader); } // createAopProxy()方法就是决定究竟创建何种类型的proxy protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } // 关键方法createAopProxy() return getAopProxyFactory().createAopProxy(this); } // createAopProxy() public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // 1.config.isOptimize()是否使用优化的代理策略目前使用与CGLIB // config.isProxyTargetClass() 是否目标类本身被代理而不是目标类的接口 // hasNoUserSuppliedProxyInterfaces()是否存在代理接口 if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) { Class targetClass config.getTargetClass(); if (targetClass null) { throw new AopConfigException(TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.); } // 2.如果目标类是接口类(目标对象实现了接口)则直接使用JDKproxy if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) { return new JdkDynamicAopProxy(config); } // 3.其他情况则使用CGLIBproxy return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }3,getProxy()方法 final class JdkDynamicAopProxy implements AopProxy, InvocationHandler, Serializable// JdkDynamicAopProxy类结构由此可知其实现了InvocationHandler则必定有invoke方法来被调用也就是用户调用bean相关方法时此invoke()被真正调用 // getProxy() public Object getProxy(ClassLoader classLoader) { if (logger.isDebugEnabled()) { logger.debug(Creating JDK dynamic proxy: target source is this.advised.getTargetSource()); } Class[] proxiedInterfaces AopProxyUtils.completeProxiedInterfaces(this.advised, true); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); // JDK proxy 动态代理的标准用法 return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); }4,invoke()方法法 //使用了JDK动态代理模式真正的方法执行在invoke()方法里看到这里在想一下上面动态代理的例子是不是就完全明白Spring源码实现动态代理的原理了。 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { MethodInvocation invocation; Object oldProxy null; boolean setProxyContext false; TargetSource targetSource this.advised.targetSource; Class targetClass null; Object target null; try { // 1.以下的几个判断主要是为了判断method是否为equals、hashCode等Object的方法 if (!this.equalsDefined AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return equals(args[0]); } else if (!this.hashCodeDefined AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } else if (method.getDeclaringClass() DecoratingProxy.class) { // There is only getDecoratedClass() declared - dispatch to proxy config. return AopProxyUtils.ultimateTargetClass(this.advised); } else if (!this.advised.opaque method.getDeclaringClass().isInterface() method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy AopContext.setCurrentProxy(proxy); setProxyContext true; } // May be null. Get as late as possible to minimize the time we own the target, // in case it comes from a pool. target targetSource.getTarget(); if (target ! null) { targetClass target.getClass(); } // 2.获取当前bean被拦截方法链表 List chain this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);// 3.如果为空则直接调用target的methodif (chain.isEmpty()) { Object[] argsToUse AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); }// 4.不为空则逐一调用chain中的每一个拦截方法的proceed这里的一系列执行的原因以及proceed执行的内容我 在这里就不详细讲了大家感兴趣可以自己去研读哈else {// We need to create a method invocation... invocation new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);// Proceed to the joinpoint through the interceptor chain. retVal invocation.proceed(); } ...return retVal; } } }那么到了这里我要讲的内容就差不多结束了如果有什么不对的或者有什么疑惑欢迎大家指点以上便是今天的分享希望大家喜欢觉得内容不错的欢迎点击「在看」支持谢谢各位喜欢文章点个在看