Spring基础知识之AOP的理解与应用
AOP是什么?
面向切面编程,传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性的问题,这些横切性的问题与我们的业务逻辑关系不大,这些横切性的问题不会影响到主业务逻辑的实现,但是会散落到代码的各个部分,难以维护。AOP就是处理一些横切性的问题,AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的,使代码的重用性的开发效率更高。
APO的应用场景?
- 日志记录
- 权限验证
- 效率检查
- 事务处理
- exception
SpringAOP 和 AspectJ的关系?
AOP是一种编程理念,SpringAOP和AspectJ都是AOP的实现,SpringAOP有自己的语法,但是语法过于复杂,所以SpringAOP借助了AspectJ的注解,但是底层还是自己的。
SpringAOP提供的两种编程风格:
1.使用@Aspect注解
2.使用xml配置,aop:config命名空间
SpringAOP支持AspectJ的步骤:
1.启用@AspectJ注解:使用Java@Configuration声明配置类,添加@EnableAspectJAutoProxy注解启用@AspectJ支持
1 /** 2 * 使用注解实现aop代理 3 * @author Administrator 4 * 5 */ 6 @Configuration 7 @EnableAspectJAutoProxy 8 @ComponentScan 9 public class AOPAnnotation { 10 11 }
2.声明一个@AspectJ注释类,并且定义成一个bean交给spring容器管理。
1 /** 2 * 1.创建aop的切面类 3 * @author Administrator 4 * 5 */ 6 @Component//首先加入spring容器中 7 @Aspect//使用aspectj注解 8 public class AspectjUtil { 9 10 11 }
3.声明一个pointCut,切入点表达式有@Pointcut注释表示,切入点声明由两部分组成,一个签名包含名称和任何参数,以及一个切入点表达式,该切入点表达式确定我们对哪个方法的执行感兴趣。
声明切入点的几种方式(后面有详细讲解):
1.execution,匹配方法执行连接点
2.within,将匹配限制为特定类型中的连接点
3.args,参数
4.target,目标队形
5.this,代理对象
1 /** 2 * 1.创建aop的切面类 3 * @author Administrator 4 * 5 */ 6 @Component//首先加入spring容器中 7 @Aspect//使用aspectj注解 8 public class AspectjUtil { 9 10 /** 11 * 2.声明一个切点 12 * @Description: TODO 13 * @returnType: void 14 */ 15 @Pointcut("execution(* org.wk.spring.dao.*.*(..))")//切入点表达式 16 public void pointCut(){//切入点签名 17 System.out.println("pointCut方法"); 18 } 19 }
4.声明一个Advice通知,advice通知和pointcut切入点表达式相关联,并在切入点匹配的方法执行@Before之前,@After之后或前后运行。
通知类型:
1.Before:连接点执行之前,但是无法阻止连接点的正常执行,除非该段执行抛出异常。
2.After:连接点正常执行之后,执行过程中,正常执行结束后退出,非异常退出。
3.After throwing:连接点抛出异常后执行
4.After(finally):无论连接点是正常退出,还是异常退出,都会执行。
5.Around:围绕连接点执行,例如方法调用,这是最有用的切面方式,around通知可以在方法调用之前和之后执行自定义行为。
1 /** 2 * 1.创建aop的切面类 3 * @author Administrator 4 * 5 */ 6 @Component//首先加入spring容器中 7 @Aspect//使用aspectj注解 8 public class AspectjUtil { 9 10 /** 11 * 2.声明一个切点 12 * @Description: TODO 13 * @returnType: void 14 */ 15 @Pointcut("execution(* org.wk.spring.dao.*.*(..))")//切入点表达式 16 public void pointCut(){//切入点签名 17 System.out.println("pointCut方法"); 18 } 19 20 /** 21 * 3.定义通知点 22 * @Description: TODO 23 * @returnType: void 24 * 声明before通知,在pointcut切入点之前运行 25 */ 26 @Before("pointCut()") 27 public void before(){ 28 //编写通知的逻辑 29 System.out.println("在pointCut方法之前"); 30 } 31 }
各个连接点Pointcut的意义?
1.execution,用于匹配方法执行join points连接点,表达式的最小粒度是方法,在aop中最多使用。
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:方法的参数类型,如java.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(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切面粒度最小是表达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的信息,并且在spring中,大部分使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用范围是最为广泛的。
2.within,表达式的最小粒度为类。
@Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
3.args,表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关。
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)")//等同于@target
ProceedingJoinPoint 和JoinPoint的区别?
ProceedingJoinPoint继承了JoinPoint,其中proceed()方法是aop代理链执行的方法。ProceedingJointPoint扩充实现了proceed()方法,用于继续执行连接点。JoinPoint仅能获取相关参数,无法执行连接点。
JoinPoint的方法:
1.java.lang.Object[] getArgs():获取连接点方法运行时的入参列表。
2.Signature getSignature() :获取连接点的方法签名对象。
3.java.lang.Object getTarget() :获取连接点所在的目标对象。
4.java.lang.Object getThis() :获取代理对象本身。
proceed()有重载,有个带参数的方法,可以修改目标方法的的参数
1 /** 2 * 1.创建aop的切面类 3 * @author Administrator 4 * 5 */ 6 @Component//首先加入spring容器中 7 @Aspect//使用aspectj注解 8 public class AspectjUtil { 9 10 /** 11 * 2.声明一个切点 12 * @Description: TODO 13 * @returnType: void 14 */ 15 @Pointcut("execution(* org.wk.spring.dao.*.*(..))")//切入点表达式 16 public void pointCut(){//切入点签名 17 System.out.println("pointCut方法"); 18 } 19 20 @Around("pointCut()") 21 public void around(ProceedingJoinPoint pjp){ 22 System.out.println("before around"); 23 //获取切点中方法的参数 24 Object[] args = pjp.getArgs(); 25 if(args!=null && args.length>0){ 26 //对切入方法的参数进行逻辑处理 27 for (int i = 0; i < args.length; i++) { 28 args[i]=args[i]+" world!"; 29 } 30 } 31 //执行环绕方法,把进行逻辑处理后的参数传进去 32 try { 33 pjp.proceed(args); 34 } catch (Throwable e) { 35 // TODO Auto-generated catch block 36 e.printStackTrace(); 37 } 38 System.out.println("after around"); 39 } 40 }
完成了对参数的逻辑处理。
使用XML方式配置AOP:
1.创建处理切面的类,定义处理方法
1 /** 2 * 使用xml配置aop的类 3 * @author Administrator 4 * 5 */ 6 public class AOPXmlUtil { 7 /** 8 * 定义处理方法,与xml通知相对应 9 * @Description: TODO 10 * @returnType: void 11 */ 12 public void before(){ 13 System.out.println("xml before"); 14 } 15 }
2.配置spring.xml文件,使用aop相关标签
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" 5 xmlns:c="http://www.springframework.org/schema/c" 6 xmlns:aop="http://www.springframework.org/schema/aop" 7 xmlns:context="http://www.springframework.org/schema/context" 8 xsi:schemaLocation="http://www.springframework.org/schema/beans 9 https://www.springframework.org/schema/beans/spring-beans.xsd 10 http://www.springframework.org/schema/context 11 https://www.springframework.org/schema/context/spring-context.xsd 12 http://www.springframework.org/schema/aop 13 https://www.springframework.org/schema/aop/spring-aop.xsd"> 14 <bean id="dao" name="dao" class="org.wk.spring.dao.impl.IndexDaoImpl"> 15 </bean> 16 <bean id="service" class="org.wk.spring.service.impl.IndexServiceImpl"> 17 <property name="dao" ref="dao"></property> 18 </bean> 19 <!--引入处理切面的类 ,类中提供切面之后执行的逻辑方法。 --> 20 <bean id ="xmlAop" class="org.wk.spring.aop.AOPXmlUtil"></bean> 21 <!--引入aop的标签,在xml中配置切面 --> 22 <aop:config> 23 <!--引入切点 --> 24 <!-- aop:pointcut ID重复会出现覆盖,以最后出现的为准。不同aop:aspect内出现的pointcut配置,可以相互引用 --> 25 <aop:pointcut expression="execution(* org.wk.spring.dao.*.*(..))" id="allDao"/> 26 <!--创建aop切面处理类 --> 27 <!-- aop:aspect ID重复不影响正常运行,依然能够有正确结果 --> 28 <aop:aspect id="" ref="xmlAop"> 29 <aop:before method="before()" pointcut-ref="allDao"/> 30 </aop:aspect> 31 </aop:config> 32 </beans>