-
定义:
-
Aspect Oriented Programming 面向切面编程
-
切面本质上是个类,全名:切面类,切面(Aspect)类中可以指定五种类型的通知(advice)方法。
-
底层原理:动态代理,
-
实现AOP的框架有三个:
- spring AOP 、AspectJ、JBoss AOP
- spring AOP只支持到方法的连接点,另外两个还可以支持字段和构造器的连接点
- (直接在方法上做文章就可以了,在构造器上做什么文章?以后再研究)
-
通知、切点、连接点术语
-
通知(Advice):定义了切面是什么和何时调用,何时调用包含以下几种:需要增加的操作
- BeforeAdvice:在目标方法执行之前调用前置通知方法
- AfterAdvice:在目标方法执行之后调用后置通知方法,无论目标方法是否执行成功
- After-returning-Advice:在目标方法执行成功之后调用返回通知方法
- After-throwing-Advice:在目标方法执行抛出异常时调用异常通知方法
- AroundAdvice:目标方法在环绕通知方法的方法体中间调用
-
-
切点(PointCut):
- 定义:定义了在目标类的目标方法上切入通知方法,切点会匹配要要织入通知的一个或者多个连接点,我们通常使用明确的方法来指定这些切点,
或者利用execution表达式定义匹配到的类或者方法名来指定切点,指定需要增加操作的类方法,比如Service、Dao(有接口的实现类)
-
执行(* com.sample.service.impl … *。*(…))
-
符号 含义 执行() 表达式的主体; 第一个” *“符号 表示返回值的类型任意; com.sample.service.impl AOP所切的服务的包名,即,我们的业务部分 包名后面的” …“ 表示当前包及子包 第二个” *“ 表示类名,*即所有类。此处可以自定义,下文有举例 。*(…) 表示任何方法名,括号表示参数,两个点表示任何参数类型 -
连接点(jointPoint)
-
在应用程序执行过程中能够插入切面的一个点,这个点可以是调用方法时,也可以是修改一个字段时,切面代码根据这些连接点插入到应用的正常流程中,并添加新的行为,如日志、事务、安全、缓存等,连接通知和切点的标签,指定哪个哪个切点和哪个通知有关联
-
-
问题
- 事务操作、权限校验、日志记录、缓存存储等
-
如何使用
-
比依赖注入多了一种实现方式,xml、接口、注解
-
方式一xml:使用自定义通知,然后在xml中声明
-
在xml中的配置元素声明切面
-
AOP配置元素 描述 <aop:advisor>
定义AOP通知器 <aop:after>
定义AOP后置通知(不管该方法是否执行成功) <aop:after-returning>
在方法成功执行后调用通知 <aop:after-throwing>
在方法抛出异常后调用通知 <aop:around>
定义AOP环绕通知 <aop:aspect>
定义切面 <aop:aspect-autoproxy>
定义 @AspectJ
注解驱动的切面<aop:before>
定义AOP前置通知 <aop:config>
顶层的AOP配置元素,大多数的aop:*包含在aop:config元素内 <aop:declare-parent>
为被通知的对象引入额外的接口,并透明的实现 <aop:pointcut>
定义切点 -
自定义切面
-
package com.tf.aop.log; import org.aspectj.lang.ProceedingJoinPoint; public class XmlAopDemoUserLog { // 前置通知方法 public void beforeLog() { System.out.println("开始执行前置通知 日志记录"); } // 后置通知方法 public void afterLog() { System.out.println("开始执行后置通知 日志记录"); } // 返回通知方法 public void afterReturningLog() { System.out.println("方法成功执行后返回通知方法 日志记录"); } // 异常通知方法 public void afterThrowingLog() { System.out.println("方法抛出异常后执行异常通知方法 日志记录"); } // 环绕通知方法 public Object aroundLog(ProceedingJoinPoint joinpoint) { Object result = null; try { System.out.println("环绕通知方法:前置通知方法"); long start = System.currentTimeMillis(); result = joinpoint.proceed();// 目标方法执行 long end = System.currentTimeMillis(); System.out.println("总共执行时长" + (end - start) + " 毫秒"); System.out.println("环绕通知方法:返回通知方法"); } catch (Throwable t) { System.out.println("环绕通知方法:异常通知方法"); System.out.println("出现错误"); } System.out.println("环绕通知方法:后置通知方法"); return result; } }
-
创建需要增加操作的类
2. 4. package com.tf.aop.dao; public interface UserDao { void getUser(); void addUser(String name) ; }
package com.tf.aop.dao; import org.springframework.stereotype.Repository; @Repository public class UserDaoImpl implements UserDao { public String getUser() { System.out.println("查询返回了一个用户"); return "提莫"; } public void addUser(String name) { // System.out.println(2/0); System.out.println("增加了一个用户:"+name); } }
-
xml文件
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置自动扫描包 --> <context:component-scan base-package="com.tf.aop"></context:component-scan> <!-- 注册切面类到ioc --> <bean id="xmlAopDemoUserLog" class="com.tf.aop.log.XmlAopDemoUserLog"></bean> <!-- 使用自定义通知 --> <aop:config> <!--声明切点--> <aop:pointcut id="logPoint" expression="execution(* com.tf.aop.dao.UserDaoImpl.*(..))"></aop:pointcut> <!--声明切面 --> <aop:aspect ref="xmlAopDemoUserLog"> <!--声明通知--> <aop:before method="beforeLog" pointcut-ref="logPoint"></aop:before> <aop:after method="afterLog" pointcut-ref="logPoint"></aop:after> <aop:after-returning method="afterReturningLog" pointcut-ref="logPoint"></aop:after-returning> <aop:after-throwing method="afterThrowingLog" pointcut-ref="logPoint"></aop:after-throwing> <aop:around method="aroundLog" pointcut-ref="logPoint" ></aop:around> </aop:aspect> </aop:config> </beans>
-
pom.xml文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>Spring.Framerwork.Study</groupId> <artifactId>Spring_Framerwork</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>study_AOP</module> </modules> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.2.7.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <!-- 为了使用注解@Aspect --> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.6</version> </dependency> <!-- 为了使用注解@Aspect --> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency> </dependencies> </project>
-
测试:
@Test public void test03 (){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); UserDao userDao = (UserDao) applicationContext.getBean("userDaoImpl"); userDao.getUser(); }
-
-
方式二:使用spring框架中的通知接口
-
pom.xml文件导入包: aspectjweaver.jar
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency>
-
创建切面:实现不同的Advice接口:(作者从哪获取的信息来通过实现这些接口来做操作?)
-
接口类 类型 AfterReturningAdvice 返回通知方法 MethodBeforeAdvice 前置通知方法 MethodInterceptor 环绕通知方法 -
创建通知advice,每一种通知分别实现不同的通知advice接口
package com.tf.aop.log; import org.springframework.aop.AfterReturningAdvice; import java.lang.reflect.Method; public class AfterReturningLog implements AfterReturningAdvice { public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable { System.out.println("调用了:"+method+",方法执行成功后调用该通知"); } }
package com.tf.aop.log; import org.springframework.aop.MethodBeforeAdvice; import java.lang.reflect.Method; public class MotherBeforeLog implements MethodBeforeAdvice { public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println(objects); System.out.println("调用了方法:"+method+",在调用方法前,执行该通知"); } }
package com.tf.aop.log; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; // 环绕通知 public class AroundAdviceLog implements MethodInterceptor { public Object invoke(MethodInvocation methodInvocation) throws Throwable { Object result = null ; System.out.println("环绕通知的前置通知方法:"); System.out.println("getMethod:"+methodInvocation.getMethod()); System.out.println("getArguments:"+methodInvocation.getArguments()); try{ result = methodInvocation.proceed();// 执行目标方法 System.out.println("环绕通知的返回通知方法:"); }catch (Exception e){ System.out.println(e.getStackTrace()); System.out.println("环绕通知的异常通知方法:"); } System.out.println("环绕通知的异常通知方法:"); return result; } }
-
配置xml
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置自动扫描包 --> <context:component-scan base-package="com.tf.aop"></context:component-scan> <!-- 注册bean到ioc --> <!--<bean id="userService" class="com.tf.aop.service.UserServiceImpl"></bean>--> <!--<bean id="userDaoImpl" class="com.tf.aop.dao.UserDaoImpl"></bean>--> <bean id="xmlAopDemoUserLog" class="com.tf.aop.log.XmlAopDemoUserLog"></bean> <bean id="afterReturningLog" class="com.tf.aop.log.AfterReturningLog"></bean> <bean id="methorBeforeLog" class="com.tf.aop.log.MethorBeforeLog"></bean> <bean id="aroundAdviceLog" class="com.tf.aop.log.AroundAdviceLog"></bean> <!--aop配置标签中配置切面和切点--> <aop:config > <aop:pointcut id="logPoint" expression="execution( * com.tf.aop.dao.UserDaoImpl.getUser(..))"></aop:pointcut> <aop:pointcut id="logPointB" expression="execution( * com.tf.aop.dao.UserDaoImpl.addUser(..))"></aop:pointcut> <!-- 使用顾问连接切面和切点,或者说使用顾问连接通知和切点 --> <aop:advisor advice-ref="methorBeforeLog" pointcut-ref="logPoint"></aop:advisor> <aop:advisor advice-ref="afterReturningLog" pointcut-ref="logPoint"></aop:advisor> <aop:advisor advice-ref="aroundAdviceLog" pointcut-ref="logPointB"></aop:advisor> </aop:config> </beans>
-
Error:发现了以元素 ‘{“http://www.springframework.org/schema/aop”:pointcut}’ 开头的无效内容。应以 ‘{“http://www.springframework.org/schema/aop”:aspect}’ 之一开头。
原因:标签的顺序错了!<aop:pointcut 和 </aop:config 是有先后顺序的!
-
Caused by: java.lang.IllegalArgumentException: warning no match for this type name: com.tf.aop.dao [Xlint:invalidAbsoluteTypeName]
原因:没有指定到具体的类型:
<aop:pointcut id="logPoint" expression="execution( * com.tf.aop.dao(..))"></aop:pointcut>
<aop:pointcut id="logPoint" expression="execution( * com.tf.aop.dao.UserDaoImpl.getUser(..))"></aop:pointcut>
-
-
测试
@Test public void test02 (){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); // getBean方法的二个参数无论是接口还是实现类都可以 //Caused by: java.lang.IllegalArgumentException: // warning no match for this type name: com.tf.aop.dao [Xlint:invalidAbsoluteTypeName] // 因为是动态代理:getBean方法中不要指定class类型,返回值是接口。 UserDao userDao = (UserDao) applicationContext.getBean("userDaoImpl"); userDao.getUser(); }
-
-
-
方式三:使用注解实现AOP:
- pom.xml引入两个jar包:aspectjrt.jar和aspectjweaver.jar
<!-- 为了使用注解@Aspect --> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjrt --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.9.6</version> </dependency> <!-- 为了使用注解@Aspect --> <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.5</version> </dependency>
2. 创建切面类
package com.tf.aop.log; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect // 标注为切面类 @Component // 注册到ioc容器 public class AopAnnoLog { // 前置通知方法:目标方法调用前,会被执行的通知方法 @Before("execution(* com.tf.aop.dao.UserDaoImpl.*(..))") public void before(JoinPoint point){ /** * 接口:JoinPoint point 解决了通知方法对目标方法的参数做操作问题 * */ // System.out.println(point.getArgs());//[Ljava.lang.Object;@2beee7ff 参数数组,也是只有参数是有用信息 // System.out.println(point.getKind());//method-execution // System.out.println(point.getSignature());//String com.tf.aop.dao.UserDao.getUser() 被通知前置的方法全路径名 // System.out.println(point.getSourceLocation());//org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@29647f75 // System.out.println(point.getStaticPart());//execution(String com.tf.aop.dao.UserDao.getUser()) // System.out.println(point.getTarget());//com.tf.aop.dao.UserDaoImpl@3315d2d7 // System.out.println(point.getThis());//com.tf.aop.dao.UserDaoImpl@3315d2d7 // System.out.println(point.toLongString());//execution(public abstract java.lang.String com.tf.aop.dao.UserDao.getUser()) // System.out.println(point.toShortString());//execution(UserDao.getUser()) // System.out.println(point.toString());//execution(String com.tf.aop.dao.UserDao.getUser()) // System.out.println("=============================="); Object[] args = point.getArgs(); for (Object obj : args){ System.out.println("参数:"+obj); } System.out.println("目标方法的前置通知方法"); } // 后置通知方法,即使目标通知方法抛异常了,该通知方法仍然会被执行 @After("execution(* com.tf.aop.dao.UserDaoImpl.*(..))") public void after (){ System.out.println("目标方法的后置通知方法"); } /** * 后置方法如何解决对目标方法的结果值做操作的问题? * 注解@AfterReturning 使用了 属性:returning="param_name" */ // 返回通知方法: 目标方法调用成功后的通知方法 @AfterReturning(value="execution(* com.tf.aop.dao.UserDaoImpl.*(..))",returning = "result") public void afterReturning(JoinPoint joinPoint,Object result){ System.out.println("调用目标方法名称:"+joinPoint.getSignature().getName()); System.out.println("返回值对象为:"+result); System.out.println("目标方法调用成功后的通知方法"); } // 异常通知方法:目标方法调用抛异常后调用的通知方法 @AfterThrowing(value="execution(* com.tf.aop.dao.UserDaoImpl.*(..))",throwing = "ex") public void afterThrowingLog(Exception ex){ System.out.println("抛出的异常是:"+ex); System.out.println("getMessage:"+ex.getMessage()); System.out.println("目标方法调用抛异常后调用的通知方法"); } /** * 环绕通知需要携带:ProceedingJoinPoint 类型的参数 * 环绕通知需要有返回值,返回值即是目标方法的返回值 * @param joinPoint */ // 环绕通知方法:本质上是前面四个通知方法的综合体 @Around(value = "execution(* com.tf.aop.dao.UserDaoImpl.*(..))" ) public Object aroundLog(ProceedingJoinPoint joinPoint ){ System.out.println("环绕通知方法:前置通知方法"); Object result = null ; try { result = joinPoint.proceed();// 执行目标方法 System.out.println("环绕通知方法:返回通知方法"); } catch (Throwable throwable) { System.out.println("环绕通知方法:异常通知方法"); throwable.printStackTrace(); } System.out.println("环绕通知方法:后置通知方法"); return result ; } }
3. application-context.xml文件配置:
<?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:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" 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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd"> <!-- 配置自动扫描包 --> <context:component-scan base-package="com.tf.aop"></context:component-scan> <!-- 自动为切面方法中匹配的方法所在的类生成代理对象。 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
4. 测试:
@Test public void test04 (){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); UserDao userDao = (UserDao) applicationContext.getBean("userDaoImpl"); System.out.println(userDao.getUser()); System.out.println("============================="); userDao.addUser("提莫"); }
-
Spring-Aop-应用
最新推荐文章于 2024-09-06 18:50:44 发布