关于aop:聊聊如何通过APTAST来实现AOP功能

前言如果有应用过spring aop性能的小伙伴,应该都会晓得spring aop次要是通过动静代理在运行时,对业务进行切面拦挡操作。明天咱们就来实现一下如何通过APT+AST在编译期时实现AOP性能。不过在此之前先科普一下APT和AST相干内容 APT(注解处理器)apt能够查看我之前写过的文章聊聊如何使用JAVA注解处理器(APT) AST(形象语法树)什么是AST形象语法树(Abstract Syntax Tree,AST),是源代码语法结构的一种形象示意。它以树状的模式体现编程语言的语法结构,树上的每个节点都示意源代码中的一种构造。比方包、类型、修饰符、运算符、接口、返回值都能够是一个语法结构。 示例: package com.example.adams.astdemo;public class TestClass { int x = 0; int y = 1; public int testMethod(){ int z = x + y; return z; }}对应的形象语法树如下: java的编译过程重点关注步骤一和步骤二生成AST的过程 步骤一:词法剖析,将源代码的字符流转变为 Token 列表。 通过词法分析器剖析源文件中的所有字符,将所有的单词或字符都转化成符合规范的Token 规范化的token能够分成一下三种类型: java关键字:public, static, final, String, int等等;自定义的名称:包名,类名,办法名和变量名;运算符或者逻辑运算符等符号:+、-、*、/、&&,|| 等等。 步骤二: 语法分析,依据 Token 流来结构树形表达式也就是 AST。 语法树的每一个节点都代表着程序代码中的一个语法结构,如类型、修饰符、运算符等。通过这个步骤后,编译器就根本不会再对源码文件进行操作了,后续的操作都建设在形象语法树之上。 AST的利用场景AST 定义了代码的构造,通过操作 AST,咱们能够精准地定位到申明语句、赋值语句、运算语句等,实现对源代码的剖析、优化、变更等操作。 注: AST操作属于编译器级别,对程序运行齐全没有影响,效率绝对其余AOP更高 java形象语法树罕用API类介绍JCTreeJCTree 是语法树元素的基类,蕴含一个重要的字段 pos,该字段用于指明以后语法树节点(JCTree)在语法树中的地位,因而咱们不能间接用 new 关键字来创立语法树节点,即便创立了也没有意义。 重点介绍几个JCTree的子类: 1、JCStatement:申明语法树节点,常见的子类如下 JCBlock:语句块语法树节点JCReturn:return 语句语法树节点JCClassDecl:类定义语法树节点JCVariableDecl:字段 / 变量定义语法树节点2、JCMethodDecl:办法定义语法树节点3、JCModifiers:拜访标记语法树节点4、JCExpression:表达式语法树节点,常见的子类如下 ...

April 25, 2023 · 7 min · jiezi

关于aop:Spring-AOP

1. 概述从实现的角度来说,代理分为基于类的代理和基于接口的代理,基于接口的代理有 动态代理和 动静代理,而基于类的代理须要依赖第三方库,比方 cglib,cglib的代理在运行时动静生成字节码文件来实现代理。 2. 动态代理在编译期间就曾经实现代理2.1 实现动态代理的必要条件基于接口或者抽象类代理类实现接口或者继承抽象类,通过构造函数传入指标对象重写办法,通过指标对象去执行业务逻辑而代理对象去执行与业务不想关的逻辑2.2 示例代码public interface UserService { /** * 增加 * @param email 邮箱 * @return 受影响的行数 */ int add(String email);}public class UserServiceImpl implements UserService { @Override public int add(String email) { System.out.println(String.format("email:%s", email)); return 1; }}public class UserServiceProxyImpl implements UserService { private final UserService targetObject; public UserServiceProxyImpl(UserService targetObject) { this.targetObject = targetObject; } @Override public int add(String email) { System.out.println("begin transaction"); int affectedRow = targetObject.add(email); System.out.println("commit transaction"); System.out.println(String.format("affected row:%s", affectedRow)); return affectedRow; }}2.4 动态代理的毛病只能代理一个实现类或接口,无奈做到代理多个类,这样照成了代理类的作用范畴的局限性对于不须要代理的接口办法也须要实现,造成的代码的冗余,扩大难的问题依赖于接口3. 动静代理在运行期间动静生成字节码文件3.1 示例代码public class ServiceProxy implements InvocationHandler { private final Object targetObject; private ClassLoader classLoader; private Class<?>[] interfaces; public ServiceProxy(Object targetObject) { this.targetObject = targetObject; this.classLoader = targetObject.getClass().getClassLoader(); this.interfaces = targetObject.getClass().getInterfaces(); } public Object getProxyObject() { return Proxy.newProxyInstance(classLoader, interfaces, this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(String.format("%s.%s", targetObject.getClass().getName(), method.getName())); System.out.println("begin transaction"); if (args != null) { System.out.println(String.format("params:%s", Arrays.toString(args))); } Object retVal = method.invoke(targetObject, args); System.out.println("commit transaction"); System.out.println(String.format("affected row:%s", retVal)); return retVal; }}public interface UserService { /** * 增加 * @param email 邮箱 * @return 受影响的行数 */ int add(String email);}public class UserServiceImpl implements UserService { @Override public int add(String email) { return 1; }}3.2 与动态代理的区别动态代理在编译期就曾经实现,而动静代理在运行时实现动态代理只能代理一个类,而动静代理能够代理多个类都依赖与接口动态代理扩大难以及难以保护,而动静代理代理了接口中的所有的办法,达到了通用性。因为动静代理代理了接口中的所有办法,如果指标对象须要扩大接口办法且不依赖代理对象就须要关注代理的实现细节。4. CgLig 代理cglib全称Code Generation Libary,即 代码生成库,能可能在运行时生成子类对象从而达到对指标对象扩大的性能。 ...

November 27, 2022 · 3 min · jiezi

关于aop:AOP如何实现及实现原理

1. AOP简介置信大家或多或少的理解过AOP,都晓得它是面向切面编程,在网上搜寻能够找到很多的解释。这里我用一句话来总结:AOP是可能让咱们在不影响原有性能的前提下,为软件横向扩大性能。 那么横向扩大怎么了解呢,咱们在WEB我的项目开发中,通常都恪守三层准则,包含管制层(Controller)->业务层(Service)->数据层(dao),那么从这个构造下来的为纵向,它具体的某一层就是咱们所说的横向。咱们的AOP就是能够作用于这某一个横向模块当中的所有办法。 咱们在来看一下AOP和OOP的区别:AOP是OOP的补充,当咱们须要为多个对象引入一个公共行为,比方日志,操作记录等,就须要在每个对象中援用公共行为,这样程序就产生了大量的反复代码,应用AOP能够完满解决这个问题。 接下来介绍一下提到AOP就必须要理解的知识点: 切面:拦截器类,其中会定义切点以及告诉切点:具体拦挡的某个业务点。告诉:切面当中的办法,申明告诉办法在指标业务层的执行地位,告诉类型如下: 1. 前置告诉:@Before 在指标业务办法执行之前执行2. 后置告诉:@After 在指标业务办法执行之后执行3. 返回告诉:@AfterReturning 在指标业务办法返回后果之后执行4. 异样告诉:@AfterThrowing 在指标业务办法抛出异样之后5. 盘绕告诉:@Around 功能强大,可代替以上四种告诉,还能够控制目标业务办法是否执行以及何时执行2. 代码中实现举例下面曾经大略的介绍了AOP中须要理解的基本知识,也晓得了AOP的益处,那怎么在代码中实现呢?给大家举个例子:咱们当初有个学校管理系统,曾经实现了对老师和学生的增删改,又新来个需要,说是对老师和学生的每次增删改做一个记录,到时候校长能够查看记录的列表。那么问题来了,怎么样解决是最好的解决办法呢?这里我列举了三种解决办法,咱们来看下他的优缺点。 -最简略的就是第一种办法,咱们间接在每次的增删改的函数当中间接实现这个记录的办法,这样代码的反复度太高,耦合性太强,不倡议应用。 -其次就是咱们最长应用的,将记录这个办法抽离进去,其余的增删改调用这个记录函数即可,显然代码反复度升高,然而这样的调用还是没有升高耦合性。 -这个时候咱们想一下AOP的定义,再想想咱们的场景,其实咱们就是要在不扭转原来增删改的办法,给这个零碎减少记录的办法,而且作用的也是一个层面的办法。这个时候咱们就能够采纳AOP来实现了。 咱们来看下代码的具体实现: 首先我定义了一个自定义注解作为切点@Target(AnnotationTarget.FUNCTION) //注解作用的范畴,这里申明为函数@Order(Ordered.HIGHEST_PRECEDENCE) //申明注解的优先级为最高,假如有多个注解,先执行这个annotation class Hanler(val handler: HandlerType) //自定义注解类,HandlerType是一个枚举类型,外面定义的就是学生和老师的增删改操作,在这里就不展现具体内容了接下来就是要定义切面类了 @Aspect //该注解申明这个类为一个切面类@Componentclass HandlerAspect{ @Autowired private lateinit var handlerService: HandlerService @AfterReturning("@annotation(handler)") //当有函数正文了注解,将会在函数失常返回后在执行咱们定义的办法 fun hanler(hanler: Hanler) { handlerService.add(handler.operate.value) //这里是真正执行记录的办法 }}最初就是咱们原本的业务办法了 /** * 删除学生办法 */@Handler(operate= Handler.STUDENT_DELETE) //当执行到删除学生办法时,切面类就会起作用了,当学生失常删除后就会执行记录办法,咱们就能够看到记录办法生成的数据fun delete(id:String) {studentService.delete(id)}AOP实现原理咱们当初理解了代码中如何实现,那么AOP实现的原理是什么呢?之前看了一个博客说到,提到AOP大家都晓得他的实现原理是动静代理,显然我之前就是不晓得的,哈哈,然而置信阅读文章的你们肯定是晓得的。讲到动静代理就不得不说代理模式了, 代理模式的定义:给某一个对象提供一个代理,并由代理对象管制对原对象的援用。代理模式蕴含如下角色:subject:形象主题角色,是一个接口。该接口是对象和它的代理共用的接口; RealSubject:实在主题角色,是实现形象主题接口的类; Proxy:代理角色,外部含有对实在对象RealSubject的援用,从而能够操作实在对象。代理对象提供与实在对象雷同的接口,以便代替实在对象。同时,代理对象能够在执行实在对象操作时,附加其余的操作,相当于对实在对象进行封装。如下图所示: 那么代理又分为动态代理和动静代理,这里写两个小的demo,动静代理采纳的就是JDK代理。举个例子就是当初一个班上的学生须要交作业,当初由班长代理交作业,那么班长就是代理,学生就是被代理的对象。 3.1 动态代理首先,咱们创立一个Person接口。这个接口就是学生(被代理类),和班长(代理类)的公共接口,他们都有交作业的行为。这样,学生交作业就能够让班长来代理执行。 /** * Created by Mapei on 2018/11/7 * 创立person接口 */public interface Person { //交作业 void giveTask();}Student类实现Person接口,Student能够具体实施交作业这个行为 ...

November 23, 2022 · 3 min · jiezi

关于aop:because-it-is-a-JDK-dynamic-proxy-that-implements-问题

景象形容以下的SpringBoot 工程在启动的时候会启动失败。(SpringBoot 1.5.7-RELEASE)package com.example.demo;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.transaction.annotation.EnableTransactionManagement;@SpringBootApplication@EnableTransactionManagementpublic class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }}package com.example.demo.controller;import com.example.demo.service.DemoServiceImpl;import javax.annotation.Resource;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class DemoController { @Resource private DemoServiceImpl demoService; @RequestMapping("/test") public String test() { return demoService.demo(); }}package com.example.demo.service;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Service("demoService")public class DemoServiceImpl implements DemoService{ @Override @Transactional public String demo() { return "DemoServiceImpl"; }}package com.example.demo.service;public interface DemoService { String demo();}报错信息***************************APPLICATION FAILED TO START***************************Description:The bean 'demoService' could not be injected as a 'com.example.demo.service.DemoServiceImpl' because it is a JDK dynamic proxy that implements: com.example.demo.service.DemoServiceAction:Consider injecting the bean as one of its interfaces or forcing the use of CGLib-based proxies by setting proxyTargetClass=true on @EnableAsync and/or @EnableCaching.如果只是解决如果不去深究起因,只是须要解决这个异样。那么有以下几种形式。只需实现其中任意一个即可。1. 批改EnableTransactionManagement注解参数DemoApplication 上的注解 @EnableTransactionManagement 变成 @EnableTransactionManagement(proxyTargetClass = false)2. 在application.properties配置文件中减少配置spring.aop.proxy-target-class=true3. 注入bean 的时候变更为应用其接口(最举荐)@Resourceprivate DemoServiceImpl demoService;改成: ...

April 28, 2022 · 1 min · jiezi

关于aop:Spring-AOP原理上

SpringAOP工作原理-上作为 Spring 体系里的大块头,AOP用起来是很爽,然而问你它是怎么实现的,你就懵逼。嘿嘿嘿 ~ 还是从 SpringBoot 的启动流程来讲起,看看定义切面后的启动流程。 先看咱们的测试例子: TestController.java : LogAspect.java : 接下来,从启动流程看,TestController 和 LogAspect是怎么创立的。 咱们找到 AbstractAutowireCapableBeanFactory#initializeBean() 办法,如图: 别问我怎么找到这个办法的,我不会通知你是打了九九八十一个断点调进去的 =。= 进入 applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName) 办法,看下参数: 遍历到 AnnotationAwareAspectJAutoProxyCreator 这个processor的时候,debug进去,能够看到: 这个 wrapIfNecessary()办法就很要害了,再点进去: 能够看到这一步里,先是找到所有的告诉器(拦截器或者增强器),而后接下来创立proxy: 点击createProxy(),进入,看外围代码代码: protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { …… ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); …… Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); proxyFactory.addAdvisors(advisors); proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } ClassLoader classLoader = getProxyClassLoader(); if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) { classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader(); } return proxyFactory.getProxy(classLoader); }能够看到,这里都是对 proxyFactory 进行设置的。点击 proxyFactory.getProxy(classLoader) ,始终追到 ...

January 14, 2022 · 1 min · jiezi

关于aop:AOP-概念篇

Spring AOP 系列的第一篇 先介绍一下 AOP 相干的一些概念。 呈现的契机在事实中、咱们常常须要记录重要操作的流水以及打印相干的日志到日志文件 // 微信公众号:CoderLipublic class BizService01 { public void dealBiz(BizDto bizDto) { // 脱敏打印 + 统计上报到经营零碎 record(bizDto); // 业务操作 } private void record(BizDto bizDto){ // ..... }}当存在 n 多个这样的 Service 的时候、咱们进一步的操作可能是:将其抽取到一个公共的中央进行记录保护 // 微信公众号:CoderLipublic class BizService01 { public void dealBiz(BizDto bizDto) { // 脱敏打印 + 统计上报到经营零碎 RecordUtils.record(bizDto); // 业务操作 } }再进一步、咱们可能应用模板办法来设计。子类继承该根底服务 // 微信公众号:CoderLipublic abstract class BaseBizService { public void dealBiz(BizDto bizDto){ // 脱敏打印 + 统计上报到经营零碎 if (isRecord()) { RecordUtils.record(bizDto); } readDealBiz(bizDto); } protected abstract void readDealBiz(BizDto bizDto); protected boolean isRecord(){ return true; }}这样子貌似是能解决对立解决非核心业务、然而如果咱们还须要进行其余的一些非核心业务解决的时候、比如说、权限测验、性能监控等、并且这些非核心业务他们的程序、在每一个业务场景中、程序可能不一样、是否执行这些非核心业务也是不确定的、那么单纯靠模板办法来解决的话、会显得十分费劲、甚至能够说、这种变动的流程不适宜模板办法了曾经。 ...

December 18, 2021 · 2 min · jiezi

关于aop:一文带你搞定AOP切面

摘要:AOP在spring中又叫“面向切面编程”,是对传统咱们面向对象编程的一个补充,次要操作对象就是“切面”,能够简略的了解它是贯通于办法之中,在办法执行前、执行时、执行后、返回值后、异样后要执行的操作。本文分享自华为云社区《一篇文搞懂《AOP面向切面编程》是一种什么样的体验?》,作者: 灰小猿。 一、什么是Spring的AOP?AOP在spring中又叫“面向切面编程”,它能够说是对传统咱们面向对象编程的一个补充,从字面上顾名思义就能够晓得,它的次要操作对象就是“切面”,所以咱们就能够简略的了解它是贯通于办法之中,在办法执行前、执行时、执行后、返回值后、异样后要执行的操作。相当于是将咱们本来一条线执行的程序在两头切开退出了一些其余操作一样。 在利用AOP编程时,依然须要定义公共性能,但能够明确的定义这个性能利用在哪里,以什么形式利用,并且不用批改受影响的类。这样一来横切关注点就被模块化到非凡的类里——这样的类咱们通常就称之为“切面”。 例如上面这个图就是一个AOP切面的模型图,是在某一个办法执行前后执行的一些操作,并且这些操作不会影响程序自身的运行。 AOP切面编程中有一个比拟业余的术语,我给大家罗切出来了: 当初大略的理解了AOP切面编程的基本概念,接下来就是实际操作了。 二、AOP框架环境搭建1、导入jar包目前比拟风行且罕用的AOP框架是AspectJ,咱们在做SSM开发时用到的也是AspectJ,应用该框架技术就须要导入它所反对的jar包, aopalliance.jaraspectj.weaver.jarspring-aspects.jar对于SSM开发所应用的所有jar包和相干配置文件我都已将帮大家筹备好了! 点击链接下载就能用。【全网最全】SSM开发必备依赖-Jar包、参考文档、罕用配置 2、引入AOP名称空间应用AOP切面编程时是须要在容器中引入AOP名称空间的, 3、写配置其实在做AOP切面编程时,最常应用也必备的一个标签就是,< aop:aspectj-autoproxy></aop:aspectj-autoproxy>, 咱们在容器中须要增加这个元素,当Spring IOC容器侦测到bean配置文件中的< aop:aspectj-autoproxy>元素时,会主动为与AspectJ切面匹配的bean创立代理。同时在当初的spring中应用AOP切面有两种形式,别离是AspectJ注解或基于XML配置的AOP, 上面我顺次和大家介绍一下这两种形式的应用。 三、基于AspectJ注解的AOP开发在上一篇文章中我也和大家将了对于spring中注解开发的弱小,所以对于AOP开发咱们同样也能够应用注解的模式来进行编写,上面我来和大家介绍一下如何应用注解形式书写AOP。 1、五种告诉注解首先要在Spring中申明AspectJ切面,只须要在IOC容器中将切面申明为bean实例。 当在Spring IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些与 AspectJ切面相匹配的bean创立代理。 在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,它往往要蕴含很多告诉。告诉是标注有某种注解的简略的Java办法。 AspectJ反对5种类型的告诉注解: @Before:前置告诉,在办法执行之前执行@After:后置告诉,在办法执行之后执行@AfterRunning:返回告诉,在办法返回后果之后执行@AfterThrowing:异样告诉,在办法抛出异样之后执行@Around:盘绕告诉,围绕着办法执行2、切入点表达式标准这五种告诉注解前面还能够跟特定的参数,来指定哪一个切面办法在哪一个办法执行时触发。那么具体操作是怎么样的呢? 这里就须要和大家介绍一个名词:“切入点表达式”,通过在注解中退出该表达式参数,咱们就能够通过表达式的形式定位一个或多个具体的连接点, 切入点表达式的语法格局标准是: execution([权限修饰符] [返回值类型] [简略类名/全类名] [办法名] ([参数列表])) 其中在表达式中有两个罕用的特殊符号: 星号“ * ”代表所有的意思,星号还能够示意任意的数值类型 “.”号:“…”示意任意类型,或任意门路下的文件, 在这里举出几个例子:表达式: execution( com.atguigu.spring.ArithmeticCalculator.(…)) 含意: ArithmeticCalculator接口中申明的所有办法。第一个“”代表任意修饰符及任意返回值。第二个“”代表任意办法。“…”匹配任意数量、任意类型的参数。若指标类、接口与该切面类在同一个包中能够省略包名。 表达式: execution(public ArithmeticCalculator.(…)) 含意: ArithmeticCalculator接口的所有私有办法 表达式: execution(public double ArithmeticCalculator.*(…)) 含意: ArithmeticCalculator接口中返回double类型数值的办法 表达式: execution(public double ArithmeticCalculator.*(double, …)) 含意: 第一个参数为double类型的办法。“…” 匹配任意数量、任意类型的参数。 表达式: execution(public double ArithmeticCalculator.*(double, double)) 含意: 参数类型为double,double类型的办法 ...

August 3, 2021 · 4 min · jiezi

关于aop:Spring-AOP相同切入点抽取和通知的优先级

雷同切入点的抽取 @Pointcut(value="execution(* com.zong.spring.User.add(..))")public void pointcut1(){}@Before(value="pointcut1()")public void before(){}@After(value="pointcut1()")public void after(){}告诉的优先级:当多个加强类对同一个办法进行加强时,应用@Order注解增加在加强类下面示意优先级,数字越小优先级越高 @Component@Aspect@Order(1)public class Proxy1{}@Component@Aspect@Order(3)public class Proxy2{}

June 18, 2021 · 1 min · jiezi

关于aop:AOP进行通知切面的步骤

1、根本配置1)在Spring的配置文件或配置类中,开启注解扫描2)应用注解创立被加强类和加强类对象3)在加强类上增加注解@Aspect4)在Spring配置文件中开启生成代理对象xml配置文件形式:<aop:aspectj-autoproxy></aop:aspectj-autoproxy> 2、配置不同类型的告诉加强类(代理类) @Component@Aspectpublic class UserProxy{ //前置告诉 @Before(value="execution(* com.zong.spring.User.add(..))") public void before(){ System.out.println("before..."); } //后置告诉 @AfterReturning(value="execution(* com.zong.spring.User.add(..))") public void afterReturning(){ System.out.println("afterReturning..."); } //最终告诉 @After(value="execution(* com.zong.spring.User.add(..))") public void after(){ System.out.println("after..."); } //异样告诉 @AfterThrowing(value="execution(* com.zong.spring.User.add(..))") public void afterThrowing(){ System.out.println("afterThrowing..."); } //盘绕告诉 @Around(value="execution(* com.zong.spring.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint){ System.out.println("盘绕之前..."); //执行被加强的办法 proceedingJoinPoint.proceed(); System.out.println("盘绕之后..."); }}执行后果 盘绕之前...before..add..盘绕之后..after...afterReturning...异样执行后果 盘绕之前...Before...after...afterThrowing...

June 17, 2021 · 1 min · jiezi

关于aop:Spring中AOP操作

1、Spring个别是基于AspectJ实现AOP操作1)AspectJ是一个独立的AOP框架,非Spring的组成部分 2、AspectJ实现AOP操作的两种形式:1)基于XML配置文件实现2)基于注解实现(罕用) 3、导入AsepctJ须要的依赖包 4、切入点表达式1)作用:晓得对哪个类的哪个办法进行加强2)语法结构:execution(权限修饰符[类全门路]办法名称)示例1:execution( com.zong.spring.BookDao.add(..)) 示意所有权限,返回类型省略示例2execution( com.zong.spring.BookDao.(..))示例3execution( com.zong.spring..*(..))

June 17, 2021 · 1 min · jiezi

关于aop:AOP术语

1、连接点类中哪些办法能够被加强,这些办法就称为连接点 2、切入点理论被加强的办法,就称为切入点 3、告诉(加强)理论加强的逻辑局部就称为告诉分为:前置告诉后置告诉盘绕告诉(蕴含前置、后置)异样告诉(异样时执行)最终告诉(相似finally) 4、切面把告诉利用到切入点的过程就叫切面,是个动作

June 17, 2021 · 1 min · jiezi

关于aop:AOP原理

不批改原代码的前提下,在原性能上减少一个新性能,例如在登陆性能中新增权限性能管制 底层原理:动静代理两种状况1)有接口 应用JDK动静代理创立接口实现类代理对象2)没有接口 应用CGLIB创立以后类子类的代理对象 JDK动静代理实现代码示例 public interface UserDao { public int add(int a, int b);}public class UserDaoImpl implements UserDao { @Override public int add(int a, int b) { // TODO Auto-generated method stub System.out.println("执行"+a+"+"+b); return a+b; }}public class JdkProxy { public static void main(String[] args) { Class[] interfaces = {UserDao.class}; UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao)Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), interfaces, new UserDaoProxy(userDao)); int result = dao.add(1, 2); }}//创立代理对象class UserDaoProxy implements InvocationHandler{ //把此次加强的对象传进来 private Object obj; public UserDaoProxy(Object obj) { this.obj = obj; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ //办法之前 System.out.println("办法之前执行"+method.getName()+"传递的参数:"+Arrays.toString(args)); //被加强的办法 Object res = method.invoke(obj, args); //办法之后 System.out.println("办法之后执行"+obj); return res; }}

June 16, 2021 · 1 min · jiezi

关于aop:如何低侵入的记录调用日志

前言前阵子敌人他老大叫他实现这么一个性能,就是低侵入的记录接口每次的申请响应日志,而后并统计每次申请调用的胜利、失败次数以及响应耗时,过后敌人的实现思路是在每个业务的controller的办法上加一个自定义注解,而后写一个aop,以这个自定义注解为pointcut来记录日志。 这种AOP+注解来实现日志记录,应该是很常见的实现形式。然而敌人在落地的时候,发现我的项目要加自定义注解的中央太多。前面我就跟他说,那就不写注解,间接以形如下 execution(* com.github.lybgeek.logaop.service..*.*(..))这样不行吗?他说他这个性能他老大是心愿给各个项目组应用,像我下面的办法,预计行不通,我就问他说为啥行不通,他说各个我的项目的包名都不一样,如果我那种思路,他就说这样在代码里poincut不得要这么写 execution(* com.github.lybgeek.a.service..*.*(..) || * com.github.lybgeek.b.service..*.*(..) || * com.github.lybgeek.c.service..*.*(..) )这样每次新加要日志记录,都得改切面代码,还不如用自定注解来的好。听完他的解释,我一脸黑人问号脸。于是就趁着5.1假期期间,写个demo实现下面的需要 业务场景低侵入的记录接口每次的申请响应日志,而后并统计每次申请调用的胜利、失败次数以及响应耗时这个业务需要应该算是很简略,实现的难点就在于低侵入,提到低侵入,我首先想到是使用者无需写代码,或者只需写大量代码或者仅需简略配置一下,最好能做到业务无感知。 实现伎俩我这边提供2种思路 javaagent + byte-buddyspringboot主动拆卸 + AOPjavaagent1、什么是javaagentjavaagent是一个简略优雅的java agent,利用java自带的instrument个性+javassist/byte-buddy字节码能够实现对类的拦挡或者加强。 javaAgent 是运行在 main办法之前的拦截器,它内定的办法名叫 premain ,也就是说先执行 premain 办法而后再执行 main 办法 2、如何实现一个javaagenta、必须实现premain办法示例: public class AgentDemo { public static void premain(String agentArgs, Instrumentation inst) { System.out.println("agentArgs : " + agentArgs); inst.addTransformer(new DefineTransformer(),true); } static class DefineTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("premain load Class:" + className); return classfileBuffer; } }}b、在META-INF目录增加MANIFEST.MF文档,内容形如下 ...

May 5, 2021 · 3 min · jiezi

关于springboot:模仿Cacheables实现方法拦截

背景在SpringBoot开发中,通过@Cacheable注解便能够实现办法级别缓存,如下 @GetMapping(value = "/user/detail") @Cacheable(value = "user", key = "#uid") public User deteail(@RequestParam(value = "uid") String uid) {...}Cacheable的逻辑 如果缓存中没有key为#uid的数据就执行detail函数并且把后果放到缓存中如果缓存中存在key为#uid的数据就间接返回,不执行detail函数通过Cacheable咱们能够十分不便的在代码中应用缓存,那么Cacheable是如何实现的,一开始认为是通过AOP实现,然而通过查看源码,发现跟AOP又有点不一样。 Cacheable原理如果要应用Cacheable就必须在启动类上加上@EnableCaching(),该注解定义如下 @Import(CachingConfigurationSelector.class)public @interface EnableCaching {...}CachingConfigurationSelector继承了AdviceModeImportSelector,次要看selectImports办法 public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching>{ //..... @Override public String[] selectImports(AdviceMode adviceMode) { switch (adviceMode) { case PROXY: return getProxyImports(); case ASPECTJ: return getAspectJImports(); default: return null; } } private String[] getProxyImports() { List<String> result = new ArrayList<>(3); result.add(AutoProxyRegistrar.class.getName()); result.add(ProxyCachingConfiguration.class.getName()); if (jsr107Present && jcacheImplPresent) { result.add(PROXY_JCACHE_CONFIGURATION_CLASS); } return StringUtils.toStringArray(result); }}判断实现模式是基于代理(PROXY)还是ASPECTJ,Spring-AOP模式应用时代理模式,所以这边会走到getProxyImports这里getProxyImports中退出两个代理类,咱们次要看ProxyCachingConfiguration@Configuration@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public class ProxyCachingConfiguration extends AbstractCachingConfiguration { @Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor() { BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor(); advisor.setCacheOperationSource(cacheOperationSource()); advisor.setAdvice(cacheInterceptor()); if (this.enableCaching != null) { advisor.setOrder(this.enableCaching.<Integer>getNumber("order")); } return advisor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public CacheOperationSource cacheOperationSource() { return new AnnotationCacheOperationSource(); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public CacheInterceptor cacheInterceptor() { CacheInterceptor interceptor = new CacheInterceptor(); interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager); interceptor.setCacheOperationSource(cacheOperationSource()); return interceptor; }}这里创立了几个类,咱们重点关注以下两个类 ...

March 15, 2021 · 2 min · jiezi

关于aop:代理模式AOP绪论

本篇能够了解为Spring AOP的铺垫,以前大略会用Spring框架提供的AOP,然而对AOP还不是很理解,于是打算零碎的梳理一下代理模式和AOP。简略的谈一下代理软件世界是对事实世界形象,所以软件世界的某些概念也不是凭空产生,也是从事实世界中脱胎而来,就像多态这个概念一样,Java官网出的指导书 《The Java™ Tutorials》在讲多态的时候,首先提到多态首先是一个生物学的概念。那么设计模式中的代理模式,咱们也能够认为是从事实世界中形象而来,在事实世界代理这个词也是很常见的,比方房产中介代理房东的房子,经纪人代理明星谈商演。基本上代理人都在肯定范畴内代理被代表人的事务。 那咱们为什么须要代理模式呢? 咱们事实上也能够从事实世界去寻找答案,为什么房东要找房产中介呢? 因为房东也是人,也有本人的生存,不可能将全身心都搁置在将本人的房屋出租上,不违心做扭转。那么软件世界的代理模式,我认为也是有着同样的起因,原来的某个对象须要再度承接一个性能,然而咱们并不违心批改对象从属的类,起因可能有很多,兴许旧的对象曾经对外应用,一旦批改兴许会有其余意想不到的反馈,兴许咱们没方法批改等等。起因有很多,同时对一个过来的类批改也不合乎开闭准则,对扩大凋谢,对批改关闭。于是咱们就心愿在不批改指标对象的性能前提下,对指标的性能进行扩大。 网上的大多数博客在讲代理模式的时候,大多都会从一个接口动手,而后需要是扩大实现对该接口实现类的加强,而后写代码演示。像是上面这样: public interface IRentHouse { /** * 实现该接口的类将可能对外提供房子 */ void rentHouse();}public class Landlord implements IRentHouse { @Override public void rentHouse() { System.out.println(" 我向你提供房子..... "); }}public class HouseAgent implements IRentHouse { @Override public void rentHouse() { System.out.println("在该类的办法之前执行该办法"); IRentHouse landlord = new Landlord(); landlord.rentHouse(); System.out.println("在该类的办法之后执行该办法"); }}public class Test { public static void main(String[] args) { IRentHouse landlord = new HouseAgent(); landlord.rentHouse(); }}测试后果: 许多博客在刚讲动态代理的时候通常会从这里动手,在不扭转原来类同时,对原来的类进行增强。对,这算是一个相当事实的需要,尤其是在原来的类在零碎中应用的比拟多的状况下,且运行比较稳定,一旦改变就算是再小心翼翼,也无奈保障对原来的零碎一点影响都没有,最好的办法是不改,那如何加强原有的指标对象,你新加强的个别就是要满足新的调用者的需要,那我就新增一个类吧,供你调用。很完满,那问题又来了,为什么各个博客都是从接口登程呢? ...

February 27, 2021 · 2 min · jiezi

关于aop:AOP的常用注解

@Aspect:作用:把以后类申明为切面类。@Before:作用:把以后办法看成是前置告诉。属性: value:用于指定切入点表达式,还能够指定切入点表达式的援用。 讲师:陈飞@AfterReturning作用:把以后办法看成是后置告诉。属性: value:用于指定切入点表达式,还能够指定切入点表达式的援用。@AfterThrowing作用:把以后办法看成是异样告诉。属性: value:用于指定切入点表达式,还能够指定切入点表达式的援用。@After作用:把以后办法看成是始终告诉。属性: value:用于指定切入点表达式,还能够指定切入点表达式的援用。@Around作用:把以后办法看成是盘绕告诉。属性: value:用于指定切入点表达式,还能够指定切入点表达式的援用。@Pointcut作用:指定切入点表达式属性: value:指定表达式的内容

January 14, 2021 · 1 min · jiezi

关于aop:AOP简介与作用

AOP简介与作用 AOPAspect Oriented Programming(AOP)是较为热门的一个话题。AOP,国内大抵译作“面向切面编程”。 “面向切面编程”,这样的名字并不是非常容易了解,且容易产生一些误导。笔者不止一次听到相似“OOP/OOD11行将掉队,AOP是新一代软件开发形式”这样的发言。而在AOP中,Aspect的含意,可能更多的了解为“切面”比拟适合。所以笔者更偏向于“面向切面编程”的译法。能够通过预编译形式和运行期动静代理实现在不批改源代码的状况下给程序动静对立增加性能的一种技术。AOP理论是GoF设计模式的连续,设计模式手不释卷谋求的是调用者和被调用者之间的解耦,进步代码的灵活性和可扩展性,AOP能够说也是这种指标的一种实现。利用对象只实现它们应该做的——实现业务逻辑——仅此而已。它们并不负责(甚至是意识)其它的零碎级关注点,例如日志或事务反对。AOP次要性能日志记录,性能统计,安全控制,事务处理,异样解决等等wn及扩大 AOP次要用意将日志记录,性能统计,安全控制,事务处理,异样解决等代码从业务逻辑代码中划分进去,通过对这些行为的拆散,咱们心愿能够将它们独立到非领导业务逻辑的办法中,进而扭转这些行为的时候不影响业务逻辑的代码。arkdown Extra** 定义列表语法: 代码块假如在一个利用零碎中,有一个共享的数据必须被并发同时拜访,首先,将这个数据封装在数据对象中,称为Data Class,同时,将有多个拜访类,专门用于在同一时刻拜访这同一个数据对象。为了实现上述并发拜访同一资源的性能,须要引入锁Lock的概念,也就是说,某个时刻,当有一个拜访类拜访这个数据对象时,这个数据对象必须上锁Locked,用完后就立刻解锁unLocked,再供其它拜访类拜访。应用传统的编程习惯,咱们会创立一个抽象类,所有的拜访类继承这个形象父类,如下:abstract class Worker {abstract void locked();abstract void accessDataObject();abstract void unlocked();}accessDataObject()办法须要有“锁”状态之类的相干代码。 Java只提供了单继承,因而具体拜访类只能继承这个父类,如果具体拜访类还要继承其它父类,比方另外一个如Worker的父类,将无奈不便实现。 重用被打折扣,具体拜访类因为也蕴含“锁”状态之类的相干代码,只能被重用在相干有“锁”的场合,重用范畴很窄。 认真钻研这个利用的“锁”,它其实有下列个性:“锁”性能不是具体拜访类的首要或次要性能,拜访类次要性能是拜访数据对象,例如读取数据或更改动作。“锁” “锁”性能其实是这个零碎的一个纵向切面,波及许多类、许多类的办法。如上图: `因而,一个新的程序结构应该是关注零碎的纵向切面,例如这个利用的“锁”性能,这个新的程序结构就是aspect(方面)在这个利用中,“锁”方面(aspect)应该有以下职责:` * 1* 2提供一些必备的性能,对被拜访对象实现加锁或解锁性能。以保障所有在批改数据对象的操作之前可能调用lock()加锁,在它应用实现后,调用unlock()解锁。 AOP利用范畴很显著,AOP非常适合开发J2EE容器服务器,JBoss 4.0正是应用AOP框架进行开发。具体性能如下:Authentication 权限Caching缓存Context passing内容传递Error handling 错误处理Lazy loading 延时加载Debugging 调试logging, tracing, profiling and monitoring 记录跟踪 优化 校准Performance optimization性能优化Persistence 长久化Resource pooling资源池Synchronization 同步Transactions事务【AOP有必要吗?】 `当然,上述利用范例在没有应用AOP状况下,也失去了解决,例如JBoss 3.XXX也提供了上述利用性能,并且没有应用AOP。 然而,应用AOP能够让咱们从一个更高的抽象概念来了解软件系统,AOP兴许提供一种有价值的工具。能够这么说:因为应用AOP构造,JBoss 4.0的源码要比JBoss 3.X容易了解多了,这对于一个大型简单零碎来说是十分重要的。 从另外一个方面说,如同不是所有的人都须要关怀AOP,它可能是一种架构设计的抉择,如果抉择J2EE零碎,AOP关注的上述通用方面都曾经被J2EE容器实现了,J2EE利用零碎开发者可能须要更多地关注行业利用方面aspect。 传统的程序通常体现出一些不能天然地适宜繁多的程序模块或者是几个严密相干的程序模块的行为,AOP 将这种行为称为横切,它们逾越了给定编程模型中的典型职责界线。横切行为的实现都是扩散的,软件设计师会发现这种行为难以用失常的逻辑来思考、实现和更改。最常见的一些横切行为如上面这些:` * 1* 2* 3* 4* 5* 6* 71、日志记录,跟踪,优化和监控2、事务的解决3、长久化4、性能的优化5、资源池,如数据库连接池的治理6、零碎对立的认证、权限治理等7、利用零碎的异样捕获及解决8、针对具体行业利用的横切行为 `后面几种横切行为都曾经失去了亲密的关注,也呈现了各种有价值的利用,但兴许今后几年,AOP 对针对具体行业利用的奉献会成为令人关注的焦点。` * 1AOP实现我的项目AOP是一个概念,并没有设定具体语言的实现,它能克服那些只有单继承个性语言的毛病(如Java),AOP具体实现有以下几个我的项目: ...

January 14, 2021 · 1 min · jiezi

关于aop:SpringBoot强化篇八-Spring-AOP

Spring AOP简介AOP(Aspect Orient Programming)是一种设计思维,是软件设计畛域中的面向切面编程,它是面向对象编程(OOP)的一种补充和欠缺。它以通过预编译形式和运行期动静代理形式,实现在不批改源代码的状况下给程序动静对立增加额定性能的一种技术。AOP与OOP字面意思相近,但其实两者齐全是面向不同畛域的设计思维。理论我的项目中咱们通常将面向对象了解为一个动态过程(例如一个零碎有多少个模块,一个模块有哪些对象,对象有哪些属性),面向切面的运行期代理形式,了解为一个动静过程,能够在对象运行时动静织入一些扩大性能或管制对象执行。 AOP 利用场景剖析?理论我的项目中通常会将零碎分为两大部分,一部分是外围业务,一部分是非核业务。在编程实现时咱们首先要实现的是外围业务的实现,非核心业务个别是通过特定形式切入到零碎中,这种特定形式个别就是借助AOP进行实现。 AOP就是要基于OCP(开闭准则),在不扭转原有系统核心业务代码的根底上动静增加一些扩大性能并能够"管制"对象的执行。例如AOP利用于我的项目中的日志解决,事务处理,权限解决,缓存解决等等。 Spring AOP 利用原理剖析Spring AOP底层基于代理机制(动静形式)实现性能扩大: 如果指标对象(被代理对象)实现接口,则底层能够采纳JDK动静代理机制为指标对象创立代理对象(指标类和代理类会实现独特接口)。如果指标对象(被代理对象)没有实现接口,则底层能够采纳CGLIB代理机制为指标对象创立代理对象(默认创立的代理类会继承指标对象类型)。阐明:Spring boot2.x 中AOP当初默认应用的CGLIB代理,如果须要应用JDK动静代理能够在配置文件(applicatiion.properties)中进行如下配置: #cglib aop proxy#spring.aop.proxy-target-class=true#jdk aop proxyspring.aop.proxy-target-class=falseSpring 中AOP 相干术语剖析切面(aspect): 横切面对象,个别为一个具体类对象(能够借助@Aspect申明)。告诉(Advice):在切面的某个特定连接点上执行的动作(扩大性能),例如around,before,after等。连接点(joinpoint):程序执行过程中某个特定的点,个别指向被拦挡到的指标办法。切入点(pointcut):对多个连接点(Joinpoint)一种定义,个别能够了解为多个连接点的汇合。 Spring AOP疾速实际我的项目创立及配置第一步创立maven我的项目或在已有我的项目根底上增加AOP启动依赖: <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency>第二步定义业务层接口 package com.cy.pj.common.service;public interface MailService { boolean sendMail(String msg);}第三步定义业务层实现类 @Servicepublic class MailServiceImpl implements MailService{ @Override public boolean sendMail(String msg) {//ocp(开闭准则-->对扩大凋谢,对批改敞开) long t1=System.currentTimeMillis(); System.out.println("send->"+msg); long t2=System.currentTimeMillis(); System.out.println("send time:"+(t2-t1)); return true; }}咱们本人计算了执行工夫,然而违反了ocp准则,所以须要无侵入式扩大这个记录执行工夫的性能。咱们在这个类中增加外部类来实现两种形式的扩大。 package com.cy.pj.common.service;import org.springframework.stereotype.Service;@Servicepublic class MailServiceImpl implements MailService{ @Override public boolean sendMail(String msg) {//ocp(开闭准则-->对扩大凋谢,对批改敞开) //long t1=System.currentTimeMillis(); System.out.println("send->"+msg); //long t2=System.currentTimeMillis(); //System.out.println("send time:"+(t2-t1)); return true; }}//上面的两种设计理解?(基于原生形式实现性能扩大)//本人入手写子类重写父类办法进行性能扩大class TimeMailServiceImpl extends MailServiceImpl{//这种写法的原型就是CGLIB代理机制的形式(继承) @Override public boolean sendMail(String msg) { long t1=System.currentTimeMillis(); boolean flag=super.sendMail(msg); long t2=System.currentTimeMillis(); System.out.println("send time:"+(t2-t1)); return flag; }}//本人写兄弟类对指标对象(兄弟类)进行性能扩大,这种形式又叫组合class TimeMailServiceImpl2 implements MailService{//这种写法的原型就是JDK代理机制的形式(实现) private MailService mailService; public TimeMailServiceImpl2(MailService mailService){ this.mailService=mailService; } @Override public boolean sendMail(String msg) { long t1=System.currentTimeMillis(); boolean flag=mailService.sendMail(msg); long t2=System.currentTimeMillis(); System.out.println("send time:"+(t2-t1)); return flag; }}下来通过aop形式实现业务 ...

November 14, 2020 · 5 min · jiezi

关于aop:AOP

AOP概述1.什么是AOP?AOP是一种设计思维,在不批改源代码的状况下给程序动静对立增加额定性能的一种技术,如图: 2.AOP利用原理剖析AOP底层基于代理机制(动静形式)实现性能扩大:1):如果指标对象(被代理对象)实现接口,则底层能够采纳JDK动静代理机制为指标对象创立代理对象(指标类和代理类会实现独特接口)。2):如果指标对象(被代理对象)没有实现接口,则底层能够采纳CGLIB代理机制为指标对象创立代理对象(默认创立的代理类会继承指标对象类型)。如图: AOP默认应用CGLIB代理,如需应用JDK代理配置文件配置:spring.aop.proxy-target-class=false 基于JDK代理形式实现: 基于CGLIB代理形式实现: AOP相干术语:切面(aspect):个别为一个具体类(@Aspect注解正文)切入点() 告诉(Advice):在切面的某个特定点上执行的扩大 切入点(pointcut):应用@Pointcut注解进行切入点的形容,应用bean表达式定义切入点,语法:bean(spring容器中治理的某个bean的名字),bean表达式是一种粗粒度的切入点表达式(不能具体到bean中哪个办法)需写一个办法作为承载切入点 连接点(joinpoint):封装了切入点汇合办法中的某个正在执行的指标办法 AOP增加的依赖:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency> 注解阐明:@Aspect 注解用于标识或者形容AOP中的切面类型,基于切面类型构建的对象用于为指标对象进行性能扩大或控制目标对象的执行。@Pointcut注解用于形容切面中的办法,并定义切面中的切入点(基于特定表达式的形式进行形容),在本案例中切入点表达式用的是bean表达式,这个表达式以bean结尾,bean括号中的内容为一个spring治理的某个bean对象的名字。@Around注解用于形容切面中办法,这样的办法会被认为是一个盘绕告诉(外围业务办法执行之前和之后要执行的一个动作),@Aournd注解外部value属性的值为一个切入点表达式或者是切入点表达式的一个援用(这个援用为一个@PointCut注解形容的办法的办法名)。ProceedingJoinPoint类为一个连接点类型,此类型的对象用于封装要执行的指标方告诉类型(告诉-Advice形容的是一种扩大业务):1.@Before。(指标办法执行之前执行)2.@AfterReturning。(指标办法胜利完结时执行)3.@AfterThrowing。(指标办法异样完结时执行)4.@After。(指标办法完结时执行)5.@Around.(重点把握,指标办法执行前后都能够做业务拓展)(优先级最高) 阐明:在切面类中应用什么告诉,由业务决定,并不是说,在切面中要把所有告诉都写上。 切入点表达式加强:** bean表达式(重点) bean表达式个别利用于类级别,实现粗粒度的切入点定义,案例剖析: bean("userServiceImpl")指定一个userServiceImpl类中所有办法。bean("*ServiceImpl")指定所有后缀为ServiceImpl的类中所有办法。阐明:bean表达式外部的对象是由spring容器治理的一个bean对象,表达式外部的名字应该是spring容器中某个bean的name。 缺点:不能准确到具体方法,也不能针对于具体模块包中的办法做切入点设计 =========================================================== within表达式(理解) within表达式利用于类级别,实现粗粒度的切入点表达式定义,案例剖析: within("aop.service.UserServiceImpl")指定以后包中这个类外部的所有办法。within("aop.service.*") 指定当前目录下的所有类的所有办法。within("aop.service..*") 指定当前目录以及子目录中类的所有办法。within表达式利用场景剖析: 1)对所有业务bean都要进行性能加强,然而bean名字又没有规定。 2)按业务模块(不同包下的业务)对bean对象进行业务性能加强。 =========================================================== execution表达式(理解) execution表达式利用于办法级别,实现细粒度的切入点表达式定义,案例剖析: 语法:execution(返回值类型 包名.类名.办法名(参数列表))。 execution(void aop.service.UserServiceImpl.addUser())匹配addUser办法。execution(void aop.service.PersonServiceImpl.addUser(String)) 办法参数必须为String的addUser办法。execution( aop.service...*(..)) 万能配置。=========================================================== @annotation表达式(重点) @annotaion表达式利用于办法级别,实现细粒度的切入点表达式定义,案例剖析 @annotation(anno.RequiredLog) 匹配有此注解形容的办法。@annotation(anno.RequiredCache) 匹配有此注解形容的办法。其中:RequiredLog为咱们本人定义的注解,当咱们应用@RequiredLog注解润饰业务层办法时,零碎底层会在执行此办法时进行日扩大操作。 切面优先级设置实现切面的优先级须要借助@Order注解进行形容,数字越小优先级越高,默认优先级比拟低。列如:@Order(1),@Order(2)

November 12, 2020 · 1 min · jiezi

关于aop:Spring-AOP使用时的一些问题

在应用AOP的时候遇到了一些问题,特此记录一下 首先写一个罕用的AOP切片 切片类AopLogpackage com.mantis.aop.aspect;import com.fasterxml.jackson.databind.ObjectMapper;import com.mantis.aop.common.util.DataUtil;import eu.bitwalker.useragentutils.UserAgent;import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.stereotype.Component;import org.springframework.validation.BeanPropertyBindingResult;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.util.ArrayList;import java.util.Arrays;import java.util.Enumeration;import java.util.List;import java.util.stream.Collectors;/** * @Description:执行程序 失常程序 Around Before Method.invoke Around After AfterReturning * 异样程序 Around Before Method.invoke After AfterThrowing * @author: wei.wang * @since: 2020/4/4 13:47 * @history: 1.2020/4/4 created by wei.wang */@Aspect@Componentpublic class AopLog { private static Logger logger = LoggerFactory.getLogger(AopLog.class); /** * 定义切点,切点为com.smec.fin.controller包和子包里任意办法的执行和service层所有办法的执行 */ @Pointcut("execution(public * com.mantis.aop.controller..*.*(..))") public void log() { // 定义切点 } /** * 定义切点,切点为com.smec.fin.controller包和子包里任意办法的执行和service层所有办法的执行 */ @Pointcut("execution(public * com.mantis.aop.service.impl..*.*(..))") public void log2() { // 定义切点 } /** * 盘绕告诉 * * @param point * @return * @throws Throwable */ @Around("log2()") public Object aroundLog(ProceedingJoinPoint point) throws Throwable { logger.info("开始执行盘绕操作"); Object result = point.proceed(); logger.info("执行盘绕操作完结,返回值:{}", result); return result; }}服务类AopServiceImplpackage com.mantis.aop.service.impl;import com.mantis.aop.service.AopService;import org.springframework.aop.framework.AopContext;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;/** * @Description: * @author: wei.wang * @since: 2020/10/24 12:45 * @history: 1.2020/10/24 created by wei.wang */@Servicepublic class AopServiceImpl implements AopService { @Override public void testAop(String str) { System.out.println("com.mantis.aop.service.AopService.AopServiceImpl.testAop" + str); this.testAop2("testFinalMethod"); } @Override public void testAop2(String str) { //this.testFinalMethod("testFinalMethod"); System.out.println("com.mantis.aop.service.AopService.AopServiceImpl." + str); } public final void testFinalMethod(String str) { System.out.println("com.mantis.aop.service.AopService.AopServiceImpl.testFinalMethod" + str); } public void testFinalMethod2(String str) { System.out.println("com.mantis.aop.service.AopService.AopServiceImpl.testFinalMethod" + str); }}1.同办法运行问题在应用AOP时咱们发现存在同类中调用时切点生效问题,在执行时咱们发现只执行了testAop的切点,testAop2的切点没有执行,这是因为通过AOP代理后的对象都曾经不是原来的对象了,而是退出了加强办法的代理对象,应用代理对象调用时能够执行加强办法,然而这里是应用this调用的,也就是AopServiceImpl,因为不是代理对象就没有加强办法。 ...

October 24, 2020 · 3 min · jiezi

关于aop:aop的注解和具体内容

应用jdk代理形式 AOP默认应用的CGLIB代理spring.aop.proxy-target-class=false<!-- Aop 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>@Aspect//注解用于示意或者形容aop中的切面类型 用于为指标对象进行性能扩大或控制目标对象的执行//由此注解形容的类为springaop 中的一个切面类型此类型能够定义//1切入点 (Pointcut)办法(能够是多个)要进行性能扩大的一些点//2告诉advice办法能够是多个封装了扩大性能的一些办法在切入点办法之前货之后要执行的办法@Slf4j//打印日志信息@Component//是spring形容bean类的一个注解 用于通知spring这个类的实例由spring创立public class SysLogAspect { //在业务层 的imp小写复制过去 @Pointcut("bean(sysUserServiceImpl)")//注解哟凝固形容切面中的办法 定义切面中的切入点 定义这个类所有切入办法 //本注解以bean结尾 bean括号中的内容为一个spring治理的某个bean对象的名字 // public void logPointCut() {}//办法中不写任何内容 只是切入点表达式的一个载体 //aournd注解外部value属性的值 为一个切入点表达式一个援用 这个援用为一个@pointcut注解示意的办法的办法名 //ProceedingJoinPoint类为一个连接点类型,此类型的对象用于封装要执行的指标办法 //他只能用于@Around注解形容的办法参数。 @Around("logPointCut()")//注解以用于形容切面中办法 会被认为是一个盘绕告诉 和型业务 执行之前个之后要执行的一个动作 public Object around(ProceedingJoinPoint jp)//指标办法 throws Throwable{ long t1=System.currentTimeMillis();//执行工夫 try { Object result=jp.proceed();//最终可能要执行你的指标办法 long t2=System.currentTimeMillis();//执行工夫 log.info("指标办法{}执行时长{}",t2-t1);//{}这个在log.info外面代表占位符 return result; }catch(Throwable e) { log.error("指标办法呈现了点问题具体为:{}",e.getMessage());//记录日志 throw e; } }}aop 异样解决监听@Slf4j@Aspect@Componentpublic class SysExceptionAspect { /**此办法能够作为一个异样监控办法*/ @AfterThrowing(pointcut = "bean(*ServiceImpl)",throwing = "ex") public void handleException(JoinPoint jp,Throwable ex) { //通过连接点获取指标对象类型 Class<?> targetClass=jp.getTarget().getClass(); String className=targetClass.getName(); //通过连接点获取办法签名对象 MethodSignature s=(MethodSignature)jp.getSignature(); String methodName=s.getName();//获取指标办法名 String targetClassMethod=className+"."+methodName; log.error("{}'exception msg is {}",targetClassMethod,ex.getMessage()); //拓展? //1)将日志写到日志文件 //2)将出现异常的这个信息发送到某个人的邮箱(email),例如QQ邮箱 //3)将出现异常的状况发短信给某人(运维人员) //4)报警(播放一段好听的音乐) }}

October 14, 2020 · 1 min · jiezi

关于aop:AOP形式实现Redis缓存

AOP连接点罕用APIProceedingJoinPoint只能盘绕告诉应用(ProceedingJoinPoint控制目标办法的执行),如果当做参数来用,必须位于参数的第一位 private void saveSysLog(ProceedingJoinPoint point){ //1.获取指标类(就相当于晓得了是哪一个类) Class<?> targetCls = point.getTarget().getClass(); MethodSignature ms= (MethodSignature) point.getSignature(); System.out.println("ms________________"+ms); String methodName = ms.getName();//再获取办法名 //2.获取类里的办法(类里有很多办法 通过办法名和办法参数类型可能确定是哪一个办法) Method targetMethod=targetCls.getMethod(methodName,ms.getParameterTypes()); //3.获取指标办法上的注解 RequiredLog requiredLog = targetMethod.getAnnotation(RequiredLog.class); //4.获取注解上的值if(requiredLog!=null){ operation=requiredLog.value();}String method=targetCls.getName()+"."+methodName; Object[] paramsObj=point.getArgs(); //转一般串 //String params= Arrays.toString(paramsObj); //将参数转换为字符串 String params=new ObjectMapper().writeValueAsString(paramsObj); }public void before(JoinPoint joinPoint){ //getSignature:获取是一个对象(外面蕴含办法的返回值 办法名 办法参数) String methodName = joinPoint.getSignature().getName(); String typeName = joinPoint.getSignature().getDeclaringTypeName(); System.out.println("指标办法的门路:"+typeName+"."+methodName); // getArgs:返回指标办法的参数 Object[] args = joinPoint.getArgs(); System.out.println("参数:"+ Arrays.toString(args)); Class<?> methodClass = joinPoint.getTarget().getClass(); System.out.println("指标对象类型:"+methodClass);入门案例@Aspect //我是一个aop的切面类@Component//将类交给spring容器治理public class CacheAOP { //公式=切入点表达式 + 告诉办法 /* *对于切入点表达式的应用阐明 * 粗粒度: * 1.bean(bean的Id)一个类 bean的Id指的是交给spring容易治理的类的类名小写 , * 也能够bean(*ServiceImpl) 多个类 * 2.within(包名.类名) 一个类/*代替就是多个 */ @Pointcut("bean(itemCatServiceImpl)") public void pointCut(){ //定义切入点表达式 只为了占位 } //定义前置告诉,与切入点表达式进行绑定,留神绑定的是办法 @Before("pointCut()") //等同于@Before("bean(itemCatServiceImpl") 区别:pointCut()示意切入点表达式的援用 //实用于多个告诉同用状况 public void before(JoinPoint joinPoint){ //getSignature:获取是一个对象(外面蕴含办法的返回值 办法名 办法参数) String methodName = joinPoint.getSignature().getName(); String typeName = joinPoint.getSignature().getDeclaringTypeName(); System.out.println("指标办法的门路:"+typeName+"."+methodName); // getArgs:返回指标办法的参数 Object[] args = joinPoint.getArgs(); System.out.println("参数:"+ Arrays.toString(args)); Class<?> methodClass = joinPoint.getTarget().getClass(); System.out.println("指标对象类型:"+methodClass); System.out.println("我是前置告诉"); } @Around("pointCut()")public Object doaround(ProceedingJoinPoint joinPoint) { /** * ProceedingJoinPoint只能盘绕告诉应用(ProceedingJoinPoint控制目标办法的执行), * 如果当做参数来用,必须位于参数的第一位, */ Object result=null; try { result = joinPoint.proceed();//执行下一个告诉或指标办法 } catch (Throwable throwable) { throwable.printStackTrace(); } System.out.println("盘绕告诉完结"); return result;} }AOP 实现 Redis缓存编写配置类 ...

October 14, 2020 · 2 min · jiezi

关于aop:AOP编程全解析

AOP是一种编程思维,一套标准。 软件开发经验了面向过程编程时代,以C语言为代表,之后是面向对象编程时代,以Java语言为代表。 在21世纪大牛们又提出了一种新的编程思维面向方面编程,即AOP理念,全称Aspect-Oriented Programming。 AOP是第三代编程思维,到哪免不了都要问下。倒退历史1997年在面向对象编程大会上Gregor Kiczales等人首次提出了AOP的概念,之后各大公司等别离退出钻研。2001年Palo Alto钻研核心公布了首个反对AOP的语言AspectJ,同时也是一个标准。 指标定位在对真实世界形象的面向对象编程过程中,始终随同着某写操作的代码无奈实现模块化封装,会散落在各个对象中存在,特地是非功能性代码。对于个别的性能开发采取面向对象形式进行形象是可能很好应酬的,然而面向方面(切面)给了一种新的思维形式来思考编程,能更好的进行全局结构化思考。 所以AOP次要解决两个问题: 代码扩散问题,特地是那些非功能性代码。作为面向对象编程思维的一种补充和欠缺。 外围知识点连接点连接点:join point,程序的一个执行点,如类中的一个办法,办法外面一个代码块。 切入点切入点:point cut,是一个捕捉连接点的代码构造,就是定义一个代码逻辑用来捕捉某个连接点的代码。 方面方面;aspect,是具体被执行的切面逻辑代码,相似于一个类。 告诉告诉:advice,是point cut执行的代码,定义在连接点什么机会来执行aspect。 次要使用场景场景分为2类: 一类是非功能性需要,如日志、异样、平安、事务都能够应用AOP思维编程。 另一类是功能性需要,在原来对象形象的思维中增加AOP思维,这里是一种结构化思维,在定义类时思考多个类的切面共性。 支流AOP语言实现对AOP实现除了AspectJ外,已知的还有JBoss AOP、Spring AOP等。 这里只介绍AspectJ和SpringAOP,重点是他们不同点。AspcetJAspectJ采纳动态织入形式进行切面织入原代码,提供独立的编译器把切面和原代码的java文件编织成一个新的class文件。提供了具体的编译日志和调试工具,编译工夫长然而运行效率高。 连接点的反对范畴: 办法和结构器调用办法和结构器执行属性拜访异样解决类初始化,是static代码块语法结构控制流对象及参数类型条件测试关联连接点告诉形式: before,连接点执行前运行after,连接点执行后运行around,连接点的整个外侧,整个包住,可能绝的连接点执行和批改上下文环境Spring AOPSpring AOP没有齐全实现AspectJ语言,它更多的是对Spring framwork进行Aop能力的扩大实现,补全Spring framework的有余并让Aop与Spring framwork交融。 连接点只反对办法拦挡调用。 连接点告诉形式在aspect的before、after、around的根底上减少throw对异样的触发的拦挡。 Spring AOP与Spring IoC体系交融,对于aspect类对立交由Spring beans治理,并且提供ProxyFactoryBean的AOP代理工厂类,还有主动代理的BeanNameAutoProxyCreator和DefaultAdvisorAutoProxyCreator的弱小工具。 Spring AOP是动静织入,在运行时实现AOP的aspect代码织入原代码逻辑中。其底层默认采纳JDK的动静代理实现AOP代理,当对象没有实现接口时,CGLIB会默认应用。 优缺点长处:解决代码散乱问题、代码逻辑解偶、易于保护、提供扩展性和可重用性。 毛病:切面越多零碎越简单难懂、工程师学习成本增加(业务不再是线型,变成了跳跃式) AOP编程要谨慎应用,作为面向对象编程的一种补充。 作者:Owen Jia 关注他的博客:https://blog.shareworld.vip

September 28, 2020 · 1 min · jiezi

关于aop:Spring中基于AOP的事务管理

Spring中基于AOP的事务管理1.在我的项目启动类上增加@EnableTransactionManagement注解,(新版本springboot不必增加)2.在适合的类上或者办法上增加@Transactional注解 @Transcationl当在一个业务的办法中设置一个异样时,尽管抛出了异样,然而数据还是变动了!!当状态从0禁止变为1(启用)时我在下面设置了抛出一个异样刷新页面后,发现数据曾经产生了变动!!当我在用户业务层增加 @Transactionl后数据产生了回滚,禁止并没有变成启动!阐明transcationl失效了 readyOnly readyOnly只读:指定事务是否是只读事务当在用户的业务层上注解@Transactional(readOnly = true)如果再对用户进行增删改等操作时,就会报错 当readOnly只读默认是false,在进行查问的时候应用true能够减少效率,然而对于增删改等要应用true rollbackFor rollbackFor:在呈现指定异样时回滚(默认RuntimeException,Error都会滚) 在扭转状态的业务办法上增加rollbackFor属性,值为自定义异样;在valid==1时,抛出此异样;检测数据是否回滚能够看到抛出异样,并且禁用状态并没有扭转,数据回滚了 noRollbackFor noRollbackFor:呈现指定异样,不产生回滚 把之前的rollbackFor改为noRollbackFor扭转状态,扭转之前是禁用抛出异样后刷新页面,状态曾经变成启用了。阐明尽管应用@Transactional注解,抛出了异样,然而数据并没有回滚 timeout 超出设定工夫业务没有实现就回滚 isolation 事务隔离级别 如果多个事务并行执行,就会呈现脏读、幻读、反复读等景象,如果不心愿呈现这种景象,就能够设置此属性,个别设置值为 : Isolation.READ_COMMITTEDpropagation 事务的流传个性 **不同业务对象(service)**中事务办法之间互相调用时,业务的传播方式。 默认是:Propagation.REQUIRES_NEW 。指多个事务之间是同一个事务对象,其中一个事务回滚,之前的事务都会回滚 如果想让其中一个事务回滚时,其余事务不回滚;就应用 Propagation.REQUIRES_NEW

September 19, 2020 · 1 min · jiezi

关于aop:AOP基础1

Spring AOP1.什么是AOP? AOP是一种面向切面的编程思维,是OOP的一种扩大和补充;和OOP(面向对象)相比,是一种动静过程;AOP,它可能在预编译阶段和运行期动静代理的形式,实现在不批改代码的状况下给程序增加额定的性能。 AOP常被用在非核心业务的扩大上,进步零碎的可扩展性。例如日志治理,权限治理,事务处理,缓存等。2.CGLIB(默认),JDK AOP的底层是基于动静代理实现的 (1)如果被代理对象实现的是接口,就能够应用JDK动静代理机制对指标创立代理对象(代理对象和被代理对象实现同一个接口) (2)如果被代理对象没有实现接口,就能够应用CGLIB代理机制创立代理对象(代理对象implemens被代理对象) (3)应用JDK时须要配置文件中:spring.aop.proxy-target-class=false3.AOP相干术语 (1)Aspect:定义一个切面类,被此注解形容的类是一个Spring AOP的一个切面类 (2)PointCut:定义一个切入点办法;只能存在Aspect中;属性值是切入点的切入点表达式,有多种;切入点是连接点的汇合; (3)Joinpoit:程序执行中的连接点,个别指向被拦挡到的指标办法 (4)Advice:定义一个告诉,及切入点要执行的办法;其属性值是切入点表达式 4.切入点表达式4.1 bean:用于匹配bean对象的所有办法 bean表达式个别用于类级别,实现粗粒度的切入比方: bean(userServiceImpl) 指定userServiceImpl中的所有办法 bean(*ServiceImpl) 指定以ServiceImpl结尾的所有类的所有办法 4.2 annotation: @annotation是办法级别的细粒度切入办法,比方: ("@annotation(com.com.py.pj.common.annotation.SysLogRequired)") 其中com.com.py.pj.common.annotation是创立注解的包;SysLogRequired是注解名 应用此接入点表达式时,须要把自定义的注解写在业务办法上。 5.须要的依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency>6.代码实现参考 AOP之记录用户日志行为

September 18, 2020 · 1 min · jiezi

关于aop:AOP之记录用户日志行为

Aop之输入日志保留在数据库中 1.须要的依赖 <dependencies> <!-- AOP依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies>2.切面类和注解 package com.py.pj.common.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author WL * @version 创立工夫:2020-9-17 19:10:00 * @Description 保留日志自定义注解 */@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface SysLogRequired { String value();}package com.py.pj.common.aspect;import java.lang.reflect.Method;import java.util.Date;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import com.py.pj.common.annotation.SysLogRequired;import com.py.pj.sys.entity.SysLog;import com.py.pj.sys.service.SysLogService;/*** @author WL* @version 创立工夫:2020-9-17 19:11:17* @Description 切面类;实现日志的主动保留*/@Aspect@Componentpublic class SysLogAspect { private static final Logger log = LoggerFactory.getLogger(SysLogAspect.class); @Autowired private SysLogService sysLogService; // 切入点;应用@annotation的形式,细粒度 @Pointcut("@annotation(com.py.pj.common.annotation.SysLogRequired)") public void doSaveLog() {} // 盘绕告诉; @Around("doSaveLog()") public Object around(ProceedingJoinPoint jp) throws Throwable{ long t1 = System.currentTimeMillis(); Object result = jp.proceed(); // 要执行的办法 long t2 = System.currentTimeMillis(); long t = t2-t1; log.info("执行工夫:{ }",t); SaveLog(jp,t); return result; } /** * @param jp * @param time * getSignature(): 该办法次要获取被代理对象的属性名称汇合 * getTarget():此处是取得了被代理类的对象Impl * @throws JsonProcessingException * @throws SecurityException * @throws NoSuchMethodException */ private void SaveLog(ProceedingJoinPoint jp, long time) throws JsonProcessingException, NoSuchMethodException, SecurityException { //List com.py.pj.sys.serviceImpl.SysDeptServiceImpl.findObjects() MethodSignature ms = (MethodSignature) jp.getSignature(); //....办法全类名 System.out.println("ms="+ms); Class<?> targetClass = jp.getTarget().getClass(); // 被代理的类的反射 String targetName = targetClass.getName(); //DaoImpl被代理的类对象的名字 System.out.println("targetName="+targetName);//com.py.pj.sys.serviceImpl.SysDeptServiceImpl // 获取接口申明办法 String MethodName = ms.getMethod().getName();//findObjects System.out.println("MethodName="+MethodName); Class<?>[] parameterTypes = ms.getMethod().getParameterTypes(); // 暴力反射获取办法 Method targetMethod = targetClass.getDeclaredMethod(MethodName, parameterTypes); //获取办法参数 Object[] paramObj = jp.getArgs(); System.out.println("paramObj="+paramObj); String params = new ObjectMapper().writeValueAsString(paramObj); String username = "weilong"; SysLog slog = new SysLog(); SysLogRequired operation = targetMethod.getDeclaredAnnotation(SysLogRequired.class); if (targetMethod!=null) { slog.setOperation(operation.value()); } slog.setCreatedTime(new Date()); slog.setIp("1.1.1.1"); slog.setMethod(targetName+"."+MethodName); slog.setParams(params); slog.setUsername(username); slog.setTime(time); sysLogService.insertObjects(slog); }}3.在须要增加日志的业务办法上增加正文 ...

September 18, 2020 · 2 min · jiezi

关于aop:Spring-Aop

一,AOP术语阐明1,告诉Advice:在AOP中,切面的工作被称为告诉。告诉定义了切面“是什么”以及“何时”应用。除了形容切面要实现的工作,告诉还解决了何时执行这个工作的问题。Spring切面能够利用5中类型的告诉:`(1)前置告诉(Before):前置告诉,在办法执行之前执行;(2)后置告诉(After):后置告诉,在办法执行之后执行;(3)返回告诉(After-returning):返回告诉,在办法返回后果之后执行;(4)异样告诉(After-throwing):异样告诉,在办法抛出异样之后执行;(5)盘绕告诉(Around):盘绕告诉,围绕着办法执行; ` 2,连接点(Join point):连接点是在利用执行过程中可能插入切面的一个点。这个点能够是调用办法时,抛出异样时,甚至是批改一个字段时。切面代码能够利用这些点插入到利用的失常流程之中,并增加行为。 3,切点(Pointcut):如果说告诉定义了切面“是什么”和“何时”的话,那么切点就定义了“何处”。比方我想把日志引入到某个具体的办法中,这个办法就是所谓的切点。 4,切面(Aspect):切面是告诉和切点的联合。告诉和切点独特定义了切面的全部内容————它是什么,在何时和何处实现其性能。 三、应用基于注解的形式实现AOP1.须要引入相干的jar,应用maven` com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar` 2、在Spring的配置文件中退出 aop 的命名空间3、须要在Spring的配置文件中,退出如下配置:<!-- 主动扫描的包 --> <context:component-scan base-package="com.scorpios.spring.aop.impl"></context:component-scan> <!-- 使 AspectJ 的注解起作用 --><aop:aspectj-autoproxy></aop:aspectj-autoproxy>组件扫描(component scanning):Spring 可能从classpath下主动扫描, 侦测和实例化具备特定注解的组件。 特定组件包含: @Component: 根本注解, 标识了一个受Spring治理的组件 @Respository: 标识长久层组件 @Service: 标识服务层(业务层)组件 @Controller: 标识体现层组件对于扫描到的组件, Spring有默认的命名策略: 应用非限定类名, 第一个字母小写。 也能够在注解中通过value属性值标识组件的名称 3、编写业务办法接口public interface ArithmeticCalculator { int add(int i,int j); int sub(int i,int j); int mul(int i, int j); int div(int i, int j); }4、实现业务办法 (留神此处Component注解)@Componentpublic class ArithmeticCalculatorImpl implements ArithmeticCalculator { @Override public int add(int i, int j) { int result = i + j; return result; } @Override public int sub(int i, int j) { int result = i - j; return result; } @Override public int mul(int i, int j) { int result = i * j; return result; } @Override public int div(int i, int j) { int result = i / j; return result; }}5、 编写切面类:(是个别的Java类,在其中增加要额定实现的性能,留神注解)

September 17, 2020 · 1 min · jiezi

关于aop:4-Spring-AOP事务管理

1 Spring中AOP事务简介1.1 事务事务(Transaction)是一个业务,是一个不可分割的逻辑工作单元,基于事务能够更好的保障业务的正确性。 1.2 事务个性: (ACID个性)原子性(Atomicity):一个事务中的多个操~~~~作要么都胜利要么都失败一致性(Consistency): 操作前后总数应该是统一的。隔离性(Isolation):事务与事务之间是互相隔离的持久性(Durability):事务一旦提交数据会长久保留到数据库 2 Spring中事务管理2.1spring中事务形式概述Spring框架中提供了一种申明式事务的解决形式,此形式基于AOP代理,能够将具体业务逻辑与事务处理进行解耦。也就是让咱们的业务代码逻辑不受净化或大量净化,就能够实现事务管制。 在SpringBoot我的项目中,其外部提供了事务的主动配置,当咱们在我的项目中增加了指定依赖spring-boot-starter-jdbc时,框架会主动为咱们的我的项目注入事务管理器对象,最罕用的为DataSourceTransactionManager对象。 2.2理论我的项目中最罕用的注解形式的事务管理基于@Transactional 注解进行申明式事务管理的实现步骤1) 启用申明式事务管理,在我的项目启动类上增加@EnableTransactionManagement,咱们新版本中也可不增加(例如新版Spring Boot我的项目)。 2) 将@Transactional注解增加到适合的业务类或办法上,并设置适合的属性信息。 2.3 AOP 编程中基于注解形式的事务管制@Transactional2.3.1@Transactional形容类示意类中所有办法都要进行事务管制,如果办法上也有该注解则办法上的事务注解个性优先级比拟高2.3.2 readOnly属性 含意:是否为只读事务(只容许查问操作 其余操作不容许) 默认值是false。readOnly=false所注解的办法或类代表减少,删除,批改业务.readOnly=true代表读取数据2.3.3 rollbackFor属性含意是(什么异样回滚事务) 默认值是(RuntimeException与Error 呈现这俩个都会回滚事务 然而查看异样(IllegalAccessException)不回滚)2.3.4 noRollbackFor属性 含意是什么(什么状况下不回滚),没有默认值2.3.5 timeout 属性含意为是否反对事务超时,默认没有值,若为-1默认不反对事务超时,咱们能够定义超时工夫。如果配置了具体工夫,则超过该工夫限度但事务还没有实现,则主动回滚事务。这个工夫的记录形式是在事务开启当前到sql语句执行之前。2.3.6 isolation=Isolation.READ_COMMITTED (默认写成它就行 不呈现脏读),把事务隔离级别设置为它,不容许呈现脏读(事务隔离级别较低)。隔离级别越高并发就会越小,性能越差,但更平安。 事务管制过程剖析Spring事务管理是基于接口代理(JDK)或动静字节码(CGLIB)技术,而后通过AOP施行事务加强的。当咱们执行增加了事务个性的指标形式时,零碎会通过指标对象的代理对象调用DataSourceTransactionManager对象,在事务开始的时,执行doBegin办法,事务完结时执行doCommit或doRollback办法。 3 Spring 中事务流传个性事务流传(Propagation)个性指"不同业务(service)对象"中的事务办法之间互相调用时,事务的传播方式。 3.1罕用事务传播方式3.1.1@Transactional(propagation=Propagation.REQUIRED)如果没有事务创立新事务, 如果以后有事务参加以后事务, Spring 默认的事务流传行为是PROPAGATION_REQUIRED,它适宜于绝大多数的状况。 //代码示例@Transactional(propagation = Propagation.REQUIRED) @Override public List<Node> findZtreeMenuNodes() { return sysMenuDao.findZtreeMenuNodes(); }当有一个业务对象调用如上办法时,此办法始终工作在一个曾经存在的事务办法,或者是由调用者创立的一个事务办法中。3.1.2@Transactional(propagation=Propagation.REQUIRES_NEW)必须是新事务, 如果有以后事务, 挂起以后事务并且开启新事务。 //代码示例@Transactional(propagation = Propagation.REQUIRES_NEW) @Override public void saveObject(SysLog entity) { sysLogDao.insertObject(entity); }当有一个业务对象调用如上业务办法时,此办法会始终运行在一个新的事务中。 ...

September 17, 2020 · 1 min · jiezi

关于aop:Spring-AOP

什么是AOPAOP(Aspect Orient Programming)是一种设计思维,是软件设计畛域中的面向切面编程,是面向对象编程(OOP)的一种补充和欠缺。它以通过预编译形式和运行期动静代理形式,实现在不扭转源代码的状况下给程序动静对立增加额定性能的一种技术。能够说AOP是OOP动静的一部分,OOP是一种动态,必须想确定好对象、子系统、模块(少返工)...AOP面向切面的运行代理,能够对象运行时动静的织入一些扩大性能或管制对象执行。{(服务增益)基于根本业务,做日志解决、权限管制、事务处理、做异步、做缓存......} 补充:设计思维:MVC分层架构设计思维、连接池(池化思维)、IOC管制反转思维、面向对象(OOP)....... OCP:开闭准则,实现性能的扩大业务在AOP编程时,如何基于OCP准则实现性能扩大? 1.在编译时,相似于用Lomback思维,在编译时主动重写。 2.在运行时,对这个办法重写(写一个子类继承这个类重写)或组合形式:对象之间的协同 Spring AOP利用原理剖析基于代理机制机制(动静形式)实现性能扩大1.JDK动静代理机制:指标对象(被代理对象)实现接口,JDK代理机制为指标对象创立代理对象(指标类和代理类会实现独特接口)----了解为兄弟关系,兄弟间协同、耦合2.CGLIB代理机制:指标对象(被代理对象)没有实现接口,CGLIB代理机制为指标对象创建对象(默认创立的代理类会继承指标对象类型)----继承----SpringBoot2.X中AOP默认应用CGLIB代理,如果要应用JDK代理能够在配置文件(applicatiion.properties)**中进行配置:spring.aop.proxy-target-class=false Spring中AOP相干术语1.切面(aspect):横切面对象,借助@Aspect申明2.告诉(Advice):在切面的某个特定的连接点上执行的动作(扩大性能)3.连接点(joinpoint):程序执行过程中某个特定的点,个别指拦挡到的指标办法4.切入点(pointput):对多个连接点的一种定义,个别能够了解为多个连接点的汇合@Aspect注解用于标识或者形容AOP中的切面类型,基于切面类型构建的对象用于为指标对象进行性能扩大或控制目标对象的执行。@PointCut注解用于形容切面中的办法,并定义切面中的切入点(基于特定表达式的形式进行形容)@Around注解用于形容切面种办法,这样的办法会被认为是一个盘绕告诉(外围业务执行之前和之后要执行的一个动作),@Aournd注解外部value属性的值为一个切入点表达式或者是切入点表达式的一个援用(这个援用为一个@PointCut注解形容的办法的办法名)。ProceedingJoinPoint类为一个连接点类型,此类型的对象用于封装要执行的指标办法相干的一些信息。只能用于@Around注解形容的办法参数。

September 16, 2020 · 1 min · jiezi

关于aop:缓存服务器Redis-04-AOP实现Redis缓存服务

在上文中,咱们尽管在业务层service中实现了代码的实现.然而该代码不具备复用性.如果换了其余的业务则须要从新编辑. 并且因为缓存的代码写在业务层service中,所以代码的耦合性高,不不便当前的扩大. 所以咱们从代码复用以及升高代码的耦合性的方面应用AOP来实现redis缓存. AOPAOP即面向切面编程--在不改变原有代码的根底上,对业务进行加强. AOP = 切入点表达式 + 告诉办法 专业术语: 1.连接点: 在执行失常的业务过程中满足了切入点表达式时进入切面的点.(织入) 多个 2.告诉: 在切面中执行的具体的业务(扩大) 办法 3.切入点: 可能进入切面的一个判断 if判断 一个 4.指标办法: 将要执行的实在的业务逻辑. 告诉办法分类:1.前置告诉(@Before): 指标办法执行之前执行 2.后置告诉(@After): 指标办法执行之后执行 3.异样告诉(@AfterThrowing): 指标办法执行之后抛出异样时执行 4.最终告诉(@AfterReturning): 不论什么时候都要执行的办法. 阐明:上述的四大告诉类型不能控制目标办法是否执行.个别应用上述的四大告诉类型,都是用来记录程序的执行状态.5.盘绕告诉(@Around): 在指标办法执行前后都要执行的告诉办法. 控制目标办法是否执行.并且盘绕告诉的性能最为弱小.盘绕告诉是最为罕用的. 切入点表达式分类:1.bean(bean的id) 类名首字母小写 匹配1个类 2.within(包名.类名) 按包门路匹配类 匹配多个类 上述表达式是粗粒度的管制,按类匹配. 3.execution(返回值类型 包名.类名.办法名(参数列表)) 4.@annotation(包名.注解名) 按注解进行拦挡.上述是细粒度的管制.bean和@annotation较为罕用 实现Redis缓存业务剖析1.自定义注解CacheFind 次要被注解标识的办法,则开启缓存的实现. 2.为了未来辨别业务,须要在注解中标识key属性,由使用者自行填写. 3.为了用户提供数据超时性能. 自定义注解@CacheFind@Retention(RetentionPolicy.RUNTIME) //该注解运行时无效@Target({ElementType.METHOD}) //对办法无效public @interface CacheFind { String key(); //该属性为必须增加 int seconds() default 0; //设定超时工夫 默认不超时}编写CacheAOP类package com.jt.aop; import com.jt.annotation.CacheFind; import com.jt.util.ObjectMapperUtil; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import redis.clients.jedis.Jedis; import java.util.Arrays; @Component @Aspect public class RedisAOP { //注入缓存reids对象 @Autowired private Jedis jedis; //@Pointcut("@annotation(com.jt.annotation.CacheFind)") //public void doRedisPointCut(){} /** * 拦挡@CacheFind注解示意的办法 * 告诉抉择: 缓存的实现应该选用盘绕告诉 * 步骤: * 1.动静生成key 用户填写的key+用户提交的参数 */ @Around("@annotation(cacheFind)") public Object around(ProceedingJoinPoint joinPoint, CacheFind cacheFind){ try { Object result = null; //1.如何动静获取用户在注解中填写的数据 String prekey = cacheFind.key(); //2.动静获取指标办法中的参数 将数组转化为字符串 String args = Arrays.toString(joinPoint.getArgs()); String key = prekey + "::" + args; //"ITEM_CAT_PARENTID::[0]" //3.查问缓存中查问 //4.判断json中是否有值 if (jedis.exists(key)) { //先获取json数据 String json = jedis.get(key); //动静获取指标办法的返回值类型?? 向上造型 不必强转 向下造型 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Class target = methodSignature.getReturnType(); //7.进入代表程序中有值,将json转为对象 result = ObjectMapperUtil.toObject(json, target); System.out.println("AOP实现缓存查问!!!!"); }else { //5.若缓存中没有查询数据库 result = joinPoint.proceed(); //执行指标办法,获取返回值后果 System.out.println("AOP执行数据库操作"); //6.将数据存到redis中 //先转为json格局 String json = ObjectMapperUtil.toJSON(result); if(cacheFind.seconds()>0){ //判断是否须要超时工夫 jedis.setex(key, cacheFind.seconds(), json); }else { jedis.set(key, json); } } return result; }catch (Throwable e){ e.printStackTrace(); throw new RuntimeException(e); } } }留神对于盘绕告诉参数的阐明:1.连接点必须位于告诉办法参数的第一位2.其余四大告诉类型不能够增加ProceedingJoinPoint对象,能够增加JointPoint对象 ...

September 11, 2020 · 2 min · jiezi

关于aop:全局异常的处理机制

作用在我的项目中,可能会遇到各种各样的异常情况,如果在每个办法中都增加其异样解决机制(如:trycatch),就会导致整个代码的构造都显得很凌乱;即便未来呈现了异样,解决起来也很麻烦,不能很好的治理,所以就须要将这些异样及逆行对立的形式进行异样解决. 实现@RestControllerAdvice //作用: 标识我是一个告诉办法,并且只拦挡Controll层的异样.并且返回JSON.public class SysResultException { //须要定义一个全局的办法 返回指定的报错信息. //ExceptionHandler 配置异样的类型,当遇到了某种异样时在执行该办法. @ExceptionHandler(RuntimeException.class) public Object exception(Exception e){ e.printStackTrace(); //日志记录/控制台输入. 让程序员晓得哪里报错!!! return SysResult.fail(); }}@RestControllerAdvice注解的作用:标识为一个告诉办法,并且只拦挡Controller层的异样.并且返回为JSON串.@ExceptionHandler(RuntimeException.class)注解的作用:配置异样的类型,当遇到了某种异样时在执行该办法,(RuntimeException.class)指异样类型为运行时异样. 留神因为我的项目中个别都是controller--service--mapper,所以咱们只须要在controller层进行对立异样解决即可,因为service/mapper层的异样都会往上抛出至controller层全局异样解决机制,该性能是通过Spring利用AOP实现的.

August 31, 2020 · 1 min · jiezi

关于aop:aop理解

Aop 了解什么是Aop?Aop是spring框架提供的切面编程,次要作用与在不批改原有业务的时候扩大新的业务.升高程序的耦合度,加强程序的开发效率.代码失去重用性. Aop如何实现Aop实现次要有两种形式配置中如果没有配置那么默认应用的是cglib1.jdk自代的动静代理.为指标对象创立代理对象2.cglib代理机制,为指标创立代理对象 什么是OCP准则(开闭准则)?ocp开闭准则是对业务批改敞开,对业务扩大开发 AOP 相干术语剖析@Async 进行异步操作spring框架会在底层创立一个新的线程池,被@Async润饰的办法会又线程池调配的一个线程来进行调用.@Order 设置优先级,设置数字优先级越高优先级也就越高 五大告诉@Advice 告诉.在切面的某一个链接点进行告诉操作例如:1.@befor:第二程序执行不论失 前置告诉2.@Around:优先级最高 盘绕告诉3.@After:不论程序执行胜利与否当快要执行完时都会先执行 后置告诉4.@AfterReturning: 程序执行胜利执行 返回告诉5.@Afterthwing: 程序执行失败执行 异样告诉 四大连接点@poincut 切面编程的切入点~~~~1.bean(间接指定保留在bean中的对象)粗略的切入点,该类下所有的办法都能够进行办法的扩大2.@annotation(指定注解接口的全限定类名),细腻的切入点,只有被该注解润饰的办法才会有办法的扩大3.within 用于匹配包下的所有类中的所有办法4.execution 用于指定办法~~~~ Spring AOP事务处理@Transaction默认事务回滚@Transaction(timeout:时长到时主动回滚事务) 如何获取注解上的值//1.获取用户行为日志信息 //获取指标对象(要执行的那个指标业务对象)类 Class<?> getcls = jp.getTarget().getClass(); MethodSignature sim = (MethodSignature) jp.getSignature();//强转是为了让他获取更多的办法 Method methods = getcls.getDeclaredMethod(sim.getName(), sim.getParameterTypes()); System.out.println(methods); //判断是否是LoginObject的注解值 LoginObject falg = methods.getAnnotation(LoginObject.class); String operation="operation"; if(falg!=null) operation = falg.value();盘绕告诉最为重要,重点一下由@Around注解形容的办法为一个盘绕告诉办法,咱们能够在此办法外部 手动调用指标办法(通过连接点对象ProceedingJoinPoint的proceed办法进行调用) 盘绕告诉:此盘绕告诉应用的切入点为bean(sysUserServiceImpl) 盘绕告诉特点: 1)编写: a)办法的返回值为Object. b)办法参数为ProceedingJoinPoint类型. c)办法抛出异样为throwable. 2)利用: a)指标办法执行之前或之后都能够进行性能拓展 b)绝对于其它告诉优先级最高。 @param jp 为一个连贯对象(封装了正在要执行的指标办法信息) @return 指标办法的执行后果 @throws Throwable 事务Transaction的解决@Transactional(timeout = 30, //执行时长到时主动回滚 readOnly = false, //只读事务,* 为了疏忽那些不须要事务的办法,比方读取数据,能够设置 read-only 为 true。 isolation = Isolation.READ\_COMMITTED,//事务的隔离级别,默认值采纳 DEFAULT. rollbackFor = Throwable.class, //异样回滚事务 propagation = Propagation.REQUIRED) //Propagation.new的话为事务的都在新的线程处理事务 no-rollback- for  抛出 no-rollback-for 指定的异样类型,不回滚事务。Spring 中事务流传个性@Transactional(propagation=Propagation.REQUIRED) 如果没有事务创立新事务, 如果以后有事务参加以后事务, Spring 默认的事务流传行为是PROPAGATION\_REQUIRED,它适宜于绝大多数的状况@Transactional(propagation=Propagation.REQUIRES\_NEW)必须是新事务, 如果有以后事务, 挂起以后事务并且开启新事务Spring AOP异步操作实现1. @EnableAsync //spring容器启动时会创立线程池 @SpringBootApplication public class Application { public static void main(String\[\] args) { SpringApplication.run(Application.class, args); }}2. @在须要异步执行的业务办法上,应用@Async办法进行异步申明。即可##### 当咱们须要本人对spring框架提供的连接池进行一些繁难配置,spring: task: execution: pool: queue-capacity: 128 阻塞队列最大期待线程数 core-size: 5 外围线程数 max-size: 128 最大线程数 keep-alive: 60000 当线程闲暇时60s后执行线程销毁回收 thread-name-prefix: db-service-task- 为线程创立名字Spring AOP中Cache操作实现1. @EnableCaching //spring容器启动时会主动启动cache配置缓存 @SpringBootApplication public class Application { public static void main(String\[\] args) { SpringApplication.run(Application.class, args); }}2.在须要缓存的办法上配置@Cacheable(value = "deptCache"), cache配置姓名以便前期清理缓存3.在须要清理缓存的办法上配置@CacheEvict(value="deptCache",allEntries=true)当执行完结的清理缓存,cache姓名和清理所以缓存配置在Spring中默认cache底层实现是一个Map对象,假如此map对象不能满足咱们理论须要,在理论我的项目中咱们能够将数据存储到第三方缓存零碎中. ...

August 22, 2020 · 1 min · jiezi

关于aop:Spring-AOP-01

概述AOP是spring框架中重要的一环,全称:Aspect Orient Programming,AOP是一种设计思维,意为面向切面编程,它是面向对象编程(OOP)的一种补充和欠缺.它以通过预编译形式和运行期动静代理形式两种形式实现在不批改源代码的状况下给程序动静对立增加额定性能的一种技术. oop与aop两者的区别能够了解为:oop是动态固定的,有多少模块多少对象,怎么执行都是运行前写好的;aop是动静扩大的,能够在对象运行时去如果一些扩大的性能或是管制对象执行某些性能. 利用场景我的项目中个别有外围业务与非核心业务,而非核心业务个别通过特定形式切入零碎-->个别就是借助于aop来实现. aop次要基于ocp(开闭准则)--对扩大凋谢,对批改敞开--简略来说就是增加性能时,不能扭转原有外围业务代码,只能在其上增加. aop能够实现,如:日志解决/事务处理/缓存解决等等. 原理剖析AOP底层基于代理机制实现性能扩大,有两种实现形式:1) 如果指标对象(被代理对象)实现接口,则底层能够采纳JDK动静代理机制为指标对象创立代理对象(指标类和代理类会实现独特接口2) 如果指标对象(被代理对象)没有实现接口,则底层能够采纳CGLIB代理机制为指标对象创立代理对象(默认创立的代理类会继承指标对象类型) Spring boot2.x当前的版本中AOP当初默认应用的CGLIB代理,如果须要应用JDK动静代理能够在配置文件(applicatiion.properties)中进行如下配置: spring.aop.proxy-target-class=false相干术语1) 切面(aspect): 横切面对象,个别为一个具体类对象(能够借助@Aspect申明)---AOP对象2) 告诉(Advice):在切面的某个特定连接点上执行的动作(扩大性能),例如around,before,after等---AOP办法3) 连接点(joinpoint):程序执行过程中某个特定的点,个别指被拦挡到的的办法---指标办法4) 切入点(pointcut):对多个连接点(Joinpoint)一种定义,个别能够了解为多个连接点的汇合---指标办法汇合 利用实例切面(aspect)@Aspect 注解形容的类为spring容器的一个切面对象类(此类型中封装切入点与告诉办法) 1)切入点:要执行拓展业务的办法的汇合2)告诉办法:封装了在切入点办法上要执行的拓展业务办法 切入点@Pointcut 注解用于形容切入点(在那些点上执行拓展业务) bean(bean类型名字):为一种切入点表达式(这个表达式中定义了那些bean对象的办法要进行性能拓展)例如:bean(sysUserServiceImpl)示意名字为sysUserServiceImpl的bean对象中所有办法的汇合为切入点,也就是说sysUserServiceImpl对象中任意办法执行时都要进行性能扩大 告诉办法@Around 注解形容的办法为一个告诉办法(服务增益办法),此办法外部能够做服务增益(拓展业务)@Around 注解外部要指定切入点表达式,在此切入点表达式对应的切入点办法上做性能扩大@param jp 示意连接点,是动静确定的,用于封装正在执行的切入点办法信息@return 指标办法的执行后果@throws Throwable 告诉办法执行过程中呈现的异样 ProceedingJoinPoint类为一个连接点类型,此类型的对象用于封装要执行的指标办法相干的一些信息。只能用于@Around注解形容的办法参数 //执行指标办法Object result = jp.proceed();//最终会调用指标办法(两头可能会其余告诉办法)加强切面告诉切面告诉共有五种:@Before/@AfterReturning/@AfterThrowing/@After/@Around(优先级最高)留神:切面并不是所有告诉都要应用,应用什么告诉由业务决定. 五个告诉的执行程序如下:@Around-->@Before-->指标办法-->@Around-->@After-->@AfterReturning(失常状况)/@AfterThrowing(异常情况) 切入点表达式1) bean:用于匹配指定bean对象的所有办法bean(bean对象名)bean表达式外部的对象是由spring容器治理的一个bean对象,表达式外部的名字应该是spring容器中某个bean的name3) within:用于匹配指定包下所有类内的所有办法within(包名+类名)--不罕用5) execution:用于按指定语法规定匹配到具体方法execution(返回值类型 包名.类名.办法名(参数列表))--不罕用7) @annotation:用于匹配指定注解润饰的办法@annotation(注解全类名) 匹配有此注解形容的办法咱们须要本人定义注解,当应用这个注解润饰的类就是指标对象 bean/within表达式个别利用于类级别,实现粗粒度的切入点定义--罕用beanexecution/@annotation表达式利用于办法级别,实现细粒度的切入点表达式定义--罕用@annotation 切面优先级设置当多个切面作用于同一个指标对象办法时,这些切面会构建成一个切面链,切面的优先级须要借助@Order注解进行形容,数字越小优先级越高,默认优先级比拟低.

August 19, 2020 · 1 min · jiezi

关于aop:你不是说你会Aop吗

一大早,小王就急匆匆的跑过来找我,说:周哥,那个记录日志的性能我想求教一下。 因为公司某个我的项目要跟别的平台做对接,咱们这边须要给他们提供一套接口。昨天,我就将记录接口日志的工作安顿给了小王。 上面是我跟小王的次要对话。 我:说说怎么了? 小王:我将记录接口日志的性能放到了每个controller中,当初感觉有点繁琐,我这样做是不是不太适合? 我:为什么要去每个接口里记录日志? 小王:最开始我是用的拦截器,然而这样一个申请就记录了两条记录。 我:为什么是两条? 小王:在preHandle中记录一条申请数据,在postHandle中记录一条响应数据。 我:。。。你不是说你会Aop吗? 小王:Aop也是一样,在前置告诉记录一条申请数据,后置告诉记录一条响应数据。 小王:这个数据和以前记录操作日志的不太一样,以前只须要在前置告诉记录一条操作日志就能够了,然而当初有响应,所以只能在controller中记录日志了。 我:那你知不知道有个盘绕告诉?你说一下Aop就几种告诉类型。 小王:总共有五种,别离是: 前置告诉:在咱们执行指标办法之前运行(@Before)后置告诉:在咱们指标办法运行完结之后,不论有没有异样(@After)返回告诉:在咱们的指标办法失常返回值后运行(@AfterReturning)异样告诉:在咱们的指标办法出现异常后运行(@AfterThrowing)盘绕告诉:指标办法的调用由盘绕告诉决定,即你能够决定是否调用指标办法,joinPoint.procced()就是执行指标办法的代码 。盘绕告诉能够管制返回对象(@Around)接下来,咱们一起来演示一下如何应用盘绕告诉来解决小王的问题。 第一步:提供接口用来接管参数和响应接口 @RestControllerpublic class TestController { @GetMapping("/getName") public String getName(HttpServletRequest request) throw Exception { String result = "Java旅途"; String age = request.getParameter("age"); if("18".equals(age)){ result = "无奈辨认"; } return result; }}第二步:定义切点 execution()是比拟罕用的定义切点的表达式,execution()语法如下: execution(修饰符 返回值 包.类.办法名(参数) throws异样)其中: 修饰符和throws异样能够省略不写 依据这些解释,咱们能够将第一步中的接口用execution()表达式来形容一下: execution(String binzh.website.controller.TestController.GetName(HttpServletRequest))*:匹配所有项..:匹配任意个办法参数..呈现在类名中时,前面必须跟*,示意包、子孙包下的所有类;当初咱们优化一下下面的表达式,定义切面为controller包及controller上面所有包的所有办法 execution(* binzh.website.controller..*.*(..))第三步:盘绕告诉记录日志 @Around("execution(* binzh.website.controller..*.*(..))")public Object around(ProceedingJoinPoint joinPoint) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String age = request.getParameter("age"); Object proceed = ""; try { proceed = joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } System.out.println("age==="+age); System.out.println("proceed ===="+proceed); return proceed;}运行后果如下: ...

August 4, 2020 · 1 min · jiezi

springaop原理

spring实现aop的基础:BeanPostProcessor原理:https://blog.csdn.net/qizhizh... 2.AOP之@EnableAspectJAutoProxy原理分析https://blog.csdn.net/qizhizh... 3.AnnotationConfigApplicationContext实现spring注解开发https://blog.csdn.net/chr1sgo... 参考:spring中bean的生命周期,spring-@Import注解,

June 17, 2020 · 1 min · jiezi

如何实现Spring框架中的AOP

声明一个AdvisedSupport类,用于保存被代理对象和拦截方法的元数据对象 创建织入点AopProxy,可以通过getProxy方法获取代理后的对象。使用CGLIB生成动态代理,生成Enhancer实例,并指定用于处理代理业务的回调类 完成了织入之后,我们要考虑另外一个问题:对什么类以及什么方法进行AOP?对于“在哪切”这一问题的定义,我们又叫做“Pointcut”。Spring中关于Pointcut包含两个角色:ClassFilter和MethodMatcher,分别是对类和方法做匹配。Pointcut有很多种定义方法,例如类名匹配、正则匹配等,但是应用比较广泛的应该是和AspectJ表达式的方式,Spring借鉴了这种方式 万事俱备,只欠东风!现在我们有了Pointcut和Weave技术,一个AOP已经算是完成了,但是它还没有结合到Spring中去。怎么进行结合呢?Spring给了一个巧妙的答案:使用BeanPostProcessor BeanPostProcessor是BeanFactory提供的,在Bean初始化过程中进行扩展的接口。只要你的Bean实现了BeanPostProcessor接口,那么Spring在初始化时,会优先找到它们,并且在Bean的初始化过程中,调用这个接口,从而实现对BeanFactory核心无侵入的扩展 那么我们的AOP是怎么实现的呢?我们知道,在AOP的xml配置中,我们会写这样一句话: <aop:aspectj-autoproxy/>它其实相当于: <bean id="autoProxyCreator" class="org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator"></bean>AspectJAwareAdvisorAutoProxyCreator就是AspectJ方式实现织入的核心。它其实是一个BeanPostProcessor。在这里它会扫描所有Pointcut,并对bean做织入 文章来源:www.liangsonghua.me作者介绍:京东资深工程师-梁松华,长期关注稳定性保障、敏捷开发、JAVA高级、微服务架构

July 15, 2019 · 1 min · jiezi

好好面试你必须要懂的SpringAop

【干货点】此处是【好好面试】系列文的第10篇文章。看完该篇文章,你就可以了解Spring中Aop的相关使用和原理,并且能够轻松解答Aop相关的面试问题。 在实际研发中,Spring是我们经常会使用的框架,毕竟它们太火了,也因此Spring相关的知识点也是面试必问点,今天我们就大话Aop。特地在周末推文,因为该篇文章阅读起来还是比较轻松诙谐的,当然了,更主要的是周末的我也在充电学习,希望有追求的朋友们也尽量不要放过周末时间,适当充电,为了走上人生巅峰,迎娶白富美。【话说有没有白富美介绍(o≖◡≖)】接下来,直接进入正文。 为什么要有aop我们都知道Java是一种面向对象编程【也就是OOP】的语言,不得不说面向对象编程是一种及其优秀的设计,但是任何语言都无法十全十美,对于OOP语言来说,当需要为部分对象引入公共部分的时候,OOP就会引入大量的重复代码【这些代码我们可以称之为横切代码】。而这也是Aop出现的原因,没错,Aop就是被设计出来弥补OOP短板的。Aop便是将这些横切代码封装到一个可重用模块中,继而降低模块间的耦合度,这样也有利于后面维护。 Aop是什么东西学过Spring的都知道,Spring内比较核心的功能便是Ioc和Aop,Ioc的主要作用是应用对象之间的解耦,而Aop则可以实现横切代码【如权限、日志等】与他们绑定的对象之间的解耦,举个浅显易懂的小栗子,在用户调用很多接口的地方,我们都需要做权限认证,判断用户是否有调用该接口的权限,如果每个接口都要自己去做类似的处理,未免有点sb了,也不够装x,因此Aop就可以派上用场了,将这些处理的代码放到切片中,定义一下切片、连接点和通知,刷刷刷跑起来就ojbk了。 想要了解Aop,就要先理解以下几个术语,如PointCut、Advice、JoinPoint。接下来尽量用白话文描述下。 PointCut【切点】其实切点的概念很好理解,你想要去切某个东西之前总得先知道要在哪里切入是吧,切点格式如下:execution( com.nuofankj.springdemo.aop.Service.*(..))可以看出来,格式使用了正常表达式来定义那个范围内的类、那些接口会被当成切点,简单明了。 AdviceAdvice行内很多人都定义成了通知,但是我总觉得有点勉强。所谓的Advice其实就是定义了Aop何时被调用,确实有种通知的感觉,何时调用其实也不过以下几种: Before 在方法被调用之前调用After 在方法完成之后调用After-returning 在方法成功执行之后调用After-throwing 在方法抛出异常之后调用Around 在被通知的方法调用之前和调用之后调用JoinPoint【连接点】JoinPoint连接点,其实很好理解,上面又有通知、又有切点,那和具体业务的连接点又是什么呢?没错,其实就是对应业务的方法对象,因为我们在横切代码中是有可能需要用到具体方法中的具体数据的,而连接点便可以做到这一点。 给出一个Aop在实际中的应用场景先给出两个业务内的接口,一个是聊天,一个是购买东西接下来该给出说了那么久的切片了可以从中看到PointCut【切点】是 execution( com.nuofankj.springdemo.aop.Service.*(..))Advice是 BeforeJoinPoint【连接点】是 MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();代码浅显易懂,其实就是将ChatService和BuyService里边给userId做权限校验的逻辑抽出来做成切片。 那么如何拿到具体业务方法内的具体参数呢?这里是定义了一个新的注解作用可以直接看注释,使用地方如下可以看到对应接口使用了AuthPermission的注解,而取出的地方在于是的,这样便可以取出来对应的接口传递的userId具体是什么了,而校验逻辑可以自己处理。 送佛送到西,不对,撸码撸整套,接下来给出运行的主类可以看到,上面有一个接口传递的userId是1,另一个是123,而上面权限认证只有1才说通过,否则会抛出异常。 运行结果如下运行结果可想而知,1的通过验证,123的失败。 Aop的原理解析关于原理解析,由于大家都不喜欢看篇幅太长的文章,因此打算拆分成两篇进行,下篇文章会对Aop的原理和设计思想进行解析,有兴趣的朋友可以关注我一波。 公众号主营:服务端编程相关技术解说,具体可以看历史文章。 公众号副业:各种陪聊吹水,包括技术、就业、人生经历、大学生活、内推等等。 欢迎关注,一起侃大山

July 7, 2019 · 1 min · jiezi

JDK动态代理

1 动态代理 动态代理设计模式的原理:使用一个代理对象将原对象(目标对象)包装起来,然后利用该代理对象取代原对象。 任何对原对象的调用都要经过代理。代理对象决定是否以及何时将方法调用转到原对象上。2 动态代理用那些? 1 基于接口的动态代理 : 如 JDk 提供的代理 2 基于继承的动态代理 : 如第三方包 Cglib,javassist 动态代理这里我们进行演示JDK 自身提供的代理:jdk动态代理需要实现两个成员:一个是Proxy代理类,一个是InvocationHandler接口 1 JDK动态代理类 Proxy : 所有动态代理对象的父类,专门用于生成代理类对象或者代理对象 public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 2 接口 InvocationHandler 完成动态代理的整个过程(动态代理类的调用处理程序都必须实现InvocationHandler接口) proxy : 动态代理对象 method : 正在被调用的方法对象 args : 正在被调用的方法参数 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;接口和实现方法 public interface Arithmetic { public int add(int a,int b ); public int mul(int a,int b );}public class ArithmeticImpl implements Arithmetic { @Override public int add(int a, int b) { return a + b; } @Override public int mul(int a, int b) { return a * b; }}代理对象 ...

June 27, 2019 · 4 min · jiezi

5-分钟即可掌握的-JavaScript-装饰者模式与-AOP

什么是装饰者模式当我们拍了一张照片准备发朋友圈时,许多小伙伴会选择给照片加上滤镜。同一张照片、不同的滤镜组合起来就会有不同的体验。这里实际上就应用了装饰者模式:是通过滤镜装饰了照片。在不改变对象(照片)的情况下动态的为其添加功能(滤镜)。 需要注意的是:由于 JavaScript 语言动态的特性,我们很容易就能改变某个对象(JavaScript 中函数是一等公民)。但是我们要尽量避免直接改写某个函数,这会导致代码的可维护性、可扩展性变差,甚至会污染其他业务。 什么是 AOP想必大家对"餐前洗手、饭后漱口"都不陌生。这句标语其实就是 AOP 在生活中的例子:吃饭这个动作相当于切点,我们可以在这个切点前、后插入其它如洗手等动作。 AOP(Aspect-Oriented Programming):面向切面编程,是对 OOP 的补充。利用AOP可以对业务逻辑的各个部分进行隔离,也可以隔离业务无关的功能比如日志上报、异常处理等,从而使得业务逻辑各部分之间的耦合度降低,提高业务无关的功能的复用性,也就提高了开发的效率。 在 JavaScript 中,我们可以通过装饰者模式来实现 AOP,但是两者并不是一个维度的概念。 AOP 是一种编程范式,而装饰者是一种设计模式。 ES3 下装饰者的实现了解了装饰者模式和 AOP 的概念之后,我们写一段能够兼容 ES3 的代码来实现装饰者模式: // 原函数var takePhoto =function(){ console.log('拍照片');}// 定义 aop 函数var after=function( fn, afterfn ){ return function(){ let res = fn.apply( this, arguments ); afterfn.apply( this, arguments ); return res; }}// 装饰函数var addFilter=function(){ console.log('加滤镜');}// 用装饰函数装饰原函数takePhoto=after(takePhoto,addFilter);takePhoto();这样我们就实现了抽离拍照与滤镜逻辑,如果以后需要自动上传功能,也可以通过aop函数after来添加。 ES5 下装饰者的实现在 ES5 中引入了Object.defineProperty,我们可以更方便的给对象添加属性: let takePhoto = function () { console.log('拍照片');}// 给 takePhoto 添加属性 afterObject.defineProperty(takePhoto, 'after', { writable: true, value: function () { console.log('加滤镜'); },});// 给 takePhoto 添加属性 beforeObject.defineProperty(takePhoto, 'before', { writable: true, value: function () { console.log('打开相机'); },});// 包装方法let aop = function (fn) { return function () { fn.before() fn() fn.after() }}takePhoto = aop(takePhoto)takePhoto()基于原型链和类的装饰者实现我们知道,在 JavaScript 中,函数也好,类也好都有着自己的原型,通过原型链我们也能够很方便的动态扩展,以下是基于原型链的写法: ...

June 23, 2019 · 2 min · jiezi

重磅开源AOP-for-Flutter开发利器AspectD

https://github.com/alibaba-flutter/aspectd 问题背景随着Flutter这一框架的快速发展,有越来越多的业务开始使用Flutter来重构或新建其产品。但在我们的实践过程中发现,一方面Flutter开发效率高,性能优异,跨平台表现好,另一方面Flutter也面临着插件,基础能力,底层框架缺失或者不完善等问题。 举个栗子,我们在实现一个自动化录制回放的过程中发现,需要去修改Flutter框架(Dart层面)的代码才能够满足要求,这就会有了对框架的侵入性。要解决这种侵入性的问题,更好地减少迭代过程中的维护成本,我们考虑的首要方案即面向切面编程。 那么如何解决AOP for Flutter这个问题呢?本文将重点介绍一个闲鱼技术团队开发的针对Dart的AOP编程框架AspectD。 AspectD:面向Dart的AOP框架AOP能力究竟是运行时还是编译时支持依赖于语言本身的特点。举例来说在iOS中,Objective C本身提供了强大的运行时和动态性使得运行期AOP简单易用。在Android下,Java语言的特点不仅可以实现类似AspectJ这样的基于字节码修改的编译期静态代理,也可以实现Spring AOP这样的基于运行时增强的运行期动态代理。那么Dart呢?一来Dart的反射支持很弱,只支持了检查(Introspection),不支持修改(Modification);其次Flutter为了包大小,健壮性等的原因禁止了反射。 因此,我们设计实现了基于编译期修改的AOP方案AspectD。 设计详图 典型的AOP场景下列AspectD代码说明了一个典型的AOP使用场景: aop.dartimport 'package:example/main.dart' as app;import 'aop_impl.dart';void main()=> app.main();aop_impl.dartimport 'package:aspectd/aspectd.dart';@Aspect()@pragma("vm:entry-point")class ExecuteDemo { @pragma("vm:entry-point") ExecuteDemo(); @Execute("package:example/main.dart", "_MyHomePageState", "-_incrementCounter") @pragma("vm:entry-point") void _incrementCounter(PointCut pointcut) { pointcut.proceed(); print('KWLM called!'); }}面向开发者的API设计PointCut的设计 @Call("package:app/calculator.dart","Calculator","-getCurTime")PointCut需要完备表征以怎么样的方式(Call/Execute等),向哪个Library,哪个类(Library Method的时候此项为空),哪个方法来添加AOP逻辑。PointCut的数据结构: @pragma('vm:entry-point')class PointCut { final Map<dynamic, dynamic> sourceInfos; final Object target; final String function; final String stubId; final List<dynamic> positionalParams; final Map<dynamic, dynamic> namedParams; @pragma('vm:entry-point') PointCut(this.sourceInfos, this.target, this.function, this.stubId,this.positionalParams, this.namedParams); @pragma('vm:entry-point') Object proceed(){ return null; }}其中包含了源代码信息(如库名,文件名,行号等),方法调用对象,函数名,参数信息等。请注意这里的@pragma('vm:entry-point')注解,其核心逻辑在于Tree-Shaking。在AOT(ahead of time)编译下,如果不能被应用主入口(main)最终可能调到,那么将被视为无用代码而丢弃。AOP代码因为其注入逻辑的无侵入性,显然是不会被main调到的,因此需要此注解告诉编译器不要丢弃这段逻辑。此处的proceed方法,类似AspectJ中的ProceedingJoinPoint.proceed()方法,调用pointcut.proceed()方法即可实现对原始逻辑的调用。原始定义中的proceed方法体只是个空壳,其内容将会被在运行时动态生成。 ...

June 19, 2019 · 2 min · jiezi

装饰器代理模式与Spring-AOP

引言翻开to-do,注解认证中答应大家要讲解代理模式。 正好遇到了一道这样的题:抛开Spring来说,如何自己实现Spring AOP? 就喜欢这样的题,能把那些天天写增删改查从来不思考的人给PK下去,今天就和大家一切学习代理模式与Spring AOP。 代理与装饰器场景描述代理,即替代之意,可替代所有功能,即和原类实现相同的规范。 代理模式和装饰器模式很像,之前的装饰器讲的不是很好,这里换个例子再讲一遍。 宁静的午后,来到咖啡馆,想喝一杯咖啡。基础实现给你一个咖啡接口: public interface Coffee { /** * 打印当前咖啡的原材料,即咖啡里有什么 */ void printMaterial();}一个默认的苦咖啡的实现: public class BitterCoffee implements Coffee { @Override public void printMaterial() { System.out.println("咖啡"); }}默认的点餐逻辑: public class Main { public static void main(String[] args) { Coffee coffee = new BitterCoffee(); coffee.printMaterial(); }}点一杯咖啡。 装饰器模式优雅的服务生把咖啡端了上来,抿了一口,有些苦。 想加点糖,对服务生说:“您好,请为我的咖啡加些糖”。 /** * 糖装饰器,用来给咖啡加糖 */public class SugarDecorator implements Coffee { /** * 持有的咖啡对象 */ private final Coffee coffee; public SugarDecorator(Coffee coffee) { this.coffee = coffee; } @Override public void printMaterial() { System.out.println("糖"); this.coffee.printMaterial(); }}然后服务生就拿走了我的咖啡,去使用SugarDecorator为咖啡加糖,最后把加好糖的咖啡给我。 ...

May 11, 2019 · 2 min · jiezi

如何优雅地在-Spring-Boot-中使用自定义注解AOP-切面统一打印出入参日志

欢迎关注个人微信公众号: 小哈学Java, 文末分享阿里 P8 资深架构师吐血总结的 《Java 核心知识整理&面试.pdf》资源链接!!个人网站: https://www.exception.site/springboot/spring-boot-aop-web-request 其实,小哈在之前就出过一篇关于如何使用 AOP 切面统一打印请求日志的文章,那为什么还要再出一篇呢?没东西写了? 哈哈,当然不是!原因是当时的实现方案还是存在缺陷的,原因如下: 不够灵活,由于是以所有 Controller 方法中的方法为切面,也就是说切死了,如果说我们不想让某个接口打印出入参日志,就办不到了;Controller 包层级过深时,导致很多包下的接口切不到;今天主要说说如何通过自定义注解的方式,在 Spring Boot 中来实现 AOP 切面统一打印出入参日志。小伙伴们可以收藏一波。 废话不多说,进入正题 ! 目录一、先看看切面日志输出效果 二、添加 AOP Maven 依赖 三、自定义日志注解 四、配置 AOP 切面 五、怎么使用呢? 六、对于文件上传好使不? 七、只想在开发环境和测试环境中使用? 八、多切面如何指定优先级? 一、先看看切面日志输出效果在看看实现方法之前,我们先看下切面日志输出效果咋样: 从上图中可以看到,每个对于每个请求,开始与结束一目了然,并且打印了以下参数: URL: 请求接口地址;Description: 接口的中文说明信息;HTTP Method: 请求的方法,是 POST, GET, 还是 DELETE 等;Class Method: 被请求的方法路径 : 包名 + 方法名;IP: 请求方的 IP 地址;Request Args: 请求入参,以 JSON 格式输出;Response Args: 响应出参,以 JSON 格式输出;Time-Consuming: 请求耗时,以此估算每个接口的性能指数;怎么样?看上去效果还不错呢?接下来看看,我们要如何一步一步实现它呢? ...

May 3, 2019 · 3 min · jiezi

Spring笔记01下载概述监听器

1.Spring介绍1.1 Spring概述Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。 1.2 Spring好处方便解耦,简化开发 Spring就是一个大工厂,专门负责生成Bean,可以将所有对象创建和依赖关系维护由Spring管理。通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。AOP变成的支持 Spring提供面向切面编程,可以方便的实现对程序进行权限拦截,运行监控等功能。通过Spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。声明式事务的支持 只需要通过配置就可以完成对事务的管理,而无需手动编程方便程序的测试 Spring对Junit4支持,可以通过注解方便的测试Spring程序方便集成各种优秀框架 Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的支持,如Struts、Hibernate、Mybatis、Quartz等降低JavaEE API的使用难度 对JavaEE开发中一些难用的API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低Java源码是经典学习范例 Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java设计模式灵活运用以及对Java技术的高深造诣。它的源代码无意是Java技术的最佳实践的范例。1.3 Spring结构体系Spring框架是一个分层架构,它包含一系列的功能要素并被分为大约20个模块。这些模块分为Core Container、DataAccess/Integration、Web、AOP、Instrumentation和测试部分。如下图所示: 1.4 在项目中的架构web层:Struts,SpringMVCdao层:Hibernate,Mybatis 1.5 程序的耦合和解耦程序的耦合 我们开发种,会写很多的类,而有些类之间不可避免地产生依赖关系,这种依赖关系称为耦合。有些依赖是必须的,有些依赖关系可以通过优化代码来解除。比如:/** * 客户的业务层实现类 */public class CustomerServiceImpl implements ICustomerService { private ICustomerDao customerDao = new CustomerDaoImpl(); }上面的代码表示:业务调用持久层,并且此时业务再依赖持久层的接口和实现类。如果此时没有持久层实现类,编译将不能通过。这种依赖关系就是我们可以通过优化代码解决的。再比如:下面的代码种,我们的类依赖MySQL的具体驱动类,如果这狮虎更换了数据库品牌,我们需要改源码来修修改数据库驱动,这显然不是我们想要的。public class JdbcDemo1 { /** * JDBC操作数据库的基本入门中存在什么问题? * 导致驱动注册两次是个问题,但不是严重的。 * 严重的问题:是当前类和mysql的驱动类有很强的依赖关系。 * 当我们没有驱动类的时候,连编译都不让。 * 那这种依赖关系,就叫做程序的耦合 * * 我们在开发中,理想的状态应该是: * 我们应该尽力达到的:编译时不依赖,运行时才依赖。 * * @param args * @throws Exception */ public static void main(String[] args) throws Exception { //1.注册驱动 //DriverManager.registerDriver(new com.mysql.jdbc.Driver()); Class.forName("com.mysql.jdbc.Driver"); //2.获取连接 //3.获取预处理sql语句对象 //4.获取结果集 //5.遍历结果集 }解决程序耦合的思路 ...

April 30, 2019 · 6 min · jiezi

从 PageHelper 学到的不侵入 Signature 的 AOP

从 PageHelper 学到的不侵入 Signature 的 AOP前言最近搭新项目框架,之前 Mybatis 的拦截器都是自己写的,一般是有个 Page 类型做判断是否增加分页 sql。但是这样同样的业务开放给页面和 api 可能要写两个,一种带分页类型 Page 一种不带分页。发现开源项目 PageHelper 不需要侵入方法的 Signature 就可以做分页,特此来源码分析一下。P.S: 看后面的源码分析最好能懂 mybatis 得拦截器分页插件原理PageHelper 的简答使用 public PageInfo<RpmDetail> listRpmDetailByCondition(String filename, Date startTime, Date endTime, Integer categoryId, Integer ostypeId, Integer statusId, Integer pageNo, Integer pageSize) { PageHelper.startPage(pageNo, pageSize); List<RpmDetail> result = rpmDetailMapper.listRpmDetailByCondition(filename, startTime, endTime, categoryId, ostypeId, statusId); return new PageInfo(result); }PageHelper.startPage 解析 public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page<E> page = new Page(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); Page<E> oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page; } protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal(); protected static void setLocalPage(Page page) { LOCAL_PAGE.set(page); }可以看出在真正使用分页前,生成了一个 page 对象,然后放入了 ThreadLocal 中,这个思想很巧妙,利用每个请求 Web 服务,每个请求由一个线程处理的原理。利用线程来决定是否进行分页。是否用 ThreadLocal 来判断是否分页的猜想? //调用方法判断是否需要进行分页,如果不需要,直接返回结果 if (!dialect.skip(ms, parameter, rowBounds)) { //判断是否需要进行 count 查询 if (dialect.beforeCount(ms, parameter, rowBounds)) { //查询总数 Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql); //处理查询总数,返回 true 时继续分页查询,false 时直接返回 if (!dialect.afterCount(count, parameter, rowBounds)) { //当查询总数为 0 时,直接返回空的结果 return dialect.afterPage(new ArrayList(), parameter, rowBounds); } } resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey); } else { //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页 resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } @Override private PageParams pageParams; public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) { if (ms.getId().endsWith(MSUtils.COUNT)) { throw new RuntimeException(“在系统中发现了多个分页插件,请检查系统配置!”); } Page page = pageParams.getPage(parameterObject, rowBounds); if (page == null) { return true; } else { //设置默认的 count 列 if (StringUtil.isEmpty(page.getCountColumn())) { page.setCountColumn(pageParams.getCountColumn()); } autoDialect.initDelegateDialect(ms); return false; } } /** * 获取分页参数 * * @param parameterObject * @param rowBounds * @return / public Page getPage(Object parameterObject, RowBounds rowBounds) { Page page = PageHelper.getLocalPage(); if (page == null) { if (rowBounds != RowBounds.DEFAULT) { if (offsetAsPageNum) { page = new Page(rowBounds.getOffset(), rowBounds.getLimit(), rowBoundsWithCount); } else { page = new Page(new int[]{rowBounds.getOffset(), rowBounds.getLimit()}, rowBoundsWithCount); //offsetAsPageNum=false的时候,由于PageNum问题,不能使用reasonable,这里会强制为false page.setReasonable(false); } if(rowBounds instanceof PageRowBounds){ PageRowBounds pageRowBounds = (PageRowBounds)rowBounds; page.setCount(pageRowBounds.getCount() == null || pageRowBounds.getCount()); } } else if(parameterObject instanceof IPage || supportMethodsArguments){ try { page = PageObjectUtil.getPageFromObject(parameterObject, false); } catch (Exception e) { return null; } } if(page == null){ return null; } PageHelper.setLocalPage(page); } //分页合理化 if (page.getReasonable() == null) { page.setReasonable(reasonable); } //当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果 if (page.getPageSizeZero() == null) { page.setPageSizeZero(pageSizeZero); } return page; } /* * 获取 Page 参数 * * @return / public static <T> Page<T> getLocalPage() { return LOCAL_PAGE.get(); }果然如此,至此真相已经揭开。怎么保证之后的 sql 不使用分页呢?如: 先查出最近一个月注册的 10 个用户,再根据这些用户查出他们所有的订单。第一次查询需要分页,第二次并不需要来看看作者怎么实现的? try{ # 分页拦截逻辑 } finally { dialect.afterAll(); } @Override public void afterAll() { //这个方法即使不分页也会被执行,所以要判断 null AbstractHelperDialect delegate = autoDialect.getDelegate(); if (delegate != null) { delegate.afterAll(); autoDialect.clearDelegate(); } clearPage(); } /* * 移除本地变量 */ public static void clearPage() { LOCAL_PAGE.remove(); }总结逻辑将分页对象放入 ThreadLocal根据 ThreadLocal 判断是否进行分页无论分页与不分页都需要清除 ThreadLocal备注 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.8</version> </dependency> ...

April 19, 2019 · 3 min · jiezi

SpringBoot 2.X Kotlin系列之AOP统一打印日志

在开发项目中,我们经常会需要打印日志,这样方便开发人员了解接口调用情况及定位错误问题,很多时候对于Controller或者是Service的入参和出参需要打印日志,但是我们又不想重复的在每个方法里去使用logger打印,这个时候希望有一个管理者统一来打印,这时Spring AOP就派上用场了,利用切面的思想,我们在进入、出入Controller或Service时给它切一刀实现统一日志打印。SpringAOP不仅可以实现在不产生新类的情况下打印日志,还可以管理事务、缓存等。具体可以了解官方文档。https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#aop-api基础概念在使用SpringAOP,这里还是先简单讲解一些基本的知识吧,如果说的不对请及时指正,这里主要是根据官方文档来总结的。本章内容主要涉及的知识点。Pointcut: 切入点,这里用于定义规则,进行方法的切入(形象的比喻就是一把刀)。JoinPoint: 连接点,用于连接定义的切面。Before: 在之前,在切入点方法执行之前。AfterReturning: 在切入点方法结束并返回时执行。这里除了SpringAOP相关的知识,还涉及到了线程相关的知识点,因为我们需要考虑多线程中它们各自需要保存自己的变量,所以就用到了ThreadLocal。依赖引入这里主要是用到aop和mongodb,在pom.xml文件中加入以下依赖即可:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-mongodb</artifactId></dependency>相关实体类/** * 请求日志实体,用于保存请求日志 /@Documentclass WebLog { var id: String = "" var request: String? = null var response: String? = null var time: Long? = null var requestUrl: String? = null var requestIp: String? = null var startTime: Long? = null var endTime: Long? = null var method: String? = null override fun toString(): String { return ObjectMapper().writeValueAsString(this) }}/* * 业务对象,上一章讲JPA中有定义 /@Documentclass Student { @Id var id :String? = null var name :String? = null var age :Int? = 0 var gender :String? = null var sclass :String ?= null override fun toString(): String { return ObjectMapper().writeValueAsString(this) }}定义切面定义切入点/* * 定义一个切入,只要是为io.intodream..web下public修饰的方法都要切入 /@Pointcut(value = “execution(public * io.intodream..web..(..))")fun webLog() {}定义切入点的表达式还可以使用within、如:/** * 表示在io.intodream.web包下的方法都会被切入 /@Pointcut(value = “within(io.intodream.web..")定义一个连接点/** * 切面的连接点,并声明在该连接点进入之前需要做的一些事情 /@Before(value = “webLog()”)@Throws(Throwable::class)fun doBefore(joinPoint: JoinPoint) { val webLog = WebLog() webLog.startTime = System.currentTimeMillis() val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes? val request = attributes!!.request val args = joinPoint.args val paramNames = (joinPoint.signature as CodeSignature).parameterNames val params = HashMap<String, Any>(args.size) for (i in args.indices) { if (args[i] !is BindingResult) { params[paramNames[i]] = args[i] } } webLog.id = UUID.randomUUID().toString() webLog.request = params.toString() webLog.requestUrl = request.requestURI.toString() webLog.requestIp = request.remoteAddr webLog.method = request.method webRequestLog.set(webLog) logger.info(“REQUEST={} {}; SOURCE IP={}; ARGS={}”, request.method, request.requestURL.toString(), request.remoteAddr, params)}方法结束后执行@AfterReturning(returning = “ret”, pointcut = “webLog()”)@Throws(Throwable::class)fun doAfterReturning(ret: Any) { val webLog = webRequestLog.get() webLog.response = ret.toString() webLog.endTime = System.currentTimeMillis() webLog.time = webLog.endTime!! - webLog.startTime!! logger.info(“RESPONSE={}; SPEND TIME={}MS”, ObjectMapper().writeValueAsString(ret), webLog.time) logger.info(“webLog:{}”, webLog) webLogRepository.save(webLog) webRequestLog.remove()}这里的主要思路是,在方法执行前,先记录详情的请求参数,请求方法,请求ip, 请求方式及进入时间,然后将对象放入到ThreadLocal中,在方法结束后并取到对应的返回对象且计算出请求耗时,然后将请求日志保存到mongodb中。完成的代码package io.intodream.kotlin07.aspectimport com.fasterxml.jackson.databind.ObjectMapperimport io.intodream.kotlin07.dao.WebLogRepositoryimport io.intodream.kotlin07.entity.WebLogimport org.aspectj.lang.JoinPointimport org.aspectj.lang.annotation.AfterReturningimport org.aspectj.lang.annotation.Aspectimport org.aspectj.lang.annotation.Beforeimport org.aspectj.lang.annotation.Pointcutimport org.aspectj.lang.reflect.CodeSignatureimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.beans.factory.annotation.Autowiredimport org.springframework.core.annotation.Orderimport org.springframework.stereotype.Componentimport org.springframework.validation.BindingResultimport org.springframework.web.context.request.RequestContextHolderimport org.springframework.web.context.request.ServletRequestAttributesimport java.util./** * {描述} * * @author yangxianxi@gogpay.cn * @date 2019/4/10 19:06 * /@Aspect@Order(5)@Componentclass WebLogAspect { private val logger:Logger = LoggerFactory.getLogger(WebLogAspect::class.java) private val webRequestLog: ThreadLocal<WebLog> = ThreadLocal() @Autowired lateinit var webLogRepository: WebLogRepository /* * 定义一个切入,只要是为io.intodream..web下public修饰的方法都要切入 / @Pointcut(value = “execution(public * io.intodream..web..(..))”) fun webLog() {} /** * 切面的连接点,并声明在该连接点进入之前需要做的一些事情 / @Before(value = “webLog()”) @Throws(Throwable::class) fun doBefore(joinPoint: JoinPoint) { val webLog = WebLog() webLog.startTime = System.currentTimeMillis() val attributes = RequestContextHolder.getRequestAttributes() as ServletRequestAttributes? val request = attributes!!.request val args = joinPoint.args val paramNames = (joinPoint.signature as CodeSignature).parameterNames val params = HashMap<String, Any>(args.size) for (i in args.indices) { if (args[i] !is BindingResult) { params[paramNames[i]] = args[i] } } webLog.id = UUID.randomUUID().toString() webLog.request = params.toString() webLog.requestUrl = request.requestURI.toString() webLog.requestIp = request.remoteAddr webLog.method = request.method webRequestLog.set(webLog) logger.info(“REQUEST={} {}; SOURCE IP={}; ARGS={}”, request.method, request.requestURL.toString(), request.remoteAddr, params) } @AfterReturning(returning = “ret”, pointcut = “webLog()”) @Throws(Throwable::class) fun doAfterReturning(ret: Any) { val webLog = webRequestLog.get() webLog.response = ret.toString() webLog.endTime = System.currentTimeMillis() webLog.time = webLog.endTime!! - webLog.startTime!! logger.info(“RESPONSE={}; SPEND TIME={}MS”, ObjectMapper().writeValueAsString(ret), webLog.time) logger.info(“webLog:{}”, webLog) webLogRepository.save(webLog) webRequestLog.remove() }}这里定义的是Web层的切面,对于Service层我也可以定义一个切面,但是对于Service层的进入和返回的日志我们可以把级别稍等调低一点,这里改debug,具体实现如下:package io.intodream.kotlin07.aspectimport com.fasterxml.jackson.databind.ObjectMapperimport org.aspectj.lang.JoinPointimport org.aspectj.lang.annotation.AfterReturningimport org.aspectj.lang.annotation.Aspectimport org.aspectj.lang.annotation.Beforeimport org.aspectj.lang.annotation.Pointcutimport org.aspectj.lang.reflect.CodeSignatureimport org.slf4j.Loggerimport org.slf4j.LoggerFactoryimport org.springframework.core.annotation.Orderimport org.springframework.stereotype.Componentimport org.springframework.validation.BindingResult/* * service层所有public修饰的方法调用返回日志 * * @author yangxianxi@gogpay.cn * @date 2019/4/10 17:33 * /@Aspect@Order(2)@Componentclass ServiceLogAspect { private val logger: Logger = LoggerFactory.getLogger(ServiceLogAspect::class.java) /* * / @Pointcut(value = “execution(public * io.intodream..service..*(..))”) private fun serviceLog(){} @Before(value = “serviceLog()”) fun deBefore(joinPoint: JoinPoint) { val args = joinPoint.args val codeSignature = joinPoint.signature as CodeSignature val paramNames = codeSignature.parameterNames val params = HashMap<String, Any>(args.size).toMutableMap() for (i in args.indices) { if (args[i] !is BindingResult) { params[paramNames[i]] = args[i] } } logger.debug(“CALL={}; ARGS={}”, joinPoint.signature.name, params) } @AfterReturning(returning = “ret”, pointcut = “serviceLog()”) @Throws(Throwable::class) fun doAfterReturning(ret: Any) { logger.debug(“RESPONSE={}”, ObjectMapper().writeValueAsString(ret)) }}接口测试这里就不在贴出Service层和web的代码实现了,因为我是拷贝之前将JPA那一章的代码,唯一不同的就是加入了切面,切面的加入并不影响原来的业务流程。执行如下请求:我们会在控制台看到如下日志2019-04-14 19:32:27.208 INFO 4914 — [nio-9000-exec-1] i.i.kotlin07.aspect.WebLogAspect : REQUEST=POST http://localhost:9000/api/student/; SOURCE IP=0:0:0:0:0:0:0:1; ARGS={student={“id”:“5”,“name”:“Rose”,“age”:17,“gender”:“Girl”,“sclass”:“Second class”}}2019-04-14 19:32:27.415 INFO 4914 — [nio-9000-exec-1] org.mongodb.driver.connection : Opened connection [connectionId{localValue:2, serverValue:4}] to localhost:270172019-04-14 19:32:27.431 INFO 4914 — [nio-9000-exec-1] i.i.kotlin07.aspect.WebLogAspect : RESPONSE={“id”:“5”,“name”:“Rose”,“age”:17,“gender”:“Girl”,“sclass”:“Second class”}; SPEND TIME=239MS2019-04-14 19:32:27.431 INFO 4914 — [nio-9000-exec-1] i.i.kotlin07.aspect.WebLogAspect : webLog:{“id”:“e7b0ca1b-0a71-4fa0-9f5f-95a29d4d54a1”,“request”:"{student={"id":"5","name":"Rose","age":17,"gender":"Girl","sclass":"Second class"}}”,“response”:”{"id":"5","name":"Rose","age":17,"gender":"Girl","sclass":"Second class"}",“time”:239,“requestUrl”:"/api/student/",“requestIp”:“0:0:0:0:0:0:0:1”,“startTime”:1555241547191,“endTime”:1555241547430,“method”:“POST”}查看数据库会看到我们的请求日志已经写入了:这里有一个地方需要注意,在Service层的实现,具体如下:return studentRepository.findById(id).get()这里的findById会返回一个Optional<T>对象,如果没有查到数据,我们使用get获取数据会出现异常java.util.NoSuchElementException: No value present,可以改为返回对象可以为空只要在返回类型后面加一个?即可,同时调用Optional的ifPresent进行安全操作。 ...

April 16, 2019 · 3 min · jiezi

看完就懂的无痕埋点

无痕埋点的设计与实现在移动互联网时代,对于每个公司、企业来说,用户的数据非常重要。重要到什么程度,用户在这个页面停留多久、点击了什么按钮、浏览了什么内容、什么手机、什么网络环境、App什么版本等都需要清清楚楚。甚至一些大厂的蛮多业务成果都是靠基于用户操作行为和记录的推荐转换二次。那么有了上述的诉求,那么技术人员如何满足这些需求?引出来了一个技术点-“埋点”埋点手段业界中对于代码埋点主要有3种主流的方案:代码手动埋点、可视化埋点、无痕埋点。简单说说这几种埋点方案。代码手动埋点:根据业务需求(运营、产品、开发多个角度出发)在需要埋点地方手动调用埋点接口,上传埋点数据。可视化埋点:通过可视化配置工具完成采集节点,在前端自动解析配置并上报埋点数据,从而实现可视化“无痕埋点”无痕埋点:通过技术手段,完成对用户行为数据无差别的统计上传的工作。后期数据分析处理的时候通过技术手段筛选出合适的数据进行统计分析。技术选型代码手动埋点该方案情况下,如果需要埋点,则需要在工程代码中,写埋点相关代码。因为侵入了业务代码,对业务代码产生了污染,显而易见的缺点是埋点的成本较高、且违背了单一原则。例1:假如你需要知道用户在点击“购买按钮”时的相关信息(手机型号、App版本、页面路径、停留时间、动作等等),那么就需要在按钮的点击事件里面去写埋点统计的代码。这样明显的弊端就是在之前业务逻辑的代码上面又多出了埋点的代码。由于埋点代码分散、埋点的工作量很大、代码维护成本较高、后期重构很头痛。例2:假如 App 采用了 Hybrid 架构,当 App 的第一版本发布的时候 H5 的关键业务逻辑统计是由 Native 定义好关键逻辑(比如H5调起了Native的分享功能,那么存在一个分享的埋点事件)的桥接。假如某天增加了一个扫一扫功能,未定义扫一扫的埋点桥接,那么 H5 页面变动的时候,Native 埋点代码不去更新的话,变动的 H5 的业务就未被精确统计。优点:产品、运营工作量少,对照业务映射表就可以还原出相关业务场景、数据精细无须大量的加工和处理缺点:开发工作量大、前期需要和运营、产品指定的好业务标识,以便产品和运营进行数据统计分析可视化埋点可视化埋点的出现,是为解决代码埋点流程复杂、成本高、新开发的页面(H5、或者服务端下发的 json 去生成相应页面)不能及时拥有埋点能力前端在「埋点编辑模式」下,以“可视化”的方式去配置、绑定关键业务模块的路径到前端可以唯一确定到view的xpath过程。用户每次操作的控件,都生成一个 xpath 字符串,然后通过接口将 xpath 字符串(view在前端系统中的唯一定位。以 iOS 为例,App名称、控制器名称、一层层view、同类型view的序号:“GoodCell.21.RetailTableView.GoodsViewController.*baoApp”)到真正的业务模块(“宝App-商城控制器-分销商品列表-第21个商品被点击了”)的映射关系上传到服务端。xpath 具体是什么在下文会有介绍。之后操作 App 就生成对应的 xpath 和埋点数据(开发者通过技术手段将从服务端获取的关键数据塞到前端的 UI 控件上。 iOS 端为例, UIView 的 accessibilityIdentifier 属性可以设置我们从服务端获取的埋点数据)上传到服务端。优点:数据量相对准确、后期数据分析成本低缺点:前期控件的唯一识别、定位都需要额外开发;可视化平台的开发成本较高;对于额外需求的分析可能会比较困难无痕埋点通过技术手段无差别地记录用户在前端页面上的行为。可以正确的获取 PV、UV、IP、Action、Time 等信息。缺点:前期开发统计基础信息的技术产品成本较高、后期数据分析数据量很大、分析成本较高(大量数据传统的关系型数据库压力大)优点:开发人员工作量小、数据全面、无遗漏、产品和运营按需分析、支持动态页面的统计分析如何选择结合上述优缺点,我们选择了无痕埋点+可视化埋点结合的技术方案。怎么说呢?对于关键的业务开发结束上线后、通过可视化方案(类似于一个界面,想想看 Dreamwaver,你在界面上拖拖控件,简单编辑下就可以生成对应的 HTML 代码)点击一下绑定对应关系到服务端。那么这个对应关系是什么?我们需要唯一定位一个前端元素,那么想到的办法就是不管 Native 和 Web 前端,控件或者元素来说就是一个树形层级,DOM tree 或者 UI tree,所以我们通过技术手段定位到这个元素,以 Native iOS 为例子假如我点击商品详情页的加入购物车按钮会根据 UI 层级结构生成一个唯一标识 “addCartButton.GoodsViewController.GoodsView.*BaoApp” 。但是用户在使用 App 的时候,上传的是这串东西的 MD5到服务端。这么做有2个原因:服务端数据库存储这串很长的东西不是很好;埋点数据被劫持的话直接看到明文不太好。所以 MD5 再上传。操刀就干数据的收集实现方案由以下几个关键指标:现有代码改动少、尽量不要侵入业务代码去实现拦截系统事件全量收集如何唯一标识一个控件元素不侵入业务代码拦截系统事件以 iOS 为例。我们会想到 AOP(Aspect Oriented Programming)面向切面编程思想。动态地在函数调用前后插入相应的代码,在 Objective-C 中我们可以利用 Runtime 特性,用 Method Swizzling 来 hook 相应的函数为了给所有类方便地 hook,我们可以给 NSObject 添加个 Category,名字叫做 NSObject+MethodSwizzling+ (void)swizzleMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{ Class class = [self class]; //原有方法 Method originalMethod = class_getInstanceMethod(class, originalSelector); //替换原有方法的新方法 Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); //先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况 BOOL didAddMethod = class_addMethod(class,originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) {//添加成功:表明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP class_replaceMethod(class,swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod)); } else {//添加失败:表明源SEL已经有IMP,直接将两个SEL的IMP交换即可 method_exchangeImplementations(originalMethod, swizzledMethod); }}+ (void)swizzleClassMethod:(SEL)originalSelector swizzledSelector:(SEL)swizzledSelector{ Class class = [self class]; //原有方法 Method originalMethod = class_getClassMethod(class, originalSelector); //替换原有方法的新方法 Method swizzledMethod = class_getClassMethod(class, swizzledSelector); //先尝试給源SEL添加IMP,这里是为了避免源SEL没有实现IMP的情况 BOOL didAddMethod = class_addMethod(class,originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)); if (didAddMethod) {//添加成功:表明源SEL没有实现IMP,将源SEL的IMP替换到交换SEL的IMP method_exchangeImplementations(originalMethod, swizzledMethod); } else {//添加失败:表明SEL已经有IMP,直接将两个SEL的IMP交换即可 method_exchangeImplementations(originalMethod, swizzledMethod); }}全量收集我们会想到 hook AppDelegate 代理方法、UIViewController 生命周期方法、按钮点击事件、手势事件、各种系统控件的点击回调方法、应用状态切换等等。动作事件App 状态的切换给 Appdelegate 添加分类,hook 生命周期UIViewController 生命周期函数给 UIViewController 添加分类,hook 生命周期UIButton 等的点击UIButton 添加分类,hook 点击事件UICollectionView、UITableView 等的在对应的 Cell 添加分类,hook 点击事件手势事件 UITapGestureRecognizer、UIControl、UIResponder相应系统事件以统计页面的打开时间和统计页面的打开、关闭的需求为例,我们对 UIViewController 进行 hookstatic char *viewController_open_time = “viewController_open_time”;static char *viewController_close_time = “viewController_close_time”;// load 方法里面添加 dispatch_once 是为了防止手动调用 load 方法。+ (void)load{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ @autoreleasepool { [[self class] swizzleMethod:@selector(viewWillAppear:) swizzledSelector:@selector(viewWillAppear:)]; [[self class] swizzleMethod:@selector(viewWillDisappear:) swizzledSelector:@selector(viewWillDisappear:)]; } });}#pragma mark - add prop- (void)setOpenTime:(NSDate *)openTime{ objc_setAssociatedObject(self,&viewController_open_time, openTime, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (NSDate *)getOpenTime{ return objc_getAssociatedObject(self, &viewController_open_time);}- (void)setCloseTime:(NSDate *)closeTime{ objc_setAssociatedObject(self,&viewController_close_time, closeTime, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}- (NSDate *)getCloseTime{ return objc_getAssociatedObject(self, &viewController_close_time);}- (void)viewWillAppear:(BOOL)animated{ NSString *className = NSStringFromClass([self class]); NSString *refer = [NSString string]; if ([self getPageUrl:className]) { //设置打开时间 [self setOpenTime:[NSDate dateWithTimeIntervalSinceNow:0]]; if (self.navigationController) { if (self.navigationController.viewControllers.count >=2) { //获取当前vc 栈中 上一个VC UIViewController referVC = self.navigationController.viewControllers[self.navigationController.viewControllers.count-2]; refer = [self getPageUrl:NSStringFromClass([referVC class])]; } } if (!refer || refer.length == 0) { refer = @“unknown”; } [SDGDataCenter openPage:[self getPageUrl:className] fromPage:refer]; } [self viewWillAppear:animated];}- (void)viewWillDisappear:(BOOL)animated{ NSString className = NSStringFromClass([self class]); if ([self getPageUrl:className]) { [self setCloseTime:[NSDate dateWithTimeIntervalSinceNow:0]]; [SDGDataCenter leavePage:[self getPageUrl:className] spendTime:[self p_calculationTimeSpend]]; } [self viewWillDisappear:animated];}#pragma mark - private method- (NSString )p_calculationTimeSpend{ if (![self getOpenTime] || ![self getCloseTime]) { return @“unknown”; } NSTimeInterval aTimer = [[self getCloseTime] timeIntervalSinceDate:[self getOpenTime]]; int hour = (int)(aTimer/3600); int minute = (int)(aTimer - hour3600)/60; int second = aTimer - hour3600 - minute60; return [NSString stringWithFormat:@"%d",second];}@end如何唯一标识一个控件元素xpath 是移动端定义可操作区域的唯一标识。既然想通过一个字符串标识前端系统中可操作的控件,那么 xpath 需要2个指标:唯一性:在同一系统中不存在不同控件有着相同的 xpath稳定性:不同版本的系统中,在页面结构没有变动的情况下,不同版本的相同页面,相同的控件的 xpath 需要保持一致。我们想到 Naive、H5 页面等系统渲染的时候都是以树形结构去绘制和渲染,所以我们以当前的 View 到系统的根元素之间的所有关键点(UIViewController、UIView、UIView容器(UITableView、UICollectionView等)、UIButton…)串联起来这样就唯一定位了控件元素。为了精确定位元素节点,参看下图假设一个 UIView 中有三个子 view,先后顺序是:label、button1、button2,那么深度依次为: 0、1、2。假如用户做了某些操作将 label1 从父 view 中被移除了。此时 UIView 只有 2 个子view:button1、button2,而且深度变为了:0、1。可以看出仅仅由于其中某个子 view 的改变,却导致其它子 view 的深度都发生了变化。因此,在设计的时候需要注意,在新增/移除某一 view 时,尽量减少对已有 view 的深度的影响,调整了对节点的深度的计算方式:采用当前 view 位于其父 view 中的所有 与当前 view 同类型 子view 中的索引值。我们再看一下上面的这个例子,最初 label、button1、button2 的深度依次是:0、0、1。在 label 被移除后,button1、button2 的深度依次为:0、1。可以看出,在这个例子中,label 的移除并未对 button1、button2 的深度造成影响,这种调整后的计算方式在一定程度上增强了 xpath 的抗干扰性。另外,调整后的深度的计算方式是依赖于各节点的类型的,因此,此时必须要将各节点的名称放到viewPath中,而不再是仅仅为了增加可读性。在标识控件元素的层级时,需要知道「当前 view 位于其父 view 中的所有 与当前 view 同类型 子view 中的索引值」。参看上图,如果不是同类型的话,则唯一性得不到保证。有个问题,比如我们点击的元素是 UITableViewCell,那么它虽然可以定位到类似于这个标示 xxApp.GoodsViewController.GoodsTableView.GoodsCell,同类型的 Cell 有多个,所以单凭借这个字符串是没有办法定位具体的那个 Cell 被点击了。有2个解决方案利用系统提供的 accessibilityIdentifier 官方给出的解释是标识用户界面元素的字符串找出当前元素在父层同类型元素中的索引。根据当前的元素遍历当前元素的父级元素的子元素,如果出现相同的元素,则需要判断当前元素是所在层级的第几个元素/A string that identifies the user interface element.default == nil/@property(nullable, nonatomic, copy) NSString *accessibilityIdentifier NS_AVAILABLE_IOS(5_0);服务端下发唯一标识接口获取的数据,里面有当前元素的唯一标识。比如在 UITableView 的界面去请求接口拿到数据,那么在在获取到的数据源里面会有一个字段,专门用来存储动态化的经常变动的数据。cell.accessibilityIdentifier = [[[SDGGoodsCategoryServices sharedInstance].categories[indexPath.section] children][indexPath.row].spmContent yy_modelToJSONString];判断在同层级、同类型的控件元素里面的序号对当前的控件元素的父视图的全部子视图进行遍历,如果存在和当前的控件元素同类型的控件,那么需要判断当前控件元素在同类型控件元素中的所处的位置,那么则可以唯一定位。举例:GoodsCell-3.GoodsTableView.GoodsViewController.xxApp//UIResponder分类{ // if (self.xq_identifier_ka == nil) { if ([self isKindOfClass:[UIView class]]) { UIView *view = (id)self; NSString *sameViewTreeNode = [view obtainSameSuperViewSameClassViewTreeIndexPath]; NSMutableString *str = [NSMutableString string]; //特殊的 加减购 因为带有spm但是要区分加减 需要带TreeNode NSString *className = [NSString stringWithUTF8String:object_getClassName(view)]; if (!view.accessibilityIdentifier || [className isEqualToString:@“XQButton”]) { [str appendString:sameViewTreeNode]; [str appendString:@","]; } while (view.nextResponder) { [str appendFormat:@"%@,", NSStringFromClass(view.class)]; if ([view.class isSubclassOfClass:[UIViewController class]]) { break; } view = (id)view.nextResponder; } self.xq_identifier_ka = [self md5String:[NSString stringWithFormat:@"%@",str]]; // self.xq_identifier_ka = [NSString stringWithFormat:@"%@",str]; }// } return self.xq_identifier_ka;}// UIView 分类(NSString *)obtainSameSuperViewSameClassViewTreeIndexPat{ NSString *classStr = NSStringFromClass([self class]); //cell的子view //UITableView 特殊的superview (UITableViewContentView) //UICollectionViewCell BOOL shouldUseSuperView = ([classStr isEqualToString:@“UITableViewCellContentView”]) || ([[self.superview class] isKindOfClass:[UITableViewCell class]])|| ([[self.superview class] isKindOfClass:[UICollectionViewCell class]]); if (shouldUseSuperView) { return [self obtainIndexPathByView:self.superview]; }else { return [self obtainIndexPathByView:self]; }}(NSString )obtainIndexPathByView:(UIView )view{ NSInteger viewTreeNodeDepth = NSIntegerMin; NSInteger sameViewTreeNodeDepth = NSIntegerMin; NSString *classStr = NSStringFromClass([view class]); NSMutableArray *sameClassArr = [[NSMutableArray alloc]init]; //所处父view的全部subviews根节点深度 for (NSInteger index =0; index < view.superview.subviews.count; index ++) { //同类型 if ([classStr isEqualToString:NSStringFromClass([view.superview.subviews[index] class])]){ [sameClassArr addObject:view.superview.subviews[index]]; } if (view == view.superview.subviews[index]) { viewTreeNodeDepth = index; break; } } //所处父view的同类型subviews根节点深度 for (NSInteger index =0; index < sameClassArr.count; index ++) { if (view == sameClassArr[index]) { sameViewTreeNodeDepth = index; break; } } return [NSString stringWithFormat:@"%ld",sameViewTreeNodeDepth]; }## 数据的上传数据通过上面的办法收集完了,那么如何及时、高效的上传到后端,给运营分析、处理呢?App 运行期间用户会点击非常多的数据,如果实时上传的话对于网络的利用率较低,所以需要考虑一个机制去控制用户产生的埋点数据的上传。思路是这样的。对外部暴露出一个接口,用来将产生的数据往数据中心存储。用户产生的数据会先保存到 AppMonitor 的内存中去,设置一个临界值(memoryEventMax = 50),如果存储的值达到设置的临界值 memoryEventMax,那么将内存中的数据写入文件系统,以 zip 的形式保存下来,然后上传到埋点系统。如果没有达到临界值但是存在一些 App 状态切换的情况,这时候需要及时保存数据到持久化。当下次打开 App 就去从本地持久化的地方读取是否有未上传的数据,如果有就上传日志信息,成功后删除本地的日志压缩包。App 应用状态的切换策略如下:- didFinishLaunchWithOptions:内存日志信息写入硬盘- didBecomeActive:上传- willTerimate:内存日志信息写入硬盘- didEnterBackground:内存日志信息写入硬盘// 将App日志信息写入到内存中。当内存中的数量到达一定规模(超过设置的内存中存储的数量)的时候就将内存中的日志存储到文件信息中(void)joinEvent:(NSDictionary *)dictionary{if (dictionary) { NSDictionary *tmp = [self createDicWithEvent:dictionary]; if (!s_memoryArray) { s_memoryArray = [NSMutableArray array]; } [s_memoryArray addObject:tmp]; if ([s_memoryArray count] >= s_flushNum) { [self writeEventLogsInFilesCompletion:^{ [self startUploadLogFile]; }]; }}}// 外界调用的数据传递入口(App埋点统计)(void)traceEvent:(AMStatisticEvent *)event{// 线程锁,防止多处调用产生并发问题@synchronized (self) { if (event && event.userInfo) { [self joinEvent:event.userInfo]; }}}// 将内存中的数据写入到文件中,持久化存储(void)writeEventLogsInFilesCompletion:(void(^)(void))completionBlock{NSArray *tmp = nil;@synchronized (self) { tmp = s_memoryArray; s_memoryArray = nil;}if (tmp) { __weak typeof(self) weakSelf = self; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *jsonFilePath = [weakSelf createTraceJsonFile]; if ([weakSelf writeArr:tmp toFilePath:jsonFilePath]) { NSString *zipedFilePath = [weakSelf zipJsonFile:jsonFilePath]; if (zipedFilePath) { [AppMonotior clearCacheFile:jsonFilePath]; if (completionBlock) { completionBlock(); } } } });}}// 从App埋点统计压缩包文件夹中的每个压缩包文件上传服务端,成功后就删除本地的日志压缩包(void)startUploadLogFile{NSArray *fList = [self listFilesAtPath:[self eventJsonPath]];if (!fList || [fList count] == 0) { return;}[fList enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if (![obj hasSuffix:@".zip"]) { return; } NSString *zipedPath = obj; unsigned long long fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:zipedPath error:nil] fileSize]; if (!fileSize || fileSize < 1) { return; } [self uploadZipFileWithPath:zipedPath completion:^(NSString *completionResult) { if ([completionResult isEqual:@“OK”]) { [AppMonotior clearCacheFile:zipedPath]; } }];}];}总结下来关键步骤:1. hook 系统的各种事件(UIResponder、UITableView、UICollectionView代理事件、UIControl事件、UITapGestureRecognizers)、hook 应用程序、控制器生命周期。在做本来的逻辑之前添加额外的监控代码2. 对于点击的元素按照视图树生成对应的唯一标识(addCartButton.GoodsView.GoodsViewController) 的 md5 值3. 在业务开发完毕,进入埋点的编辑模式,将 md5 和关键的页面的关键事件(运营、产品想统计的关键模块:App层级、业务模块、关键页面、关键操作)给绑定起来。比如 addCartButton.GoodsView.GoodsViewController.tbApp 对应了 tbApp-商城模块-商品详情页-加入购物车功能。4. 将所需要的数据存储下来5. 设计机制等到合适的时机去上传数据 ...

April 2, 2019 · 4 min · jiezi

吃透动态代理,解密spring AOP源码(四)

前面讲到了动态代理的底层原理,接下来我们来看一下aop的动态代理.Spring AOP使用了两种代理机制:一种是基于JDK的动态代理,一种是基于CGLib的动态代理.①JDK动态代理:使用JDK创建代理有一个限制,它只能为接口创建代理实例.这一点可以从Proxy的接口方法newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)中看的很清楚第二个入参 interfaces就是需要代理实例实现的接口列表.②CGLib:采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑.③对比:CGLib所创建的动态代理对象的性能比JDK的高大概10倍,但CGLib在创建代理对象的时间比JDK大概多8倍,所以对于singleton的代理对象或者具有实例池的代理,因为无需重复的创建代理对象,所以比较适合CGLib动态代理技术,反之选择JDK代理。值得一提的是由于CGLib采用动态创建子类的方式生成代理对象,所以不能对目标类中final的方法进行代理。但是这种实现方式存在三个明显需要改进的地方:a.目标类的所有方法都添加了横切逻辑,而有时,这并不是我们所期望的,我们可能只希望对业务类中的某些特定的方法添加横切逻辑;b.我们通过硬编码的方式制定了织入横切逻辑的织入点,即在目标业务方法的开始和结束前织入代码;c.我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法做到通用;还有一个问题是:spring依赖注入时,什么时候会创建代理类,有时候是cglib有时候是jdkproxy有时候只是普通实例,有兴趣的可以查阅资料,getBean依赖注入过程,可查看IOC源码。下面我们举个例子看看aop事务注解是怎么实现的。 JDK动态代理:aop中生成的代理类是JdkDynamicAopProxy子类,debug调试的时候可以看到,打开源码可看到实现了AopProxy和invocationHandler也就实现invoke方法。 invoke关键代码:// Get the interception chain for this method.加载一系列的拦截器List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);TransactionInterceptor是事务拦截器,所有带有@Transactional注解的方法都会经过拦截器invoke方法拦截,点进方法里面可以发现代码如下:比如回滚方法点进去发现是获取事务管理器然后回滚

March 18, 2019 · 1 min · jiezi

吃透动态代理,解密spring AOP源码(三)

上节讲到动态代理生成的类为$Proxy0,但是在我们项目里面却不存在,实际我们是用了这个实现类调用了方法,想要知道这个问题,首先要理解类的完整生命周期.Java源文件:即我们在IDE里面写的.java文件Java字节码:即编译器编译之后的.class文件(javac命令).备注:Java代码为何能够跨平台,和Java字节码技术是分不开的,这个字节码在windows,在linux下都是可以运行的class对象:工程启动的时候classLoader类加载器会扫描这些字节码并加载到classLoader上面生成class对象,有了类对象,便可以new实例了。(class对象保存在方法区元空间JDK1.8)卸载:垃圾回收,关于回收机制,算法有兴趣可以去了解。class对象什么时候被回收?答:可达性分析,当发现某个类不被引用,类会被回收类的生命周期与动态代理关系 动态代理是没有Java源文件,直接生成Java字节码的,加载到JVM上面的。字节码来源于内存,比如tomcat的热加载就是从网络传输过来的。 既然是直接生成的Java字节码,是怎么生成的?从源码开始分析,从Proxy.newProxyInstance方法开始看。Class<?> cl = getProxyClass0(loader, intfs);这行代码生成了.class字节码并且生成了class对象,然后拿这个类对象获取构造函数,再newInstance,生成实例对象,是通过反射的机制。重点还是怎么生成.class字节码。接下来apply()方法往下看生成了字节码数组,从而生成了Java字节码,defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length)则是加载字节码文件,此方法为native方法,C语言方法,操作系统类库(C/C++/汇编)。字节码文件的结构是如何的呢?我们把class文件生成出来并在反编译工具打开,这里就用到了源码里面的方法了。生成.class文件的代码如下:public static void generateClass(String proxyName, Class[] paramArrayOfClass, Class clazz) throws IOException { byte[] classFile=ProxyGenerator.generateProxyClass( proxyName, paramArrayOfClass); String path=clazz.getResource(".").getPath(); System.out.println(path); FileOutputStream outputStream =null; outputStream = new FileOutputStream(path + proxyName + “p.class”); outputStream.write(classFile); outputStream.flush(); outputStream.close(); } 在刚刚的动态代理测试类增加几行代码: public static void main(String[] args) throws IOException { // 代购公司C,负责代购所有产品 DynamicProxyCompanyC proxy = new DynamicProxyCompanyC(); // 日本有家A公司生产男性用品 ManToolFactory dogToolFactory = new AManFactory(); // 代购A公司的产品 proxy.setFactory(dogToolFactory); // 创建A公司的代理对象 ManToolFactory proxyObject = (ManToolFactory) proxy.getProxyInstance(); // 代理对象完成代购男性用品 proxyObject.saleManTool(“D”); System.out.println("————–"); // 日本有家B公司生产女性用品 WomanToolFactory womanToolFactory = new BWomanFactory(); // 代购B公司的产品 proxy.setFactory(womanToolFactory); // 创建B公司的代理对象 WomanToolFactory proxyObject1 = (WomanToolFactory) proxy.getProxyInstance(); // 代理对象完成代购女性用品 proxyObject1.saleWomanTool(1.8); //生成代理类的.class文件 DynamicProxyCompanyC.generateClass(proxyObject1.getClass().getSimpleName(), womanToolFactory.getClass().getInterfaces(), womanToolFactory.getClass()); } 根据打印出来的class文件路径打开并在反编译工具上打开动态代理生成的类就是这个了,调用业务方法saleWomanTool实际上变成了这个h.invoke,而这个h是所有Proxy类里面含有的 protected InvocationHandler h;(源码可见),在用Proxy创建代理实例的时候已经传入过了。所以调用方法saleWomanTool就有了前置增强和后置增强。到这里已经解开了动态代理的原理 ...

March 14, 2019 · 1 min · jiezi

吃透动态代理,解密spring AOP源码(二)

紧接着上节,为了解决静态代理的问题,出现了动态代理,先上代码:/** * 动态代理 */public class DynamicProxyCompanyC implements InvocationHandler { // 被代理的对象,即真实对象 private Object factory; public Object getFactory() { return factory; } public void setFactory(Object factory) { this.factory = factory; } // 通过proxy获取动态代理的对象 public Object getProxyInstance() { //第三个参数是InvocationHandler,传入自身说明此proxy对象是和自身的invoke方法合作的,代理对象方法调用会经过下面invoke的增强 return Proxy.newProxyInstance(factory.getClass().getClassLoader(), factory.getClass().getInterfaces(), this); } @Override /**通过动态代理对象对方法进行增强 * @param proxy 代理对象 * @param method 要增强的方法(拦截的方法) * @param args 方法参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { dosomeThingBefore(); Object ret = method.invoke(factory, args);// 通过反射机制调用方法 dosomeThingAfter(); return ret; } public void dosomeThingBefore() { System.out.println(“售前服务,负责产品调研,兴趣爱好”); } public void dosomeThingAfter() { System.out.println(“售后服务,包装丶送货上门一条龙服务”); }}假设动态代理是一个代购公司,私有变量Object factory为动态生成的具体的真实对象,可代购对应的产品 测试类:public class Proxytest { public static void main(String[] args) { // 代购公司C,负责代购所有产品 DynamicProxyCompanyC proxy = new DynamicProxyCompanyC(); // 日本有家A公司生产男性用品 ManToolFactory dogToolFactory = new AManFactory(); // 代购A公司的产品 proxy.setFactory(dogToolFactory); // 创建A公司的代理对象 ManToolFactory proxyObject = (ManToolFactory) proxy.getProxyInstance(); // 代理对象完成代购男性用品 proxyObject.saleManTool(“D”); System.out.println("————–"); // 日本有家B公司生产女性用品 WomanToolFactory womanToolFactory = new BWomanFactory(); // 代购B公司的产品 proxy.setFactory(womanToolFactory); // 创建B公司的代理对象 WomanToolFactory proxyObject1 = (WomanToolFactory) proxy.getProxyInstance(); // 代理对象完成代购女性用品 proxyObject1.saleWomanTool(1.8); }}// 售前服务,负责产品调研,兴趣爱好// A工厂出售男性用品,D罩杯// 售后服务,包装丶送货上门一条龙服务// ————–// 售前服务,负责产品调研,兴趣爱好// B工厂生产女性用品,长度1.8米// 售后服务,包装丶送货上门一条龙服务 动态代理解决了上节说的开闭原则,那么接下来我们要解密动态代理的原理,重点类DynamicProxyCompanyC :1.实现了InvocationHandler接口;2.通过proxy获取动态代理的对象。根据我们此例子里面来说,动态代理就类似一个代购公司,可代购所有产品,需要购买哪个产品的时候就实例化一个真实对象(如测试类需要男性用品则将接口引用指向真实对象AManFactory),根据真实对象创建代理对象来执行具体的方法,图解如下:Proxy:接下来我们先初步看一下JDK里面的Proxy这个源码这个注释是说Proxy提供个一个静态方法来创建代理类和代理实例,它也是所有由此方法创建的代理类的父类。静态方法创建代理实例即方法newProxyInstance(ClassLoader loader,Class<?>[]interfaces,InvocationHandler h);InvocationHandler :InvocationHandler 是一个接口,定义了invoke(Object proxy, Method method, Object[] args)方法总的来说Proxy专门负责new一个实例(真实对象),而具体方法做什么,业务怎样增强就由InvocationHandler(抽象对象)的invoke方法(抽象对象即接口定义的方法)来决定。接下来我们要搞清楚动态代理的底层原理,首先我们调试一下,会发现 ManToolFactory proxyObject = (ManToolFactory) proxy.getProxyInstance()中创建的proxyObject 对象类名是&Proxy0,是ManToolFactory接口的实现类。但是我们项目工程里面却没有&Proxy0这个类,那它究竟是怎么出现的,下节讲解。 ...

March 13, 2019 · 1 min · jiezi

吃透动态代理,解密spring AOP源码(一)

首先讲讲代理模式。什么是静态代理,为什么需要动态代理?代理模式:定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用。目的:1.通过引入代理对象来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;2.通过代理对象对原有的业务增强。如图代理模式类图:简单看了下图解就开始上代码吧1.先定义一个抽象对象即公共接口类/** * 负责生产男性用品 /public interface ManToolFactory { public void saleManTool(String size);}2.真实对象 /* * A工厂负责生产男性用品 / public class AManFactory implements ManToolFactory { @Override public void saleManTool(String size) { System.out.println(“A工厂出售男性用品,大小为” + size ); }}3.代理对象 /* * 静态代理类 /public class StaticProxy implements ManToolFactory { // 代理的真实对象,多个的话考虑公用object也就是动态代理的实现 private AManFactory aManFactory;// 类似搬运工,代理真实对象的方法 public StaticProxy(AManFactory aManFactory) { this.aManFactory = aManFactory; } @Override public void saleManTool(String size) { dosomeThingBefore();//前置增强 aManFactory.saleManTool(size); dosomeThingAfter();//后置增强 } public void dosomeThingBefore() { System.out.println(“售前服务,负责产品的调研工作”); } public void dosomeThingAfter() { System.out.println(“售后服务,送门服务,三包等”); }}由代理模式可增强原有业务。问题来了,如今代理不仅仅帮忙代购男性用品,也要代购女性用品,那同样的我们就再定义一个接口。 /* * 负责生产女性用品的抽象对象 / public interface WomanToolFactory { public void saleWomanTool(Double length); } /* * B工厂专门负责生产男性用品(真实对象) */ public class BWomanFactory implements WomanToolFactory { @Override public void saleWomanTool(Double length) { System.out.println(“B工厂生产女性用品,长度” + length); } }此时代理类需要修改,开始思路如下public class StaticProxy implements ManToolFactory,WomanToolFactory { private AManFactory aManFactory; private BWomanFactory bManFactory; @Override public void saleManTool(String size) { //TODO } @Override public void saleWomanTool(Double length) { //TODO }那如果再多一个业务,代购点别的产品,那是不是又要再实现一个接口,这样就违背了设计模式的原则:开闭原则因此动态代理就出现了。动态代理看下节 ...

March 13, 2019 · 1 min · jiezi

SpringCloud基础篇AOP之拦截优先级详解

相关文章可以查看: http://spring.hhui.top190301-SpringBoot基础篇AOP之基本使用姿势小结190302-SpringBoot基础篇AOP之高级使用技能前面两篇分别介绍了AOP的基本使用姿势和一些高级特性,当时还遗留了一个问题没有说明,即不同的advice,拦截同一个目标方法时,优先级是怎样的,本篇博文将进行详细分析同一个切面中,不同类型的advice的优先级同一个切面中,同一种类型的advice优先级不同切面中,同一类型的advice优先级不同切面中,不同类型的advice优先级<!– more –>I. 统一切面,不同类型ddvice优先级在不分析源码的前提下,也只能通过实际的case来看优先级问题了,我们现在设计一下使用实例,通过输出结果来看对应的优先级1. case设计首先创建被拦截的bean: com.git.hui.boot.aop.order.InnerDemoBean@Componentpublic class InnerDemoBean { public String print() { try { System.out.println(“in innerDemoBean start!”); String rans = System.currentTimeMillis() + “|” + UUID.randomUUID(); System.out.println(rans); return rans; } finally { System.out.println(“in innerDemoBean over!”); } }}接下来写一个切面,里面定义我们常见的各种advice对于aop的使用,有疑问的可以参考: 190301-SpringBoot基础篇AOP之基本使用姿势小结@Component@Aspectpublic class OrderAspect { @Pointcut(“execution(public * com.git.hui.boot.aop.order..())”) public void point() { } @Before(value = “point()”) public void doBefore(JoinPoint joinPoint) { System.out.println(“do before!”); } @After(value = “point()”) public void doAfter(JoinPoint joinPoint) { System.out.println(“do after!”); } @AfterReturning(value = “point()”, returning = “ans”) public void doAfterReturning(JoinPoint joinPoint, String ans) { System.out.println(“do after return: " + ans); } @Around(“point()”) public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { try { System.out.println(“do in around before”); return joinPoint.proceed(); } finally { System.out.println(“do in around over!”); } }}2. 测试使用SpringBoot的项目进行测试aop,使用还是比较简单的@SpringBootApplicationpublic class Application { private InnerDemoBean innerDemoBean; public Application(InnerDemoBean innerDemoBean) { this.innerDemoBean = innerDemoBean; this.innerDemoBean(); } private void innerDemoBean() { System.out.println(“result: " + innerDemoBean.print()); } public static void main(String[] args) { SpringApplication.run(Application.class); }}看下上面执行的输出结果do in around beforedo before!in innerDemoBean start!1552219604035|e9a31f44-6a31-4485-806a-834361842ce1in innerDemoBean over!do in around over!do after!do after return: 1552219604035|e9a31f44-6a31-4485-806a-834361842ce1result: 1552219604035|e9a31f44-6a31-4485-806a-834361842ce1从输出结果进行反推,我们可以知道统一切面中,advice执行的先后顺序如下II. 同一切面,同一类型切面正常来讲,拦截一个方法时,统一类型的切面逻辑都会写在一起,那这个case有什么分析的必要呢?在我们实际的使用中,同一类型的advice拦截同一个方法的可能性还是很高的,why? 因为多个advice有自己定义的拦截规则,它们之间并不相同,但可能存在交集,比如我们在上面的切面中,再加一个拦截注解的before advice1. case设计依然是上面的InnerDemoBean,方法上加一个自定义注解@AnoDotpublic String print() { try { System.out.println(“in innerDemoBean start!”); String rans = System.currentTimeMillis() + “|” + UUID.randomUUID(); System.out.println(rans); return rans; } finally { System.out.println(“in innerDemoBean over!”); }}然后加一个拦截注解的advice@Before("@annotation(AnoDot)")public void doAnoBefore(JoinPoint joinPoint) { System.out.println(“dp AnoBefore”);}2. 测试再次执行前面的case,然后看下输出结果如下In NetAspect doAround before!do in around beforedp AnoBeforedo before!in innerDemoBean start!1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0in innerDemoBean over!do in around over!do after!do after return: 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0In NetAspect doAround over! ans: 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0result: 1552221765322|d92b6d37-0025-43c0-adcc-c4aa7ba639e0我们主要看下两个before,发现 AnoBefore 在前面; 因此这里的一个猜测,顺序就是根据方法命名的顺序来的,比如我们再加一个 doXBefore,然后我们预估输出结果应该是do AnoBefore > doBefore > doXBefore额外添加一个@Before("@annotation(AnoDot)")public void doXBefore(JoinPoint joinPoint) { System.out.println(“dp XBefore”);}接着就是输出结果如下,和我们预期一致3. Order注解尝试我们知道有个Order注解可以来定义一些优先级,那么把这个注解放在advice方法上,有效么?实际尝试一下@Order(1)@Before(value = “point()")public void doBefore(JoinPoint joinPoint) { System.out.println(“do before!”);}@Order(2)@Before("@annotation(AnoDot)")public void doAnoBefore(JoinPoint joinPoint) { System.out.println(“dp AnoBefore”);}@Order(3)@Before("@annotation(AnoDot)")public void doXBefore(JoinPoint joinPoint) { System.out.println(“dp XBefore”);}如果注解有效,我们预期输出结果如下do Before > do AnoBefore > do XBefore然后再次执行,看下输出结果是否和我们预期一样4. 小结同一个切面中,相同的类型的advice,优先级是根据方法命名来的,加@Order注解是没有什么鸟用的,目前也没有搜索到可以调整优先级的方式III. 不同切面,相同类型的advice如果说上面这种case不太好理解为啥会出现的话,那么这个可能就容易理解多了;毕竟一个切面完成一件事情,出现相同的advice就比较常见了;比如spring mvc中,我们通常会实现的几个切面一个before advice的切面,实现输出请求日志一个before advice的切面,实现安全校验(这种其实更常见的是放在filter/intercept中)1. case设计现在就需要再加一个切面,依然以before advice作为case@Aspect@Componentpublic class AnotherOrderAspect { @Before("@annotation(AnoDot)”) public void doBefore() { System.out.println(“in AnotherOrderAspect before!”); }}2. 测试接下来看测试输出结果如下图发现了一个有意思的事情了,AnotherOrderAspect切面的输出,完全在OrderAspect切面中所有的advice之前,接着我们再次尝试使用@Order注解来试试,看下会怎样@Order(0)@Component@Aspectpublic class OrderAspect {}@Aspect@Order(10)@Componentpublic class AnotherOrderAspect {}如果顺序有关,我们预期的输出结果应该是do AnoBefore > do Before > doXBefore > do AnotherOrderAspect before!实际测试输出如下,和我们预期一致3. 小结从上面的测试来看,不同的切面,默认顺序实际上是根据切面的命令来的;A切面中的advice会优先B切面中同类型的advice我们可以通过 Order 注解来解决不同切面的优先级问题,依然是值越小,优先级越高IV. 不同切面,不同advice顺序其实前面的case已经可以说明这个问题了,现在稍稍丰富一下AnotherOrderAspect,看下结果1. case设计@Aspect@Order(10)@Componentpublic class AnotherOrderAspect { @Before("@annotation(AnoDot)”) public void doBefore() { System.out.println(“in AnotherOrderAspect before!”); } @After("@annotation(AnoDot)”) public void doAfter(JoinPoint joinPoint) { System.out.println(“do AnotherOrderAspect after!”); } @AfterReturning(value = “@annotation(AnoDot)”, returning = “ans”) public void doAfterReturning(JoinPoint joinPoint, String ans) { System.out.println(“do AnotherOrderAspect after return: " + ans); } @Around("@annotation(AnoDot)”) public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable { try { System.out.println(“do AnotherOrderAspect in around before”); return joinPoint.proceed(); } finally { System.out.println(“do AnotherOrderAspect in around over!”); } }}2. 测试看下执行后的输出结果假设A切面优先级高于B切面,那么我们执行先后顺序如下V. 小结本篇内容有点多,针对前面的测试以及结果分析,给出一个小结,方便直接获取最终的答案1. 不同advice之间的优先级顺序around 方法执行前代码 > before > 方法执行 > around方法执行后代码 > after > afterReturning/@AfterThrowing2. 统一切面中相同advice统一切面中,同类型的advice的优先级根据方法名决定,暂未找到可以控制优先级的使用方式3. 不同切面优先级不同切面优先级,推荐使用 @Order注解来指定,数字越低,优先级越高4. 不同切面advice执行顺序优先级高的切面中的advice执行顺序会呈现包围优先级低的advice的情况,更直观的先后顺序,推荐看第四节的顺序图,更加清晰明了VI. 其他0. 项目工程:https://github.com/liuyueyi/spring-boot-demomodule: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/010-aop1. 一灰灰Blog一灰灰Blog个人博客 https://blog.hhui.top一灰灰Blog-Spring专题博客 http://spring.hhui.top一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛2. 声明尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激微博地址: 小灰灰BlogQQ: 一灰灰/33027978403. 扫描关注一灰灰blog知识星球 ...

March 12, 2019 · 2 min · jiezi

拥有者权限验证

问题描述在做权限验证的时候,我们经常会遇到这样的情况:教师拥有多个学生,但是在处理学生信息的时候,教师只能操作自己班级的学生。所以,我们要做的就是,当教师尝试处理别的班的学生的时候,抛出异常。实体关系用户1:1教师,教师m:n班级,班级1:n学生实现思路以findById为例。因为从整体上看,用户和学生是m:n的关系,所以在调用这个接口的时候,获取该学生的所有用户,然后跟当前登录用户进行对比,如果不在其中,抛出异常。利用切面,我们可以在findById、update、delete方法上进行验证。注解我们会在方法上添加注解,以表示对该方法进行权限验证。@Target(ElementType.METHOD) // 注解使用在方法上@Retention(RetentionPolicy.RUNTIME) // 运行时生效public @interface AuthorityAnnotation { /** * 仓库名 / @Required Class repository();}因为我们需要获取出学生,但是并不限于学生,所以就要将仓库repository作为一个参数传入。实体上面我们说过,需要获取学生中的用户,所以我们可以在实体中定义一个方法,获取所有有权限的用户:getBelongUsers()但是,我们知道,学生和用户没用直接的关系,而且为了复用,在对其他实体进行验证的时候也能使用,可以考虑创建一个接口,让需要验证的实体去实现他。这样,我们可以在让每个实体都集成这个接口,然后形成链式调用,这样就解决了上面你的两个问题。public interface BaseEntity { List<User> getBelongToUsers();}教师:@Entitypublic class Teacher implements YunzhiEntity, BaseEntity { … @Override public List<User> getBelongToUsers() { List<User> userList = new ArrayList<>(); userList.add(this.getUser()); return userList; }}班级:@Entitypublic class Klass implements BaseEntity { … @Override public List<User> getBelongToUsers() { List<User> userList = new ArrayList<>(); for (Teacher teacher: this.getTeacherList()) { userList.addAll(teacher.getBelongToUsers()); } return userList; }}学生:@Entitypublic class Student implements BaseEntity { … @Override public List<User> getBelongToUsers() { return this.getKlass().getBelongToUsers(); }}切面有了实体后,我们就可以建立切面实现验证功能了。@Aspect@Componentpublic class OwnerAuthorityAspect { private static final Logger logger = LoggerFactory.getLogger(OwnerAuthorityAspect.class.getName()); /* * 使用注解,并第一个参数为id / @Pointcut("@annotation(com.yunzhiclub.alice.annotation.AuthorityAnnotation) && args(id,..) && @annotation(authorityAnnotation)") public void doAccessCheck(Long id, AuthorityAnnotation authorityAnnotation) { } @Before(“doAccessCheck(id, authorityAnnotation)”) public void before(Long id, AuthorityAnnotation authorityAnnotation) { }首先,我们要获取到待操作对象。但是在获取对象之前,我们必须获取到repository。这里我们利用applicationContext来获取仓库bean,然后再利用获取到的bean,生成repository对象。@Aspect@Componentpublic class OwnerAuthorityAspect implements ApplicationContextAware { private ApplicationContext applicationContext = null; // 初始化上下文 …… @Before(“doAccessCheck(id, authorityAnnotation)”) public void before(Long id, AuthorityAnnotation authorityAnnotation) { logger.debug(“获取注解上的repository, 并通过applicationContext来获取bean”); Class<?> repositoryClass = authorityAnnotation.repository(); Object object = applicationContext.getBean(repositoryClass); logger.debug(“将Bean转换为CrudRepository”); CrudRepository<BaseEntity, Object> crudRepository = (CrudRepository<BaseEntity, Object>)object; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}该类实现了ApplicationContextAware接口,通过setApplicationContext函数获取到了applicationContext。接下来,就是利用repository获取对象,然后获取他的所属用户,再与当前登录用户进行比较。@Before(“doAccessCheck(id, authorityAnnotation)")public void before(Long id, AuthorityAnnotation authorityAnnotation) { logger.debug(“获取注解上的repository, 并通过applicationContext来获取bean”); Class<?> repositoryClass = authorityAnnotation.repository(); Object object = applicationContext.getBean(repositoryClass); logger.debug(“将Bean转换为CrudRepository”); CrudRepository<BaseEntity, Object> crudRepository = (CrudRepository<BaseEntity, Object>)object; logger.debug(“获取实体对象”); Optional<BaseEntity> baseEntityOptional = crudRepository.findById(id); if(!baseEntityOptional.isPresent()) { throw new RuntimeException(“对不起,未找到相关的记录”); } BaseEntity baseEntity = baseEntityOptional.get(); logger.debug(“获取登录用户以及拥有者,并进行比对”); List<User> belongToTUsers = baseEntity.getBelongToUsers(); User currentLoginUser = userService.getCurrentLoginUser(); Boolean havePermission = false; if (currentLoginUser != null && belongToTUsers.size() != 0) { for (User user: belongToTUsers) { if (user.getId().equals(currentLoginUser.getId())) { havePermission = true; break; } } if (!havePermission) { throw new RuntimeException(“权限不允许”); } }}使用在控制器的方法上使用注解:@AuthorityAnnotation,传入repository。@RestController@RequestMapping("/student”)public class StudentController { private final StudentService studentService; // 学生 @Autowired public StudentController(StudentService studentService) { this.studentService = studentService; } /* * 通过id获取学生 * * @param id * @return */ @AuthorityAnnotation(repository = StudentRepository.class) @GetMapping("/{id}") @JsonView(StudentJsonView.get.class) public Student findById(@PathVariable Long id) { return studentService.findById(id); }}出现的问题实现之后,进行单元测试的过程中出现了问题。@Testpublic void update() throws Exception { logger.info(“获取一个保存学生”); Student student = studentService.getOneSaveStudent(); Long id = student.getId(); logger.info(“获取一个更新学生”); Student newStudent = studentService.getOneUnSaveStudent(); String jsonString = JSONObject.toJSONString(newStudent); logger.info(“发送更新请求”); this.mockMvc .perform(put(baseUrl + “/” + id) .cookie(this.cookie) .content(jsonString) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(status().isOk());}400的错误,说明参数错误,参数传的是实体,看下传了什么:我们看到,这个字段并不是我们实体中的字段,但是为什么序列化的时候出现了这个字段呢?原因是这样的,我们在实体中定义了一个getBelongToUsers函数,然后JSONobject在进行序列化的时候会根据实体中的getter方法,获取get后面的为key,也就是将belongToUsers看做了字段。所以就出现了上面传实体字段多出的情况,从而引发了400的错误。解决我们不想JSONobject在序列化的时候处理getBelongToUsers,就需要声明一下,这里用到了注解:@JsonIgnore。这样在序列化的时候就会忽略它。@Entitypublic class Student implements BaseEntity { …… @JsonIgnore @Override public List<User> getBelongToUsers() { return this.getKlass().getBelongToUsers(); }}修改后的学生实体如上,其他实现了getBelongToUsers方法的,都需要做相同处理。总结在解决这个问题的时候,开始就是自己埋头写,很多细节都没有处理好。然后偶然google到了潘老师之前写过的一篇文章,就对前面写的进行了完善。虽然自己解决问题的过程还是有很多收获的,但是如果开始直接参考这篇文章,会省不少事。其实是这样的,我们写博客,一方面是让自己有所提升,另一方面也是为了团队中的其他成员少走一些弯路。看来这次我是没有好好利用资源了。相关参考:https://my.oschina.net/dashan…https://segmentfault.com/a/11… ...

March 9, 2019 · 2 min · jiezi

aop初探

在本周的项目中第一次尝试了aop这个鼎鼎大名的东西,以前一直觉得这个东西会很难理解,就没有接触,不过再真正接触以后发现基本的使用还是很简单的,当然有这种感觉少不了学长的帮助,感谢张喜硕学长。aopaop是什么呢?用于干什么?AOP的理念:就是将分散在各个业务逻辑代码中相同的代码通过横向切割的方式抽取到一个独立的模块中。即aop的作用就是去掉代码的冗余,使程序的结构更加清晰。虽然去除冗余代码也可一通过抽象继承来实现,但这会让你继承或实现一些和业务并不相关的类或接口。spring aop的用法spring的aop是通过动态代理实现的。代理模式代理模式的定义如下:为其他对象提供一种代理以控制对这个对象的访问。比如A对象要做一件事情,在没有代理前,自己来做,在对A代理后,由A的代理类B来做。代理其实是在原实例前后加了一层处理,这也是AOP的初级轮廓。代理又分为静态代理和动态代理,这里不再细说,想要更深入的了解可以看看这篇文章而要如何在spring中使用aop呢?不要着急,接着往下看:首先便是要知道切面应该用在那了,对此你可以使用很多方法:execution:用于匹配方法执行的连接点;within:用于匹配指定类型内的方法执行;this:用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;target:用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;args:用于匹配当前执行的方法传入的参数为指定类型的执行方法;@within:用于匹配所以持有指定注解类型内的方法;@target:用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;@args:用于匹配当前执行的方法传入的参数持有指定注解的执行;@annotation:用于匹配当前执行方法持有指定注解的方法;就像这样:当然关于各个参数的具体用法,可以另写一篇文章了,如果想要了解可以参看这篇文章。在上面的方法中就是切了有HostOwnerCheck这个注解的方法然后然后就是编写你对这个方法有些什么操作,可以分别编写方法执行前,执行后……要做什么比如下面就是方法执行前判断是否有权限,如果没有就抛出一个方法:这样,一个简单的切面就完成了。参考文章Spring AOP就是这么简单啦spring AOP是什么?你都拿它做什么?

March 1, 2019 · 1 min · jiezi

Spring AOP(三) Advisor类架构

Spring AOP是Spring的两大基石之一,不了解其基础概念的同学可以查看这两篇文章AOP基本概念和修饰者模式和JDK Proxy。 如果从代码执行角度来看,Spring AOP的执行过程分为四大步骤:步骤一:Spring框架生成Advisor实例,可以是@Aspect,@Async等注解生成的实例,也可以是程序员自定义的AbstractAdvisor子类的实例。步骤二:Spring框架在目标实例初始化完成后,也就是使用BeanPostProcessor的postProcessAfterInitialization方法,根据Advisor实例中切入点Pointcut的定义,选择出适合该目标对象的Advisor实例。步骤三:Spring框架根据Advisor实例生成代理对象。步骤四:调用方法执行过程时,Spring框架执行Advisor实例的通知Advice逻辑。 由于这四个步骤涉及的源码量较大,一篇文章无法直接完全讲解完,本篇文章只讲解第一步Advisor实例生成的源码分析。接下来的文章我们就依次讲解一下后续步骤中比较关键的逻辑。Advisor类架构 Spring中有大量的机制都是通过AOP实现的,比如说@Async的异步调用和@Transational。此外,用户也可以使用@Aspect注解定义切面或者直接继承AbstractPointcutAdvisor来提供切面逻辑。上述这些情况下,AOP都会生成对应的Advisor实例。 我们先来看一下Advisor的相关类图。首先看一下org.aopalliance包下的类图。aopalliance是AOP组织下的公用包,用于AOP中方法增强和调用,相当于一个jsr标准,只有接口和异常,在AspectJ、Spring等AOP框架中使用。 aopalliance定义了AOP的通知Advice和连接点Joinpoint接口,并且还有继承上述接口的MethodInterceptor和MethodInvocation。这两个类相信大家都很熟悉。 然后我们来看一下Spring AOP中Advisor相关的类图。Advisor是Spring AOP独有的概念,比较重要的类有AbstractPointcutAdvisor和InstantiationModelAwarePointcutAdvisor。相关的讲解都在图中表明了,如果这张图中的概念和类同学们都熟识,那么对AOP的了解就已经很深入了。获取所有Advisor实例 AOP生成Advisor实例的函数入口是AbstractAdvisorAutoProxyCreator的findCandidateAdvisors函数。// AbstractAdvisorAutoProxyCreator.java 找出当前所有的Advisorprotected List<Advisor> findCandidateAdvisors() { Assert.state(this.advisorRetrievalHelper != null, “No BeanFactoryAdvisorRetrievalHelper available”); return this.advisorRetrievalHelper.findAdvisorBeans();}// AnnotationAwareAspectJAutoProxyCreator,是AbstractAdvisorAutoProxyCreator的子类@Overrideprotected List<Advisor> findCandidateAdvisors() { // 调用父类的findCandidateAdvisor函数,一般找出普通的直接 // 继承Advisor接口的实例,比如说@Async所需的AsyncAnnotationAdvisor List<Advisor> advisors = super.findCandidateAdvisors(); // 为AspectJ的切面构造Advisor,也就是说处理@Aspect修饰的类,生成上文中说的InstantiationModelAwarePointcutAdvisor实例 if (this.aspectJAdvisorsBuilder != null) { advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); } return advisors;} 相关的ProxyCreator也有一个类体系,不过太过繁杂,而且重要性不大,我们就先略过,直接将具体的类。由上边代码可知AbstractAdvisorAutoProxyCreator 的findCandidateAdvisors 函数是直接获取Spring容器中的Advisor实例,比如说AsyncAnnotationAdvisor实例,或者说我们自定义的AbstractPointcutAdvisor的子类实例。AdvisorRetrievalHelper 的findAdvisorBeans 函数通过BeanFactory的getBean获取了所有类型为Advisor的实例。 而AnnotationAwareAspectJAutoProxyCreator 看其类名就可知,是与AspectJ相关的创建器,用来获取@Aspect定义的Advisor实例,也就是InstantiationModelAwarePointcutAdvisor实例。 接下去我们看一下BeanFactoryAspectJAdvisorsBuilder的buildAspectJAdvisors函数,它根据@Aspect修饰的切面实例生成对应的Advisor实例。public List<Advisor> buildAspectJAdvisors() { List<String> aspectNames = this.aspectBeanNames; // 第一次初始化,synchronized加双次判断,和经典单例模式的写法一样。 if (aspectNames == null) { synchronized (this) { aspectNames = this.aspectBeanNames; if (aspectNames == null) { // Spring源码并没有buildAspectJAdvisorsFirstly函数,为了方便理解添加。 // 获取aspectNames,创建Advisor实例,并且存入aspectFactoryCache缓存 return buildAspectJAdvisorsFirstly(); } } } if (aspectNames.isEmpty()) { return Collections.emptyList(); } List<Advisor> advisors = new ArrayList<>(); // 遍历aspectNames,依次获取对应的Advisor实例,或者是MetadataAwareAspectInstanceFactory生成的Advisor实例 for (String aspectName : aspectNames) { List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName); // cache可以取到实例,该Advisor是单例的 if (cachedAdvisors != null) { advisors.addAll(cachedAdvisors); } else { // 取得Advisor对应的工厂类实例,再次生成Advisor实例,该Advisor是多实例的。 MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } return advisors;} buildAspectJAdvisors函数执行时分为两种情况,第一个未初始化时,也就是aspectNames为null时,执行buildAspectJAdvisorsFirstly进行第一次初始化,在这一过程中生成切面名称列表aspectBeanNames和要返回的Advisor 列表,并且将生成的Advisor实例放置到advisorsCache中。 第二种情况则是已经初始化后再次调用,遍历aspectNames,从advisorsCache 取出对应的Advisor实例,或者从advisorsCache取出Advisor对应的工厂类对象,再次生成Advisor实例。public List<Advisor> buildAspectJAdvisorsFirstly() { List<Advisor> advisors = new ArrayList<>(); List<String> aspectNames = new ArrayList<>(); // 调用BeanFactoryUtils获取所有bean的名称 String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this.beanFactory, Object.class, true, false); for (String beanName : beanNames) { if (!isEligibleBean(beanName)) { continue; } // 获取对应名称的bean实例 Class<?> beanType = this.beanFactory.getType(beanName); if (beanType == null) { continue; } /** * AbstractAspectJAdvisorFactory类的isAspect函数来判断是否为切面实例 * 判断条件为是否被@Aspect修饰或者是由AspectJ编程而来。 */ if (this.advisorFactory.isAspect(beanType)) { aspectNames.add(beanName); AspectMetadata amd = new AspectMetadata(beanType, beanName); // 切面的属性为单例模式 if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) { MetadataAwareAspectInstanceFactory factory = new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName); // 获取一个切面中所有定义的Advisor实例。一个切面可以定义多个Advisor。 List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory); // 单例模式,只需要将生成的Advisor添加到缓存 if (this.beanFactory.isSingleton(beanName)) { this.advisorsCache.put(beanName, classAdvisors); } // 多实例模式,需要保存工厂类,便于下一次再次生成Advisor实例。 else { this.aspectFactoryCache.put(beanName, factory); } advisors.addAll(classAdvisors); } else { MetadataAwareAspectInstanceFactory factory = new PrototypeAspectInstanceFactory(this.beanFactory, beanName); this.aspectFactoryCache.put(beanName, factory); advisors.addAll(this.advisorFactory.getAdvisors(factory)); } } } this.aspectBeanNames = aspectNames; return advisors;} buildAspectJAdvisorsFirstly函数的逻辑如下:首先使用BeanFactoryUtils获取了BeanFactory中所有的BeanName,然后进而使用BeanFactory获取所有的Bean实例。遍历Bean实例,通过ReflectiveAspectJAdvisorFactory的isAspect函数判断该实例是否为切面实例,也就是被@Aspect注解修饰的实例。如果是,则使用ReflectiveAspectJAdvisorFactory,根据切面实例的定义来生成对应的多个Advisor实例,并且将其加入到advisorsCache中。生成InstantiationModelAwarePointcutAdvisorImpl实例 ReflectiveAspectJAdvisorFactory 的getAdvisors 函数会获取@Aspect修饰的实例中所有没有被@Pointcut修饰的方法,然后调用getAdvisor函数,并且将这些方法作为参数。public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) { validate(aspectInstanceFactory.getAspectMetadata().getAspectClass()); // 获得该方法上的切入点条件表达式 AspectJExpressionPointcut expressionPointcut = getPointcut( candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass()); if (expressionPointcut == null) { return null; } // 生成Advisor实例 return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod, this, aspectInstanceFactory, declarationOrderInAspect, aspectName);}private AspectJExpressionPointcut getPointcut(Method candidateAdviceMethod, Class<?> candidateAspectClass) { // 获得该函数上@Pointcut, @Around, @Before, @After, @AfterReturning, @AfterThrowing注解的信息 AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); // 没有上述注解,则直接返回 if (aspectJAnnotation == null) { return null; } AspectJExpressionPointcut ajexp = new AspectJExpressionPointcut(candidateAspectClass, new String[0], new Class<?>[0]); // 获得注解信息中的切入点判断表达式 ajexp.setExpression(aspectJAnnotation.getPointcutExpression()); if (this.beanFactory != null) { ajexp.setBeanFactory(this.beanFactory); } return ajexp;} getAdvisor函数就是根据作为参数传入的切面实例的方法上的注解来生成Advisor实例,也就是InstantiationModelAwarePointcutAdvisorImpl对象。依据方法上的切入点表达式生成AspectJExpressionPointcut 。 我们都知道PointcutAdvisor实例中必然有一个Pointcut和Advice实例。修饰在方法上的注解包括:@Pointcut, @Around, @Before, @After, @AfterReturning和@AfterThrowing,所以InstantiationModelAwarePointcutAdvisorImpl会依据不同的不同的注解生成不同的Advice通知。public InstantiationModelAwarePointcutAdvisorImpl(AspectJExpressionPointcut declaredPointcut, Method aspectJAdviceMethod, AspectJAdvisorFactory aspectJAdvisorFactory, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) { // …. 省略成员变量的直接赋值 // 单例模式时 this.pointcut = this.declaredPointcut; this.lazy = false; // 按照注解解析 Advice this.instantiatedAdvice = instantiateAdvice(this.declaredPointcut);} InstantiationModelAwarePointcutAdvisorImpl的构造函数中会生成对应的Pointcut和Advice。instantiateAdvice函数调用了ReflectiveAspectJAdvisorFactory的getAdvice函数。// ReflectiveAspectJAdvisorFactorypublic Advice getAdvice(Method candidateAdviceMethod, AspectJExpressionPointcut expressionPointcut, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrder, String aspectName) { Class<?> candidateAspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass(); validate(candidateAspectClass); // 获取 Advice 注解 AspectJAnnotation<?> aspectJAnnotation = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(candidateAdviceMethod); if (aspectJAnnotation == null) { return null; } // 检查是否为AspectJ注解 if (!isAspect(candidateAspectClass)) { throw new AopConfigException(“Advice must be declared inside an aspect type: " + “Offending method ‘” + candidateAdviceMethod + “’ in class [” + candidateAspectClass.getName() + “]”); } AbstractAspectJAdvice springAdvice; // 按照注解类型生成相应的 Advice 实现类 switch (aspectJAnnotation.getAnnotationType()) { case AtPointcut: if (logger.isDebugEnabled()) { logger.debug(“Processing pointcut ‘” + candidateAdviceMethod.getName() + “’”); } return null; case AtAround: // @Before 生成 AspectJMethodBeforeAdvice springAdvice = new AspectJAroundAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; case AtBefore: // @After 生成 AspectJAfterAdvice springAdvice = new AspectJMethodBeforeAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; case AtAfter: // @AfterReturning 生成 AspectJAfterAdvice springAdvice = new AspectJAfterAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); break; case AtAfterReturning: // @AfterThrowing 生成 AspectJAfterThrowingAdvice springAdvice = new AspectJAfterReturningAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); AfterReturning afterReturningAnnotation = (AfterReturning) aspectJAnnotation.getAnnotation(); if (StringUtils.hasText(afterReturningAnnotation.returning())) { springAdvice.setReturningName(afterReturningAnnotation.returning()); } break; case AtAfterThrowing: // @Around 生成 AspectJAroundAdvice springAdvice = new AspectJAfterThrowingAdvice( candidateAdviceMethod, expressionPointcut, aspectInstanceFactory); AfterThrowing afterThrowingAnnotation = (AfterThrowing) aspectJAnnotation.getAnnotation(); if (StringUtils.hasText(afterThrowingAnnotation.throwing())) { springAdvice.setThrowingName(afterThrowingAnnotation.throwing()); } break; default: throw new UnsupportedOperationException( “Unsupported advice type on method: " + candidateAdviceMethod); } // 配置Advice springAdvice.setAspectName(aspectName); springAdvice.setDeclarationOrder(declarationOrder); // 获取方法的参数列表方法 String[] argNames = this.parameterNameDiscoverer.getParameterNames(candidateAdviceMethod); if (argNames != null) { // 设置参数名称 springAdvice.setArgumentNamesFromStringArray(argNames); } springAdvice.calculateArgumentBindings(); return springAdvice;} 至此,Spring AOP就获取了容器中所有的Advisor实例,下一步在每个实例初始化完成后,根据这些Advisor的Pointcut切入点进行筛选,获取合适的Advisor实例,并生成代理实例。后记 Spring AOP后续文章很快就会更新,请大家继续关注。 ...

February 25, 2019 · 3 min · jiezi

spring aop 之链式调用

关关雎鸠,在河之洲。窈窕淑女,君子好逑。概述AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理、日志、缓存等等。 Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持。本章我们不关注aop代理类的实现,我简单实现一个指定次序的链式调用。实现链式调用的MethodInterceptor定义拦截器链,MethodInvocation 递归进入下一个拦截器链中。类图如下:MethodInterceptorpublic interface MethodInterceptor { Object invoke(MethodInvocation invocation) throws Throwable;}MethodInvocationpublic interface MethodInvocation { Object proceed() throws Throwable;}AbstractAspectJAdvice抽象类,实现MethodInterceptorpublic abstract class AbstractAspectJAdvice implements MethodInterceptor{ private Method adviceMethod; private Object adviceObject; public AbstractAspectJAdvice(Method adviceMethod, Object adviceObject) { this.adviceMethod = adviceMethod; this.adviceObject = adviceObject; } public Method getAdviceMethod() { return this.adviceMethod; } public void invokeAdviceMethod() throws Throwable { adviceMethod.invoke(adviceObject); }}AspectJBeforeAdvice前置通知public class AspectJBeforeAdvice extends AbstractAspectJAdvice { public AspectJBeforeAdvice(Method method, Object adviceObject) { super(method, adviceObject); } @Override public Object invoke(MethodInvocation invocation) throws Throwable{ this.invokeAdviceMethod(); Object o = invocation.proceed(); return o; }}AspectJAfterReturningAdvice后置通知public class AspectJAfterReturningAdvice extends AbstractAspectJAdvice { public AspectJAfterReturningAdvice(Method method, Object adviceObject) { super(method, adviceObject); } @Override public Object invoke(MethodInvocation invocation) throws Throwable{ Object o = invocation.proceed(); this.invokeAdviceMethod(); return o; }}ReflectiveMethodInvocation实现MethodInvocation,proceed()方法递归实现链式调用。public class ReflectiveMethodInvocation implements MethodInvocation { private final Object targetObject; private final Method targetMethod; private final List<MethodInterceptor> interceptorList; private int currentInterceptorIndex = -1; public ReflectiveMethodInvocation(Object targetObject, Method targetMethod, List<MethodInterceptor> interceptorList) { this.targetObject = targetObject; this.targetMethod = targetMethod; this.interceptorList = interceptorList; } @Override public Object proceed() throws Throwable { if (this.currentInterceptorIndex == this.interceptorList.size() - 1) { return invokeJoinPoint(); } this.currentInterceptorIndex++; MethodInterceptor interceptor = this.interceptorList.get(this.currentInterceptorIndex); return interceptor.invoke(this); } private Object invokeJoinPoint() throws Throwable { return this.targetMethod.invoke(this.targetObject); }}NioCoderService模拟service类public class NioCoderService { public void testAop() { System.out.println(“http://niocoder.com/"); }}TransactionManager模拟通知类public class TransactionManager { public void start() { System.out.println(“start tx”); } public void commit() { System.out.println(“commit tx”); } public void rollback() { System.out.println(“rollback tx”); }}ReflectiveMethodInvocationTestbeforeAdvice->afterReturningAdvice测试类,测试通知public class ReflectiveMethodInvocationTest { private AspectJBeforeAdvice beforeAdvice = null; private AspectJAfterReturningAdvice afterReturningAdvice = null; private NioCoderService nioCoderService; private TransactionManager tx; public void setUp() throws Exception { nioCoderService = new NioCoderService(); tx = new TransactionManager(); beforeAdvice = new AspectJBeforeAdvice(TransactionManager.class.getMethod(“start”), tx); afterReturningAdvice = new AspectJAfterReturningAdvice(TransactionManager.class.getMethod(“commit”), tx); } public void testMethodInvocation() throws Throwable { Method method = NioCoderService.class.getMethod(“testAop”); List<MethodInterceptor> interceptorList = new ArrayList<>(); interceptorList.add(beforeAdvice); interceptorList.add(afterReturningAdvice); ReflectiveMethodInvocation mi = new ReflectiveMethodInvocation(nioCoderService, method, interceptorList); mi.proceed(); } public static void main(String[] args) throws Throwable { ReflectiveMethodInvocationTest reflectiveMethodInvocationTest = new ReflectiveMethodInvocationTest(); reflectiveMethodInvocationTest.setUp(); reflectiveMethodInvocationTest.testMethodInvocation(); }}输出:start txhttp://niocoder.com/commit tx时序图 beforeAdvice->afterReturningAdviceafterReturningAdvice->beforeAdvice修改interceptorList的顺序 public void testMethodInvocation() throws Throwable { Method method = NioCoderService.class.getMethod(“testAop”); List<MethodInterceptor> interceptorList = new ArrayList<>(); interceptorList.add(afterReturningAdvice); interceptorList.add(beforeAdvice); ReflectiveMethodInvocation mi = new ReflectiveMethodInvocation(nioCoderService, method, interceptorList); mi.proceed(); }输出:start txhttp://niocoder.com/commit tx时序图 afterReturningAdvice->beforeAdvice代码下载github:https://github.com/longfeizheng/data-structure-java/blob/master/src/main/java/cn/merryyou/aop代码下载github:https://github.com/longfeizheng/data-structure-java ...

February 19, 2019 · 2 min · jiezi

Spring AOP(一) AOP基本概念

Spring框架自诞生之日就拯救我等程序员于水火之中,它有两大法宝,一个是IoC控制反转,另一个便是AOP面向切面编程。今日我们就来破一下它的AOP法宝,以便以后也能自由使出一手AOP大法。 AOP全名Aspect-oriented programming面向切面编程大法,它有很多兄弟,分别是经常见的面向对象编程,朴素的面向过程编程和神秘的函数式编程等。所谓AOP的具体解释,以及和OOP的区别不清楚的同学可以自行去了解。 AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理。本文就主要讲解AOP的基本术语,然后用一个例子让大家彻底搞懂这些名词,最后介绍一下AOP的两种代理方式:以AspectJ为代表的静态代理。以Spring AOP为代表的动态代理。基本术语(1)切面(Aspect) 切面是一个横切关注点的模块化,一个切面能够包含同一个类型的不同增强方法,比如说事务处理和日志处理可以理解为两个切面。切面由切入点和通知组成,它既包含了横切逻辑的定义,也包括了切入点的定义。 Spring AOP就是负责实施切面的框架,它将切面所定义的横切逻辑织入到切面所指定的连接点中。@Component@Aspectpublic class LogAspect {} 可以简单地认为, 使用 @Aspect 注解的类就是切面(2) 目标对象(Target) 目标对象指将要被增强的对象,即包含主业务逻辑的类对象。或者说是被一个或者多个切面所通知的对象。(3) 连接点(JoinPoint) 程序执行过程中明确的点,如方法的调用或特定的异常被抛出。连接点由两个信息确定:方法(表示程序执行点,即在哪个目标方法)相对点(表示方位,即目标方法的什么位置,比如调用前,后等) 简单来说,连接点就是被拦截到的程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法。@Before(“pointcut()")public void log(JoinPoint joinPoint) { //这个JoinPoint参数就是连接点}(4) 切入点(PointCut) 切入点是对连接点进行拦截的条件定义。切入点表达式如何和连接点匹配是AOP的核心,Spring缺省使用AspectJ切入点语法。 一般认为,所有的方法都可以认为是连接点,但是我们并不希望在所有的方法上都添加通知,而切入点的作用就是提供一组规则(使用 AspectJ pointcut expression language 来描述) 来匹配连接点,给满足规则的连接点添加通知。@Pointcut(“execution(* com.remcarpediem.test.aop.service..(..))")public void pointcut() {} 上边切入点的匹配规则是com.remcarpediem.test.aop.service包下的所有类的所有函数。(5) 通知(Advice) 通知是指拦截到连接点之后要执行的代码,包括了“around”、“before”和“after”等不同类型的通知。Spring AOP框架以拦截器来实现通知模型,并维护一个以连接点为中心的拦截器链。 // @Before说明这是一个前置通知,log函数中是要前置执行的代码,JoinPoint是连接点,@Before(“pointcut()")public void log(JoinPoint joinPoint) { }(6) 织入(Weaving) 织入是将切面和业务逻辑对象连接起来, 并创建通知代理的过程。织入可以在编译时,类加载时和运行时完成。在编译时进行织入就是静态代理,而在运行时进行织入则是动态代理。(7) 增强器(Adviser) Advisor是切面的另外一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂切面的装配器。Advisor由切入点和Advice组成。 Advisor这个概念来自于Spring对AOP的支撑,在AspectJ中是没有等价的概念的。Advisor就像是一个小的自包含的切面,这个切面只有一个通知。切面自身通过一个Bean表示,并且必须实现一个默认接口。// AbstractPointcutAdvisor是默认接口public class LogAdvisor extends AbstractPointcutAdvisor { private Advice advice; // Advice private Pointcut pointcut; // 切入点 @PostConstruct public void init() { // AnnotationMatchingPointcut是依据修饰类和方法的注解进行拦截的切入点。 this.pointcut = new AnnotationMatchingPointcut((Class) null, Log.class); // 通知 this.advice = new LogMethodInterceptor(); }}深入理解 看完了上面的理论部分知识, 我相信还是会有不少朋友感觉AOP 的概念还是很模糊, 对 AOP 的术语理解的还不是很透彻。现在我们就找一个具体的案例来说明一下。 简单来讲,整个 aspect 可以描述为: 满足 pointcut 规则的 joinpoint 会被添加相应的 advice 操作。我们来看下边这个例子。@Component@Aspect // 切面public class LogAspect { private final static Logger LOGGER = LoggerFactory.getLogger(LogAspect.class.getName()); // 切入点,表达式是指com.remcarpediem.test.aop.service // 包下的所有类的所有方法 @Pointcut(“execution( com.remcarpediem.test.aop.service..*(..))”) public void aspect() {} // 通知,在符合aspect切入点的方法前插入如下代码,并且将连接点作为参数传递 @Before(“aspect()”) public void log(JoinPoint joinPoint) { //连接点作为参数传入 if (LOGGER.isInfoEnabled()) { // 获得类名,方法名,参数和参数名称。 Signature signature = joinPoint.getSignature(); String className = joinPoint.getTarget().getClass().getName(); String methodName = joinPoint.getSignature().getName(); Object[] arguments = joinPoint.getArgs(); MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); String[] argumentNames = methodSignature.getParameterNames(); StringBuilder sb = new StringBuilder(className + “.” + methodName + “(”); for (int i = 0; i< arguments.length; i++) { Object argument = arguments[i]; sb.append(argumentNames[i] + “->”); sb.append(argument != null ? argument.toString() : “null “); } sb.append(”)”); LOGGER.info(sb.toString()); } }} 上边这段代码是一个简单的日志相关的切面,依次定义了切入点和通知,而连接点作为log的参数传入进来,进行一定的操作,比如说获取连接点函数的名称,参数等。静态代理模式 所谓静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。ApsectJ是静态代理的实现之一,也是最为流行的。静态代理由于在编译时就生成了代理类,效率相比动态代理要高一些。AspectJ可以单独使用,也可以和Spring结合使用。动态代理模式 与静态代理不同,动态代理就是说AOP框架不会去修改编译时生成的字节码,而是在运行时在内存中生成一个AOP代理对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。 Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。 JDK代理通过反射来处理被代理的类,并且要求被代理类必须实现一个接口。核心类是 InvocationHandler接口 和 Proxy类。 而当目标类没有实现接口时,Spring AOP框架会使用CGLIB来动态代理目标类。 CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。核心类是 MethodInterceptor 接口和Enhancer 类后记 AOP的基础知识都比较枯燥,本人也不擅长概念性的文章,不过下一篇文章就是AOP源码分析了,希望大家可以继续关注。 ...

February 11, 2019 · 2 min · jiezi

手把手教你如何优雅的使用Aop记录带参数的复杂Web接口日志

前言不久前,因为需求的原因,需要实现一个操作日志。几乎每一个接口被调用后,都要记录一条跟这个参数挂钩的特定的日志到数据库。举个例子,就比如禁言操作,日志中需要记录因为什么禁言,被禁言的人的id和各种信息。方便后期查询。这样的接口有很多个,而且大部分接口的参数都不一样。可能大家很容易想到的一个思路就是,实现一个日志记录的工具类,然后在需要记录日志的接口中,添加一行代码。由这个日志工具类去判断此时应该处理哪些参数。但是这样有很大的问题。如果需要记日志的接口数量非常多,先不讨论这个工具类中需要做多少的类型判断,仅仅是给所有接口添加这样一行代码在我个人看来都是不能接受的行为。首先,这样对代码的侵入性太大。其次,后期万一有改动,维护的人将会十分难受。想象一下,全局搜索相同的代码,再一一进行修改。所以我放弃了这个略显原始的方法。我最终采用了Aop的方式,采取拦截的请求的方式,来记录日志。但是即使采用这个方法,仍然面临一个问题,那就是如何处理大量的参数。以及如何对应到每一个接口上。我最终没有拦截所有的controller,而是自定义了一个日志注解。所有打上了这个注解的方法,将会记录日志。同时,注解中会带有类型,来为当前的接口指定特定的日志内容以及参数。<!–more–>那么如何从众多可能的参数中,为当前的日志指定对应的参数呢。我的解决方案是维护一个参数类,里面列举了所有需要记录在日志中的参数名。然后在拦截请求时,通过反射,获取到该请求的request和response中的所有参数和值,如果该参数存在于我维护的param类中,则将对应的值赋值进去。然后在请求结束后,将模板中的所有预留的参数全部用赋了值的参数替换掉。这样一来,在不大量的侵入业务的前提下,满足了需求,同时也保证了代码的可维护性。下面我将会把详细的实现过程列举出来。开始操作前文章结尾我会给出这个demo项目的所有源码。所以不想看过程的兄台可移步到末尾,直接看源码。(听说和源码搭配,看文章更美味…)开始操作新建项目大家可以参考我之前写的另一篇文章,手把手教你从零开始搭建SpringBoot后端项目框架。只要能请求简单的接口就可以了。本项目的依赖如下。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>2.1.1.RELEASE</version></dependency><!– https://mvnrepository.com/artifact/org.aspectj/aspectjrt –><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.2</version></dependency><!– https://mvnrepository.com/artifact/org.aspectj/aspectjweaver –><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.2</version></dependency><dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.2</version></dependency><dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.1.14</version></dependency>新建Aop类新建LogAspect类。代码如下。package spring.aop.log.demo.api.util;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.springframework.stereotype.Component;/** * LogAspect * * @author Lunhao Hu * @date 2019-01-30 16:21 /@Aspect@Componentpublic class LogAspect { / * 定义切入点 / @Pointcut("@annotation(spring.aop.log.demo.api.util.Log)") public void operationLog() { } /* * 新增结果返回后触发 * * @param point * @param returnValue / @AfterReturning(returning = “returnValue”, pointcut = “operationLog() && @annotation(log)”) public void doAfterReturning(JoinPoint point, Object returnValue, Log log) { System.out.println(“test”); }}Pointcut中传入了一个注解,表示凡是打上了这个注解的方法,都会触发由Pointcut修饰的operationLog函数。而AfterReturning则是在请求返回之后触发。自定义注解上一步提到了自定义注解,这个自定义注解将打在controller的每个方法上。新建一个annotation的类。代码如下。package spring.aop.log.demo.api.util;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/* * Log * * @author Lunhao Hu * @date 2019-01-30 16:19 /@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Log { String type() default “”;}Target和Retention都属于元注解。共有4种,分别是@Retention、@Target、@Document、@Inherited。Target注解说明了该Annotation所修饰的范围。可以传入很多类型,参数为ElementType。例如TYPE,用于描述类、接口或者枚举类;FIELD用于描述属性;METHOD用于描述方法;PARAMETER用于描述参数;CONSTRUCTOR用于描述构造函数;LOCAL_VARIABLE用于描述局部变量;ANNOTATION_TYPE用于描述注解;PACKAGE用于描述包等。Retention注解定义了该Annotation被保留的时间长短。参数为RetentionPolicy。例如SOURCE表示只在源码中存在,不会在编译后的class文件存在;CLASS是该注解的默认选项。 即存在于源码,也存在于编译后的class文件,但不会被加载到虚拟机中去;RUNTIME存在于源码、class文件以及虚拟机中,通俗一点讲就是可以在运行的时候通过反射获取到。加上普通注解给需要记录日志的接口加上Log注解。package spring.aop.log.demo.api.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import spring.aop.log.demo.api.util.Log;/ * HelloController * * @author Lunhao Hu * @date 2019-01-30 15:52 /@RestControllerpublic class HelloController { @Log @GetMapping(“test/{id}”) public String test(@PathVariable(name = “id”) Integer id) { return “Hello” + id; }}加上之后,每一次调用test/{id}这个接口,都会触发拦截器中的doAfterReturning方法中的代码。加上带类型注解上面介绍了记录普通日志的方法,接下来要介绍记录特定日志的方法。什么特定日志呢,就是每个接口要记录的信息不同。为了实现这个,我们需要实现一个操作类型的枚举类。代码如下。操作类型模板枚举新建一个枚举类Type。代码如下。package spring.aop.log.demo.api.util;/ * Type * * @author Lunhao Hu * @date 2019-01-30 17:12 /public enum Type { / * 操作类型 / WARNING(“警告”, “因被其他玩家举报,警告玩家”); /* * 类型 / private String type; /* * 执行操作 / private String operation; Type(String type, String operation) { this.type = type; this.operation = operation; } public String getType() { return type; } public String getOperation() { return operation; }}给注解加上类型给上面的controller中的注解加上type。代码如下。package spring.aop.log.demo.api.controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;import org.springframework.web.bind.annotation.RestController;import spring.aop.log.demo.api.util.Log;/* * HelloController * * @author Lunhao Hu * @date 2019-01-30 15:52 /@RestControllerpublic class HelloController { @Log(type = “WARNING”) @GetMapping(“test/{id}”) public String test(@PathVariable(name = “id”) Integer id) { return “Hello” + id; }}修改aop类将aop类中的doAfterReturning为如下。@AfterReturning(returning = “returnValue”, pointcut = “operationLog() && @annotation(log)")public void doAfterReturning(JoinPoint point, Object returnValue, Log log) { // 注解中的类型 String enumKey = log.type(); System.out.println(Type.valueOf(enumKey).getOperation());}加上之后,每一次调用加了@Log(type = “WARNING”)这个注解的接口,都会打印这个接口所指定的日志。例如上述代码就会打印出如下代码。因被其他玩家举报,警告玩家获取aop拦截的请求参数为每个接口指定一个日志并不困难,只需要为每个接口指定一个类型即可。但是大家应该也注意到了,一个接口日志,只记录因被其他玩家举报,警告玩家这样的信息没有任何意义。记录日志的人倒不觉得,而最后去查看日志的人就要吾日三省吾身了,被谁举报了?因为什么举报了?我警告的谁?这样的日志做了太多的无用功,根本没有办法在出现问题之后溯源。所以我们下一步的操作就是给每个接口加上特定的参数。那么大家可能会有问题,如果每个接口的参数几乎都不一样,那这个工具类岂不是要传入很多参数,要怎么实现呢,甚至还要组织参数,这样会大量的侵入业务代码,并且会大量的增加冗余代码。大家可能会想到,实现一个记录日志的方法,在要记日志的接口中调用,把参数传进去。如果类型很多的话,参数也会随之增多,每个接口的参数都不一样。处理起来十分麻烦,而且对业务的侵入性太高。几乎每个地方都要嵌入日志相关代码。一旦涉及到修改,将会变得十分难维护。所以我直接利用反射获取aop拦截到的请求中的所有参数,如果我的参数类(所有要记录的参数)里面有请求中的参数,那么我就将参数的值写入参数类中。最后将日志模版中参数预留字段替换成请求中的参数。流程图如下所示。新建参数类新建一个类Param,其中包含所有在操作日志中,可能会出现的参数。为什么要这么做?因为每个接口需要的参数都有可能完全不一样,与其去维护大量的判断逻辑,还不如贪心一点,直接传入所有的可能参数。当然后期如果有新的参数需要记录,则需要修改代码。package spring.aop.log.demo.api.util;import lombok.Data;/ * Param * * @author Lunhao Hu * @date 2019-01-30 17:14 /@Datapublic class Param { / * 所有可能参数 / private String id; private String workOrderNumber; private String userId;}修改模板将模板枚举类中的WARNING修改为如下。WARNING(“警告”, “因 工单号 [(%workOrderNumber)] /举报 ID [(%id)] 警告玩家 [(%userId)]”);其中的参数,就是要在aop拦截阶段获取并且替换掉的参数。修改controller我们给之前的controller加上上述模板中国呢的参数。部分代码如下。@Log(type = “WARNING”)@GetMapping(“test/{id}")public String test( @PathVariable(name = “id”) Integer id, @RequestParam(name = “workOrderNumber”) String workOrderNumber, @RequestParam(name = “userId”) String userId, @RequestParam(name = “name”) String name) { return “Hello” + id;}通过反射获取请求的参数在此处分两种情况,一种是简单参数类型,另外一种是复杂参数类型,也就是参数中带了请求DTO的情况。获取简单参数类型给aop类添加几个私有变量。/* * 请求中的所有参数 /private Object[] args;/* * 请求中的所有参数名 /private String[] paramNames;/* * 参数类 /private Param params;然后将doAfterReturning中的代码改成如下。try { // 获取请求详情 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); HttpServletResponse response = attributes.getResponse(); // 获取所有请求参数 Signature signature = point.getSignature(); MethodSignature methodSignature = (MethodSignature) signature; this.paramNames = methodSignature.getParameterNames(); this.args = point.getArgs(); // 实例化参数类 this.params = new Param(); // 注解中的类型 String enumKey = log.type(); String logDetail = Type.valueOf(enumKey).getOperation(); // 从请求传入参数中获取数据 this.getRequestParam();} catch (Exception e) { System.out.println(e.getMessage());}首先要做的就是拦截打上了自定义注解的请求。我们可以获取到请求的详情,以及请求中的所有的参数名,以及参数。下面我们就来实现上述代码中的getRequestParam方法。getRequestParam/* * 获取拦截的请求中的参数 * @param point /private void getRequestParam() { // 获取简单参数类型 this.getSimpleParam();}getSimpleParam/* * 获取简单参数类型的值 /private void getSimpleParam() { // 遍历请求中的参数名 for (String reqParam : this.paramNames) { // 判断该参数在参数类中是否存在 if (this.isExist(reqParam)) { this.setRequestParamValueIntoParam(reqParam); } }}上述代码中,遍历请求所传入的参数名,然后我们实现isExist方法, 来判断这个参数在我们的Param类中是否存在,如果存在我们就再调用setRequestParamValueIntoParam方法,将这个参数名所对应的参数值写入到Param类的实例中。isExistisExist的代码如下。/* * 判断该参数在参数类中是否存在(是否是需要记录的参数) * @param targetClass * @param name * @param <T> * @return /private <T> Boolean isExist(String name) { boolean exist = true; try { String key = this.setFirstLetterUpperCase(name); Method targetClassGetMethod = this.params.getClass().getMethod(“get” + key); } catch (NoSuchMethodException e) { exist = false; } return exist;}在上面我们也提到过,在编译的时候会加上getter和setter,所以参数名的首字母都会变成大写,所以我们需要自己实现一个setFirstLetterUpperCase方法,来将我们传入的参数名的首字母变成大写。setFirstLetterUpperCase代码如下。/* * 将字符串的首字母大写 * * @param str * @return /private String setFirstLetterUpperCase(String str) { if (str == null) { return null; } return str.substring(0, 1).toUpperCase() + str.substring(1);}setRequestParamValueIntoParam代码如下。/* * 从参数中获取 * @param paramName * @return /private void setRequestParamValueIntoParam(String paramName) { int index = ArrayUtil.indexOf(this.paramNames, paramName); if (index != -1) { String value = String.valueOf(this.args[index]); this.setParam(this.params, paramName, value); }}ArrayUtil是hutool中的一个工具函数。用来判断在一个元素在数组中的下标。setParam代码如下。/* * 将数据写入参数类的实例中 * @param targetClass * @param key * @param value * @param <T> /private <T> void setParam(T targetClass, String key, String value) { try { Method targetClassParamSetMethod = targetClass.getClass().getMethod(“set” + this.setFirstLetterUpperCase(key), String.class); targetClassParamSetMethod.invoke(targetClass, value); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }}该函数使用反射的方法,获取该参数的set方法,将Param类中对应的参数设置成传入的值。运行启动项目,并且请求controller中的方法。并且传入定义好的参数。http://localhost:8080/test/8?workOrderNumber=3231732&userId=748327843&name=testName该GET请求总共传入了4个参数,分别是id,workOrderNumber,userId, name。大家可以看到,在Param类中并没有定义name这个字段。这是特意加了一个不需要记录的参数,来验证我们接口的健壮性的。运行之后,可以看到控制台打印的信息如下。Param(id=8, workOrderNumber=3231732, userId=748327843)我们想让aop记录的参数全部记录到Param类中的实例中,而传入了意料之外的参数也没有让程序崩溃。接下里我们只需要将这些参数,将之前定义好的模板的参数预留字段替换掉即可。替换参数在doAfterReturning中的getRequestParam函数后,加入以下代码。if (!logDetail.isEmpty()) { // 将模板中的参数全部替换掉 logDetail = this.replaceParam(logDetail);}System.out.println(logDetail);下面我们实现replaceParam方法。replaceParam代码如下。/* * 将模板中的预留字段全部替换为拦截到的参数 * @param template * @return /private String replaceParam(String template) { // 将模板中的需要替换的参数转化成map Map<String, String> paramsMap = this.convertToMap(template); for (String key : paramsMap.keySet()) { template = template.replace(”%” + key, paramsMap.get(key)).replace("(", “”).replace(")", “”); } return template;}convertToMap方法将模板中的所有预留字段全部提取出来,当作一个Map的Key。convertToMap代码如下。/* * 将模板中的参数转换成map的key-value形式 * @param template * @return /private Map<String, String> convertToMap(String template) { Map<String, String> map = new HashMap<>(); String[] arr = template.split("\("); for (String s : arr) { if (s.contains("%")) { String key = s.substring(s.indexOf("%"), s.indexOf(")")).replace("%", “”).replace(")", “”).replace("-", “”).replace("]", “”); String value = this.getParam(this.params, key); map.put(key, “null”.equals(value) ? “(空)” : value); } } return map;}其中的getParam方法,类似于setParam,也是利用反射的方法,通过传入的Class和Key,获取对应的值。getParam代码如下。/* * 通过反射获取传入的类中对应key的值 * @param targetClass * @param key * @param <T> /private <T> String getParam(T targetClass, String key) { String value = “”; try { Method targetClassParamGetMethod = targetClass.getClass().getMethod(“get” + this.setFirstLetterUpperCase(key)); value = String.valueOf(targetClassParamGetMethod.invoke(targetClass)); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } return value;}再次运行再次请求上述的url,则可以看到控制台的输出如下。因 工单号 [3231732] /举报 ID [8] 警告玩家 [748327843]可以看到,我们需要记录的所有的参数,都被正确的替换了。而不需要记录的参数,同样也没有对程序造成影响。让我们试试传入不传入非必选参数,会是什么样。修改controller如下,把workOrderNumber改成非必须按参数。@Log(type = “WARNING”)@GetMapping(“test/{id}")public String test( @PathVariable(name = “id”) Integer id, @RequestParam(name = “workOrderNumber”, required = false) String workOrderNumber, @RequestParam(name = “userId”) String userId, @RequestParam(name = “name”) String name) { return “Hello” + id;}请求如下url。http://localhost:8080/test/8?userId=748327843&name=testName然后可以看到,控制台的输出如下。因 工单号 [空] /举报 ID [8] 警告玩家 [748327843]并不会影响程序的正常运行。获取复杂参数类型接下来要介绍的是如何记录复杂参数类型的日志。其实,大致的思路是不变的。我们看传入的类中的参数,有没有需要记录的。有的话就按照上面记录简单参数的方法来替换记录参数。定义测试复杂类型新建TestDTO。代码如下。package spring.aop.log.demo.api.util;import lombok.Data;/* * TestDto * * @author Lunhao Hu * @date 2019-02-01 15:02 /@Datapublic class TestDTO { private String name; private Integer age; private String email;}修改Param将上面的所有的参数全部添加到Param类中,全部定义成字符串类型。package spring.aop.log.demo.api.util;import lombok.Data;/ * Param * * @author Lunhao Hu * @date 2019-01-30 17:14 /@Datapublic class Param { / * 所有可能参数 / private String id; private String age; private String workOrderNumber; private String userId; private String name; private String email;}修改模板将WARNING模板修改如下。/* * 操作类型 /WARNING(“警告”, “因 工单号 [(%workOrderNumber)] /举报 ID [(%id)] 警告玩家 [(%userId)], 游戏名 [(%name)], 年龄 [(%age)]”);修改controller@Log(type = “WARNING”)@PostMapping(“test/{id}")public String test( @PathVariable(name = “id”) Integer id, @RequestParam(name = “workOrderNumber”, required = false) String workOrderNumber, @RequestParam(name = “userId”) String userId, @RequestBody TestDTO testDTO) { return “Hello” + id;}修改getRequestParam/* * 获取拦截的请求中的参数 * @param point /private void getRequestParam() { // 获取简单参数类型 this.getSimpleParam(); // 获取复杂参数类型 this.getComplexParam();}接下来实现getComplexParam方法。getComplexParam/* * 获取复杂参数类型的值 /private void getComplexParam() { for (Object arg : this.args) { // 跳过简单类型的值 if (arg != null && !this.isBasicType(arg)) { this.getFieldsParam(arg); } }}getFieldsParam/* * 遍历一个复杂类型,获取值并赋值给param * @param target * @param <T> /private <T> void getFieldsParam(T target) { Field[] fields = target.getClass().getDeclaredFields(); for (Field field : fields) { String paramName = field.getName(); if (this.isExist(paramName)) { String value = this.getParam(target, paramName); this.setParam(this.params, paramName, value); } }}运行启动项目。使用postman对上面的url发起POST请求。请求body中带上TestDTO中的参数。请求成功返回后就会看到控制台输出如下。因 工单号 [空] /举报 ID [8] 警告玩家 [748327843], 游戏名 [tom], 年龄 [12]然后就可以根据需求,将上面的日志记录到相应的地方。到这可能有些哥们就觉得行了,万事具备,只欠东风。但其实这样的实现方式,还存在几个问题。比如,如果请求失败了怎么办?请求失败,在需求上将,是根本不需要记录操作日志的,但是即使请求失败也会有返回值,就代表日志也会成功的记录。这就给后期查看日志带来了很大的困扰。再比如,如果我需要的参数在返回值中怎么办?如果你没有用统一的生成唯一id的服务,就会遇到这个问题。就比如我需要往数据库中插入一条新的数据,我需要得到数据库自增id,而我们的日志拦截只拦截了请求中的参数。所以这就是我们接下来要解决的问题。判断请求是否成功实现success函数,代码如下。/* * 根据http状态码判断请求是否成功 * * @param response * @return /private Boolean success(HttpServletResponse response) { return response.getStatus() == 200;}然后将getRequestParam之后的所有操作,包括getRequestParam本身,用success包裹起来。如下。if (this.success(response)) { // 从请求传入参数中获取数据 this.getRequestParam(); if (!logDetail.isEmpty()) { // 将模板中的参数全部替换掉 logDetail = this.replaceParam(logDetail); }}这样一来,就可以保证只有在请求成功的前提下,才会记录日志。通过反射获取返回的参数新建Result类在一个项目中,我们用一个类来统一返回值。package spring.aop.log.demo.api.util;import lombok.Data;/* * Result * * @author Lunhao Hu * @date 2019-02-01 16:47 /@Datapublic class Result { private Integer id; private String name; private Integer age; private String email;}修改controller@Log(type = “WARNING”)@PostMapping(“test”)public Result test( @RequestParam(name = “workOrderNumber”, required = false) String workOrderNumber, @RequestParam(name = “userId”) String userId, @RequestBody TestDTO testDTO) { Result result = new Result(); result.setId(1); result.setAge(testDTO.getAge()); result.setName(testDTO.getName()); result.setEmail(testDTO.getEmail()); return result;}运行启动项目,发起POST请求会发现,返回值如下。{ “id”: 1, “name”: “tom”, “age”: 12, “email”: “test@test.com”}而控制台的输出如下。因 工单号 [39424] /举报 ID [空] 警告玩家 [748327843], 游戏名 [tom], 年龄 [12]可以看到,id没有被获取到。所以我们还需要添加一个函数,从返回值中获取id的数据。getResponseParam在getRequestParam后,添加方法getResponseParam,直接调用之前写好的函数。代码如下。/ * 从返回值从获取数据 */private void getResponseParam(Object value) { this.getFieldsParam(value);}运行再次发起POST请求,可以发现控制台的输出如下。因 工单号 [39424] /举报 ID [1] 警告玩家 [748327843], 游戏名 [tom], 年龄 [12]一旦得到了这条信息,我们就可以把它记录到任何我们想记录的地方。项目源码地址想要参考源码的大佬请戳 ->这里<- ...

February 11, 2019 · 6 min · jiezi

Laravel Pipeline解读

大家好,今天给大家介绍下Laravel框架的Pipeline。它是一个非常好用的组件,能够使代码的结构非常清晰。 Laravel的中间件机制便是基于它来实现的。通过Pipeline,可以轻松实现APO编程。官方GIT地址https://github.com/illuminate…下面的代码是我实现的一个简化版本:class Pipeline{ /** * The method to call on each pipe * @var string / protected $method = ‘handle’; /* * The object being passed throw the pipeline * @var mixed / protected $passable; /* * The array of class pipes * @var array / protected $pipes = []; /* * Set the object being sent through the pipeline * * @param $passable * @return $this / public function send($passable) { $this->passable = $passable; return $this; } /* * Set the method to call on the pipes * @param array $pipes * @return $this / public function through($pipes) { $this->pipes = $pipes; return $this; } /* * @param \Closure $destination * @return mixed / public function then(\Closure $destination) { $pipeline = array_reduce(array_reverse($this->pipes), $this->getSlice(), $destination); return $pipeline($this->passable); } /* * Get a Closure that represents a slice of the application onion * @return \Closure */ protected function getSlice() { return function($stack, $pipe){ return function ($request) use ($stack, $pipe) { return $pipe::{$this->method}($request, $stack); }; }; }}此类主要逻辑就在于then和getSlice方法。通过array_reduce,生成一个接受一个参数的匿名函数,然后执行调用。简单使用示例class ALogic{ public static function handle($data, \Clourse $next) { print “开始 A 逻辑”; $ret = $next($data); print “结束 A 逻辑”; return $ret; }}class BLogic{ public static function handle($data, \Clourse $next) { print “开始 B 逻辑”; $ret = $next($data); print “结束 B 逻辑”; return $ret; }}class CLogic{ public static function handle($data, \Clourse $next) { print “开始 C 逻辑”; $ret = $next($data); print “结束 C 逻辑”; return $ret; }}$pipes = [ ALogic::class, BLogic::class, CLogic::class];$data = “any things”;(new Pipeline())->send($data)->through($pipes)->then(function($data){ print $data;});运行结果:“开始 A 逻辑"“开始 B 逻辑"“开始 C 逻辑"“any things"“结束 C 逻辑"“结束 B 逻辑"“结束 A 逻辑"AOP示例AOP 的优点就在于动态的添加功能,而不对其它层次产生影响,可以非常方便的添加或者删除功能。class IpCheck{ public static function handle($data, \Clourse $next) { if (“IP invalid”) { // IP 不合法 throw Exception(“ip invalid”); } return $next($data); }}class StatusManage{ public static function handle($data, \Clourse $next) { // exec 可以执行初始化状态的操作 $ret = $next($data) // exec 可以执行保存状态信息的操作 return $ret; }}$pipes = [ IpCheck::class, StatusManage::class,];(new Pipeline())->send($data)->through($pipes)->then(function($data){ “执行其它逻辑”;}); ...

January 7, 2019 · 2 min · jiezi

spring boot 拦截器(interceptor)与切面(aop)的使用场景

在使用spring-boot的过程中,我们在处理一些before、after操作时,往往有两种技术选择:interceptor 拦截器和aop 向对切面编程。那么:什么时候该使用interceptor 拦截器,什么时候又该使用aop 向对切面编程呢?比如:我们在进行用户是否登录验证时。可以使用interceptor 拦截器结合注解来实现,也可以使用aop 向对切面编程结合注解来实现。个人经验如下:如果注解仅应用到controller 控制器或是controller 控制器对应的function 方法上,那么应该使用interceptor 拦截器。如果注解的应用范围不仅仅是controller 控制器或是controller 控制器对应的function 方法上,比如注解应用到服务 service中,那么应该使用AOP 向对切面编程。

January 7, 2019 · 1 min · jiezi

编程--基本概念

1.面向过程(PROCEDURE ORIENTED)1).具体化,流程化2).性能高3).算法+数据结构2.面向对象(OBJECT ORIENTED)(OO)1).模型化2).易维护,易复用,易扩展3.面向对象编程(OOP)1).继承 允许在现存的组件基础上创建子类组件,这统一并增强了多态性和封装性 A).重载(以统一的方法处理不同数据类型) 一个类的多态性表现 B).重写(方法重写) 父子类多态性体现2).封装(信息封装) 确保组件不会以不可预期的方式改变其它组件的内部状态3).多态 组件的引用和类集会涉及到其它不同类型的组件,而且引用组件所产生的结果得依据实际调用的类型4.面向切面编程(ASPECT ORIENTED PAROGRAMMING)(AOP)1).切面 项目模块中某些业务逻辑(业务需要一定共性)2).解耦,提高程序可重用性,提高开发效率5.三层架构、MVC、MVP、MVVM1).三层架构–界面层(User Interface Layer-Business Logic Layer-Data access Layer 界面–业务逻辑–数据访问) A).界面层(UIL) 与用户交互 B).业务逻辑层(BLL) 实现业务逻辑。业务逻辑具体包含:验证、计算、业务规则等 C).数据访问层(DAL) 与数据库打交道。主要实现对数据的增、删、改、查 2).MVC(Model-View-Controller 模型–视图–控制器) A).Model(模型) 业务逻辑、业务模型、业务操作、数据模型。定义了数据修改和操作的业务规则 B).View (视图) UI组件。接收Controller数据,降Model转化成UI C).Controller(控制器) 处理流入请求 D).特点 View和Model分离(1978 Trygve Reenskaug) E).流程 View⇒Controller⇒Model⇔View 3).MVP(Model-View-Presenter MVC改良模式(View与Model完全解耦)) A).Model(模型) 业务逻辑、业务模型、业务操作、数据模型。定义了数据修改和操作的业务规则 B).View (视图) UI组件。接收Controller数据,降Model转化成UI C).Presenter(控制器) 处理View背后所有UI事件(一个Presenter只映射一个view) D).特点 View和Presenter双向交互(IBM的子公司Taligent提出) E).流程 View⇔Presenter⇔Model 4).MVVM(Model-View-View Model MVP中把P层削弱为VM层,部分简单的逻辑职责分给了View层) A).Model(模型) 业务逻辑、业务模型、业务操作、数据模型。定义了数据修改和操作的业务规则 B).View (视图) UI组件。接收Controller数据,降Model转化成UI C).View Model(控制器) 负责暴漏方法,命令,其他属性来操作View的状态,触发View自己的事件 D).特点 View和View Model双向数据绑定关系 E).流程 View⇒View Model⇔Model ...

December 26, 2018 · 1 min · jiezi