网站建设与维护一年多少钱,wordpress编辑和作者的权限区别,百度 特定网站搜索,成都有什么好玩的娱乐场所一:java代理
整体分为两种#xff1a;静态代理和动态代理 静态代理#xff1a;23种设计模式里面有个代理模式#xff0c;那个就是静态代理。 动态代理#xff1a;分为编译时增强(AspectJ)和运行时增强(JDK动态代理和CGLIB动态代理)
1:静态代理 这种代理在我们日常生活中其…一:java代理
整体分为两种静态代理和动态代理 静态代理23种设计模式里面有个代理模式那个就是静态代理。 动态代理分为编译时增强(AspectJ)和运行时增强(JDK动态代理和CGLIB动态代理)
1:静态代理 这种代理在我们日常生活中其实非常常见例如房屋中介就相当于是一个代理当房东需要出租房子的时候需要发布广告、寻找客户、清理房间。。。由于比较麻烦因此房东可以将租房子这件事情委托给中间代理去做。这就是一个静态代理。我通过一个简单的代码来演示一下首先我们有一个租房的接口如下:
public interface Rent {void rent();
}房东实现了该接口想要出租房屋
/*** 房东*/
public class Loadlord implements Rent{Overridepublic void rent() {System.out.println(房屋出租);}
}中介作为中间代理也实现了该接口同时代理了房东如下
public class HouseAgent implements Rent{private Loadlord loadlord;public HouseAgent(Loadlord loadlord) {this.loadlord loadlord;}Overridepublic void rent() {published();loadlord.rent();agentFee();}private void published(){System.out.println(发布广告);}private void agentFee(){System.out.println(收取中介费);}
}可以看到中介的 rent 方法中除了调用房东的rent方法之外还调用了 publishAd 和agencyFee两个方法。
接下来客户租房只需要和代理打交道就行。如下
/*** 静态代理demo*/
public class StaticDemo {public static void main(String[] args) {HouseAgent houseAgent new HouseAgent(new Loadlord());houseAgent.rent();//输出发布广告// 房屋出租// 收取中介费}
}这就是简单的代理模式。
2:动态代理 动态代理讲究在不改变原类原方法的情况下增强目标方法的功能例如大家平时使用的Spring 事务功能在不改变目标方法的情况下就可以通过动态代理为方法添加事务处理能力。日志处理、接口幂等性处理、多数据源处理等都是动态代理能力的体现。 例如我有一个转账的业务:
public class MoneyService{public void transferMoney(){System.out.println(执行转账操作);}
}如果通过动态代理来为上面的业务代码添加事务那么我们只需要做一些配置系统会自动生成动态代理的类和方法:
public class MoneyServiceProxy{public void transferMoney() {try {//开启事务//执行事务//提交事务}catch (Exception e){//回滚事务}}}上面这个是自动生成的代理类不需要开发者开发。
从实现原理上来看又分为两类编译时增强运行时增强。
2.1:编译时增强 编译时增强这种有点类似于 Lombok的感觉就是在编译阶段就直接生成了代理类将来运行的时候就直接运行这个编译生成的代理类AspectJ就是这样一种编译时增强的工具。 AspectJ 全称是 Eclipse AspectJ其官网地址是:http://www.eclipse.or g/aspectj 截止到本文写作时目前最新版本为:1.9.7。从官网我们可以看到AspectJ的定位:
基于 Java 语言的面向切面编程语言兼容 Java。易学易用。
使用 AspectJ 时需要使用专门的编译器ajc。
编译时增强配置 首先在idea里面运行AspectJ需要先安装AspectJ插件如下 安装好之后我们需要在idea中配置一下使用ajc编译器代替javac 有如下几个需要修改的点:
首先修改编译器为ajc。将使用的 Java 版本改为8这个一共有两个地方需要修改。设置 aspectjtools.jar 的位置这个jar 包需要自己提前准备好可以从Maven 官网下载然后在这里配置jar的路径配置完成之后点击test 按钮进行测试测试成功就会弹出来图中的弹框。
对于第 3 步所需要的jar也可以在项目的Maven中添加如下依赖自动下载,下载到本地仓库之后再删除掉 pom.xml 中的配置即可:
dependencygroupIdorg.aspectj/groupId artifactIdaspectjtools/artifactIdversion1.9.7.M3/version
/dependency这样开发环境就准备好了。
接下来假设我有一个银行转帐的方法:
public class MoneyService {public void transferMoney(){System.out.println(转账操作);}
}我想给这个操作添加事务那么我就添加一个Aspect如下
public aspect TxAspect {void around( ):call(void MoneyService.transferMoney()){System.out.println(开启事务);try{proceed();System.out.println(提交事务事务);} catch (Exception e){System,out.println(回滚事务);}}}这就是 AspectJ的语法跟Java有点像但是不太一样。需要注意的是这个 TxAspect 不是一个 Java类它的后缀是.aj。 proceed 表示继续执行目标方法前后逻辑比较简单我就不多说了。 最后我们去运行转帐服务
public class Demo01{public static void main(String[] args) {MoneyService moneyService new MoneyService();moneyService.transferMoney();}}运行结果如下 这就是一个静态代理
实际编译后底层代码如下(所以代码包括main方法也变了) 最后执行的都是修改之后的内容。所以说 AspectJ的作用就有点类似于Lombok直接在编译时期将我们的代码改了这就是编译时增强。
2.2:运行时增强 运行时增强则是指借助于JDK动态代理或者 CGLIB 动态代理等在内存中临时生成 AOP 动态代理类我们在Spring AOP 中常说的动态代理一般是指这种运行时增强。 我们平日开发写的Spring AOP,基本上都是属于这一类。
1:JDK动态代理 这个代理有一个要求就是被代理的对象必须要有一个接口没有接口不行。但是CGLIB没有这个要求。
假如我有一个计算机接口
public interface ICalculator {int add(int a,int b);
}这个接口有个实现类
public class CalculatorImpl implements ICalculator{Overridepublic int add(int a, int b) {System.out.println(ab最终输出:(ab));return ab;}
}现在我想实现统计该接口执行时间的功能JDK动态代理如下
public class Demo2 {/***** 下面生成的 calculator 是一个代理对象注意这个 calculator 对象并不是 CalculatorImpl 的对象** 相当于在代码运行的过程中自动生成了一个代理类** public CalculatorProxy implements ICalculator{* int add(int a,int b){* long startTime System.currentTimeMillis();* //这个地方调用了 CalculatorImpl 类的实例的 add 方法去执行了真正的 add 操作* long endTime System.currentTimeMillis();* System.out.println(method.getName() 方法执行耗时 (endTime - startTime) 毫秒);* return invoke;* }* }** 最后我们调用 calculator.add 方法的时候其实执行的是这个代理的对象的 add 方法**/public static void main(String[] args) {CalculatorImpl calculatorImpl new CalculatorImpl();//第二个参数表示生成的代理对象要实现哪些接口ICalculator calculator (ICalculator) Proxy.newProxyInstance(Demo2.class.getClassLoader(), new Class[]{ICalculator.class}, new InvocationHandler() {/*** param proxy 当前代理对象* param method 被拦截下来的方法* param args 被拦截下来的方法参数** return 被拦截下来的方法的返回值* throws Throwable*/Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long startTime System.currentTimeMillis();Object invoke method.invoke(calculatorImpl, args);long endTime System.currentTimeMillis();System.out.println(method.getName() 方法执行耗时 (endTime - startTime) 毫秒);return invoke;}});System.out.println(calculator.getClass() calculator.getClass());calculator.add(3, 4);}
}不需要任何额外依赖都是JDK 自带的能力: 1.Proxy.newProxyInstance方法表示要生成一个动态代理对象。 2.newProxylnstance方法有三个参数第一个是一个类加载器第二个参数是一个被代理的对象所实现的接口第三个则是具体的代理逻辑。 3.在 InvocationHandler 中有一个 invoke 方法该方法有三个参数分别表示当前代理对象被拦截下来的方法以及方法的参数我们在该方法中可以统计被拦截方法的执行时间通过方式执行被拦截下来的目标方法。 4.最终第一步的方法返回了一个代理对象执行该代理对象就有代理的效果了。上面这个案例就是一个JDK动态代理。这是一种运行时增强在编译阶段并未修改我们的代码。
2:CGLIB动态代理 从SpringBoot2 开始,AOP 默认使用的动态代理就是CGLIB动态代理了相比于 JDK 动态代理,CGLIB动态代理支持代理一个类。使用 CGLIB 动态代理需要首先添加依赖如下:
dependencygroupIdcglib/groupIdartifactIdcglib/artifactIdversion3.3.0/version
/dependency假设我有一个计算器如下:
这个没有接口
public class Calculator {int minus(int a, int b) {System.out.println(a - b (a - b));return a - b;}
}随后在添加一个拦截器
public class CalculatoProxy implements MethodInterceptor {/*** 这个就是拦截器将来 org.javaboy.demo.Calculator#minus(int, int) 方法执行的时候这个拦截器会被触发额外的工作就可以在这个方法中完成** param o 代理对象* param method 代理方法* param objects 方法的参数* param methodProxy 方法的代理对象* return 拦截下来的方法的返回值* throws Throwable*/Overridepublic Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {long startTime System.currentTimeMillis();//相当于调用父类的方法因为 CGLIB 动态代理相当于给当前类生成了一个子类在子类中添加了额外的逻辑Object result methodProxy.invokeSuper(o, objects);long endTime System.currentTimeMillis();System.out.println(method.getName() 方法执行耗时 (endTime - startTime) 毫秒);return result;}
}通过methodProxy.invokeSuper(o, objects);调用到代理方法最后如下配置CGLIB为方法增强
public static void main(String[] args) {Enhancer enhancer new Enhancer();enhancer.setSuperclass(Calculator.class);enhancer.setCallback(new CalculatoProxy());Calculator calculator (Calculator) enhancer.create();System.out.println(calculator.getClass() calculator.getClass());calculator.minus(3, 4);}这里其实就是创建了字节增强器为生成的代理对象配置superClass然后设置拦截下来之后的回调函数就行了最后通过create方法获取到一个代理对象。这就是 CGLIB 动态代理。 Spring AOP 底层本质上就是JDK 动态代理和CGLIB动态代理(运行时增强)它会根据我们的配置自动去创建代理对象可以做到零侵入。但是这并不意味着Spring AOP 就脱离了 AspectJ实际上Spring AOP 中还是需要用到 AspectJ只不过Spring AOP使用的是 AspectJ中的一些注解如Apsect、Before、Pointcut 等并没有使用AspectJ中的编译时增强。因此大家会发现我们在使用Spring AOP 中,也需要添加AspectJ相关的依赖。
如果有接口但是也想用CGLIB有java方式和xml(xml有两种)方式如下代码
xml第一种方式
aop:aspectj-autoproxy proxy-target-classtrue/xml第二种方式
aop:config proxy-target-classtrueaop:pointcut idpc1 expressionexecution(* com.richfit.richfit.service.XqyServiceImpl.*(..))/aop:aspect reflogAdviceaop:before methodbefore pointcut-refpc1/aop:after methodafter pointcut-refpc1/aop:after-throwing methodexception throwinge pointcut-refpc1/aop:after-returning methodreturnAdvice returningobject pointcut-refpc1/aop:around methodaround pointcut-refpc1//aop:aspect/aop:config上面主要是
aop:config proxy-target-classtruejava方式 AspectComponent
//这个相当于 aop:aspectj-autoproxy/这个注解是用来识别项目中的 Aspect 的
EnableAspectJAutoProxy(proxyTargetClass true)
public class LogAspect {}二:Spring AOP 和 AspectJ AOP
Spring AOP 和 AspectJ AOP 是两种完全不同的 AOP实现。
2.1:Spring AOP
Spring AOP 是 Spring 框架中的 AOP 实现依赖于 Spring 框架要结合 Spring Bean 才能使用。Spring AOP是基于动态代理实现的它的逻辑是这样如果被代理的对象有接口Spring AOP底层就会使用JDK动态代理;如果被代理的对象没有接口那么Spring AOP底层就会使用CGLIB 动态代理。Spring AOP关注的点主要是方法级别的内容即只能增强方法。 由于Spring AOP底层是动态代理而动态代理底层是JDK动态代理或者 CGLIB动态代理(动态代理相当于生成了一个子类)所以这就意味着如果方法或者类是 final 的或者方法是static 的都会导致动态代理失效(AOP失效)其实这也是 Spring 事务失效的原因。
2.2:AspectJ AOP
AspectJ AOP 是一个完整的、独立的、并且功能十分强大的AOP解决方案, Spring AOP 只能增强方法而AspectJ AOP 不仅能增强方法还能增强属性、构造器、static 方法、final 类、final 方法、private方法等等都能增强。AspectJ AOP 是编译时增强所以性能也要高于Spring AOP(当然它也支持运行时增强)。
Spring AOP 虽然在多项数据上落后于AspectJ AOP,但是Spring AOP简单易用易上手所以开发中还是以Spring AOP为主。
三:Spring AOP核心概念
Target/目标对象被拦截下来的对象/要被增强的对象如前面MoneyService、 Calculator 都是。
Join Point/连接点可以被切面插入的地方方法调用、异常抛出等。被切面增强的连接点。
PointCut/切点被切面增强的连接点。
Advice/通知/增强切点在连接处执行的代码其实就是把目标方法拦截下来之后要做的事情。
Aspect/切面切点通知。
Weaving/织入将 Aspect应用到Target 的过程就是Weaving。
Introduction/引介为 target增强属性和方法。
四:AOP做日志通知(xml配置基于springboot项目)
首先有一个aopadvice-spring.xml xml配置
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd!--加载aopadvice-spring.xml 步骤https://pythonjishu.com/uwrtruqtkjkhjud/本项目需要的类LogAdvicexmlaopadvice-spring.xmlapplication.properties中加spring.config.nameaopadvice-springspring.config.locationclasspath:/启动类里面加ImportResource(classpath:aopadvice-spring.xml)--bean classcom.richfit.richfit.bootConfig.AopLog.LogAdvice idlogAdvice/aop:configaop:pointcut idpc1 expressionexecution(* com.richfit.richfit.service.XqyServiceImpl.*(..))/aop:aspect reflogAdviceaop:before methodbefore pointcut-refpc1/aop:after methodafter pointcut-refpc1/aop:after-throwing methodexception throwinge pointcut-refpc1/aop:after-returning methodreturnAdvice returningobject pointcut-refpc1/aop:around methodaround pointcut-refpc1//aop:aspect/aop:config
/beans之后application.properties
spring.config.nameaopadvice-spring
spring.config.locationclasspath:/最后在启动类上加ImportResource(“classpath:aopadvice-spring.xml”)即可。