Aop是什么?
与OOP对比,面向切面,传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,这些横切性的问题和我们的主业务逻辑关系不大,这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护。AOP是处理一些横切性问题,AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。使代码的重用性和开发效率更高。
aop的应用场景
- 日志记录
- 权限验证
- 效率检查
- 事务管理
- exception
springAop的底层技术
JDK动态代理 | CGLIB代理 | |
---|---|---|
编译时期的织入还是运行时期的织入? | 运行时期织入 | 运行时期织入 |
初始化时期织入还是获取对象时期织入? | 初始化时期织入 | 初始化时期织入 |
springAop和AspectJ的关系
Aop是一种概念springAop、AspectJ都是Aop的实现,SpringAop有自己的语法,但是语法复杂,所以SpringAop借助了AspectJ的注解,但是底层实现还是自己的
spring AOP提供两种编程风格@AspectJ support ------------>利用aspectj的注解Schema-based AOP support ----------->xml aop:config 命名空间证明:spring,通过源 码分析了,我们可以知道spring底层使用的是JDK或者CGLIB来完成的代理,并且在官网上spring给出了aspectj的文档,和springAOP是不同的
spring Aop的概念
aspect:一定要给spring去管理 抽象 aspectj->类
pointcut:切点表示连接点的集合 -------------------> 表 (我的理解:PointCut是JoinPoint的谓语,这是一个动作,主要是告诉通知连接点在哪里,切点表达式决定 JoinPoint 的数量)Joinpoint:连接点 目标对象中的方法 ----------------> 记录 (我的理解:JoinPoint是要关注和增强的方法,也就是我们要作用的点)
Weaving :把代理逻辑加入到目标对象上的过程叫做织入target 目标对象 原始对象aop Proxy 代理对象 包含了原始对象的代码和增加后的代码的那个对象
advice:通知 (位置 + logic)
advice通知类型:
Before 连接点执行之前,但是无法阻止连接点的正常执行,除非该段执行抛出异常
After 连接点正常执行之后,执行过程中正常执行返回退出,非异常退出
After throwing 执行抛出异常的时候
After (finally) 无论连接点是正常退出还是异常退出,都会执行
Around advice: 围绕连接点执行,例如方法调用。这是最有用的切面方式。around通知可以在方法调用之前和之后执行自定义行为。它还负责选择是继续加入点还是通过返回自己的返回值或抛出异常来快速建议的方法执行。
Proceedingjoinpoint 和JoinPoint的区别:
Proceedingjoinpoint 继承了JoinPoint,proceed()这个是aop代理链执行的方法。并扩充实现了proceed()方法,用于继续执行连接点。JoinPoint仅能获取相关参数,无法执行连接点。JoinPoint的方法
1.java.lang.Object[] getArgs():获取连接点方法运行时的入参列表; 2.Signature getSignature() :获取连接点的方法签名对象; 3.java.lang.Object getTarget() :获取连接点所在的目标对象; 4.java.lang.Object getThis() :获取代理对象本身;
proceed()有重载,有个带参数的方法,可以修改目标方法的的参数Introductionsperthis使用方式如下:@Aspect(“perthis(this(com.chenss.dao.IndexDaoImpl))”)要求:
- AspectJ对象的注入类型为prototype
- 目标对象也必须是prototype的原因为:只有目标对象是原型模式的,每次getBean得到的对象才是不一样的,由此针对每个对象就会产生新的切面对象,才能产生不同的切面结果。
springAop支持AspectJ
1、启用@AspectJ支持
使用Java Configuration启用@AspectJ支持要使用Java @Configuration启用@AspectJ支持,请添加@EnableAspectJAutoProxy注释
@Configuration@EnableAspectJAutoProxy
public class AppConfig {}
使用XML配置启用@AspectJ支持要使用基于xml的配置启用@AspectJ支持,可以使用aop:aspectj-autoproxy元素
<aop:aspectj-autoproxy/>
2、声明一个Aspect
申明一个@Aspect注释类,并且定义成一个bean交给Spring管理。
@Component@Aspect
public class UserAspect {}
3、申明一个pointCut
切入点表达式由@Pointcut注释表示。切入点声明由两部分组成:一个签名包含名称和任何参数,以及一个切入点表达式,该表达式确定我们对哪个方法执行感兴趣。
@Pointcut("execution(* transfer(..))")// 切入点表达式
private void anyOldTransfer() {}// 切入点签名
切入点确定感兴趣的 join points(连接点),从而使我们能够控制何时执行通知。Spring AOP只支持Spring bean的方法执行 join points(连接点),所以您可以将切入点看作是匹配Spring bean上方法的执行。
申明一个Advice通知
advice通知与pointcut切入点表达式相关联,并在切入点匹配的方法执行@Before之前、@After之后或前后运行。
/*
* 切面类
* */
@Component
@Aspect
public class AspectjTest {
@Pointcut("execution(* yongli.aopAnnotation.dao.*.*(..))") // 最细粒度可以匹配到 方法名和 方法参数
public void pointCut(){}
@After("pointCut()")
public void after(){
System.out.println("after");
}
各种连接点joinPoint的意义:
1.execution
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)这里问号表示当前项可以有也可以没
有,其中各项的语义如下modifiers-pattern:方法的可见性,如public,protected;ret-type-pattern:方法的返回值类型,如int,void等;declaring-type-
pattern:方法所在类的全路径名,如com.spring.Aspect;name-pattern:方法名类型,如buisinessService();param-pattern:方法的参数类型,如j
ava.lang.String;throws-pattern:方法抛出的异常类型,如java.lang.Exception;example:@Pointcut(“execution(* com.chenss.dao..(…))”)//匹配
com.chenss.dao包下的任意接口和类的任意方法@Pointcut(“execution(public * com.chenss.dao..(…))”)//匹配com.chenss.dao包下的任意接口和类的
public方法@Pointcut(“execution(public * com.chenss.dao..())”)//匹配com.chenss.dao包下的任意接口和类的public 无方法参数的方法
@Pointcut(“execution(* com.chenss.dao..(java.lang.String, …))”)//匹配com.chenss.dao包下的任意接口和类的第一个参数为String类型的方法
@Pointcut(“execution(* com.chenss.dao..(java.lang.String))”)//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut(“execution(* com.chenss.dao..(java.lang.String))”)//匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
@Pointcut(“execution(public * (…))")//匹配任意的public方法@Pointcut("execution( te*(…))”)//匹配任意的以te开头的方法@Pointcut(“execution(*
com.chenss.dao.IndexDao.(…))")//匹配com.chenss.dao.IndexDao接口中任意的方法@Pointcut("execution( com.chenss.dao….(…))”)//匹配
com.chenss.dao包及其子包中任意的方法关于这个表达式的详细写法,可以脑补也可以参考官网很容易的
,可以作为一个看spring官网文档的入门,打破你害怕看官方文档的心理,其实你会发觉官方文档也是很容易的https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts-examples
由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的信息,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的
2.within
™表达式的最小粒度为类
// ------------//
within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等@Pointcut(“within(com.chenss.dao.)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.chenss.dao…)”)//匹配com.chenss.dao包及其子包中的任意方法
3.args
args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关
args同execution不同的地方在于:
args匹配的是运行时传递给方法的参数类型
execution(* *(java.io.Serializable))匹配的是方法在声明时指定的方法参数类型。
@Pointcut(“args(java.io.Serializable)”)//匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配@Pointcut("@args(com.chenss.anno.Chenss)")//接受一个参数,并且传递的参数的运行时类型具有@Classified
4. this JDK代理时,指向接口和代理类proxy,cglib代理时 指向接口和子类(不使用proxy)
5.target 指向接口和子类
此处需要注意的是,如果配置设置proxyTargetClass=false,或默认为false,则是用JDK代理,否则使用的是CGLIB代理 * JDK代理
的实现方式是基于接口实现,代理类继承Proxy,实现接口。 * 而CGLIB继承被代理的类来实现。 * 所以使用target会保证目标不变,关联对象不会受到这个设置的影响。 * 但是使用this对象时,会根据该选项的设置,判断是否能找到对象。
@Pointcut(“target(com.chenss.dao.IndexDaoImpl)”)//目标对象,也就是被代理的对象。限制目标对象为com.chenss.dao.IndexDaoImpl类@Pointcut(“this(com.chenss.dao.IndexDaoImpl)”)//当前对象,也就是代理对象,代理对象时通过代理目标对象的方式获取新的对象,与原值并非一个@Pointcut("@target(com.chenss.anno.Chenss)")//具有@Chenss的目标对象中的任意方法@Pointcut("@within(com.chenss.anno.Chenss)")//等同于@targ
6.@annotation
@Pointcut(“bean(dao1)”)//名称为dao1的bean上的任意方法
@Pointcut(“bean(dao*)”)
Spring AOP XML实现方式的注意事项:
- 在aop:config中定义切面逻辑,允许重复出现,重复多次,以最后出现的逻辑为准,但是次数以出现的次数为准
- aop:aspect ID重复不影响正常运行,依然能够有正确结果
- aop:pointcut ID重复会出现覆盖,以最后出现的为准。不同aop:aspect内出现的pointcut配置,可以相互引用
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 定义开始进行注解扫描 --> <context:component-scan base-package="com.chenss"></context:component-scan>
<!-- 定义AspectJ对象使用的逻辑类,类中提供切面之后执行的逻辑方法 -->
<bean id="aspectAop" class="com.chenss.aspectj.Aspect"></bean>
<bean id="aspectAop2" class="com.chenss.aspectj.Aspect2"></bean>
<bean id="indexDao" class="com.chenss.entity.IndexDao"></bean>
<!--在Config中定义切面逻辑,允许重复出现,重复多次,以最后出现的逻辑为准,但是次数以出现的次数为准-->
<aop:config>
<!-- aop:aspect ID重复不影响正常运行,依然能够有正确结果 -->
<!-- aop:pointcut ID重复会出现覆盖,以最后出现的为准。不同aop:aspect内出现的pointcut配置,可以相互引用 -->
<aop:aspect id="aspect" ref="aspectAop">
<aop:pointcut id="aspectCut" expression="execution(* com.chenss.entity.*.*())"/>
<aop:before method="before" pointcut-ref="aspectCut"></aop:before>
<aop:pointcut id="aspectNameCut" expression="execution(* com.chenss.entity.*.*(java.lang.String, ..))"/>
<aop:before method="before2" pointcut-ref="aspectNameCut"></aop:before>
</aop:aspect>
</aop:config>
</beans>
spring AOP的源码分析
jdk 动态代理的实现
第一步骤
/*
* 仿照InvocationHandler 接口写的自定义 handler接口
* */
public interface YongliHandler {
Object invoke(Method method) throws Throwable;
}
第二步骤
/*
* 实现动态代理 业务逻辑为活的
* */
public class ProxyDynamicPerfect {
public static Object newInstance(Class targetInf , YongliHandler h ) {
Object proxy=null; //代理对象
Method methods[] =targetInf.getDeclaredMethods(); //获方法数组
String line="\n";
String tab ="\t";
String infName = targetInf.getSimpleName(); //获取类的名字
String content =""; //整个要写入文件的字符串
String packageContent = "package com.proxy;"+line; //生成的包名
String importContent = "import "+targetInf.getName()+";"+line
+"import com.yongli.Handler.YongliHandler;"+line
+"import java.lang.Exception;"+line
+"import java.lang.reflect.Method;"+line; //导入的包名
String clazzFirstLineContent = "public class $Proxy implements "+infName+" {"+line; //实现的接口和 类名
String filedContent =tab+"private YongliHandler h;"+line; //定义私有变量为 目标对象
String constructorContent =tab+"public $Proxy (YongliHandler h) {" +line //构造方法为导入目标对象
+tab+tab+"this.h =h;"
+line+tab+"}"+line;
String methodContent = ""; //接收方法拼接变量
for (Method method: methods ) {
String returnTypeName = method.getReturnType().getSimpleName(); //获取返回值
String methodName =method.getName(); //获取方法名
Class args[] = method.getParameterTypes(); //获取当前方法参数数组
String argsContent = "";
String paramsContent="";
int flag =0;
for (Class arg : args) {
String temp = arg.getSimpleName(); //获取当前参数的类型
//String
//String p0,Sting p1,
argsContent+=temp+" p"+flag+","; //(String p1,String p2,)
paramsContent+="p"+flag+",";
flag++;
}
if (argsContent.length()>0){ //如果参数大于0 证明有参数 那么 最后一个参数会多加 ,
argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1);
paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1);
}
methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+") throws Throwable {"+line //拼接方法
+tab+tab+"Method method = " +
"Class.forName(\""+targetInf.getName()+"\").getDeclaredMethod(\""+methodName+"\");"+line+//根据方法名称 通过反射接口拿到方法名
"Object rs =h.invoke(method);";
if (!returnTypeName.equals("void")){
methodContent+="return ("+ returnTypeName+") rs";
}
methodContent+=tab+"}"+line;
}
content=packageContent+importContent+clazzFirstLineContent+filedContent+constructorContent+methodContent+"}"; //拼接整个类
File file =new File("f:\\com\\proxy\\$Proxy.java"); //写入指定路径
try {
if (!file.exists()) {
file.createNewFile();
}
FileWriter fw = new FileWriter(file);
fw.write(content);
fw.flush();
fw.close();
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
Iterable units = fileMgr.getJavaFileObjects(file);
JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
t.call();
fileMgr.close();
URL[] urls = new URL[]{new URL("file:F:\\\\")}; //对象所在路径
URLClassLoader urlClassLoader = new URLClassLoader(urls);
Class clazz = urlClassLoader.loadClass("com.proxy.$Proxy"); //加载进jvm
Constructor constructor = clazz.getConstructor(YongliHandler.class);
proxy = constructor.newInstance(h);
}catch (Exception e){
e.printStackTrace();
}
return proxy; //返回形成程对象
}
}
第三步骤
public class Test4 {
public static void main(String[] args) throws Throwable {
Class[] interfaces ={YongliDao.class};
YongliDaoImpl yongliDao = new YongliDaoImpl();
YongliHandlerImpl yongliHandler = new YongliHandlerImpl(yongliDao);
YongliDao yongliDao1 = (YongliDao) ProxyDynamicPerfect.newInstance(interfaces[0], yongliHandler);
yongliDao1.query();
}
}