一、aop相关依赖
要使用spring的aop功能需要导入如下依赖(理论上只需要导入aop
和aspects
就行了,但是如果只有aop
和aspects
可以使用aop的基本功能,但是此时只能代理接口,所以还需要导入外部依赖)
<!--spring aop相关 -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cglib/cglib-nodep -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<!-- spring-aspects中默认引入了aspectjweaver1.8.9版本 与1.9.6有包冲突 所以这里注释掉了 -->
<!-- <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency> -->
二、基于xml的aop配置
- 定义切面类
该类只是一个简单的测试异常通知
、环绕通知
的通知方法都没有public class LoginAspect { /** * 通知方法 前置通知 */ public void beforeNotice(){ System.out.println("执行前置通知"); } /** * 通知方法 后置通知 */ public void afterNotice(){ System.out.println("执行后置通知"); } }
- LoginService
LoginService可以理解为真实对象(即需要被代理的对象)@Service public class LoginService { public String login(String userName, String password){ System.out.println("用户"+userName+"登录"); return "success"; } }
- aop-xml
spring的aop的配置需要先导入aop的命名空间和xsd,aop的配置都放在<aop:config>标签中<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:util="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"> <!-- 装配切面类 --> <bean id="loginAspect" class="com.haici.spring.demo.aop.LoginAspect"/> <!--aop的配置 --> <aop:config> <!--定义切面--> <aop:aspect ref="loginAspect"> <!--定义切点 execution(* *.*(..)) 第一个* 表示方法的返回值可以是任意类型 第二个*表示任意包下的任意的类 第三个*表示任意方法 (..)表示任意参数 --> <aop:pointcut id="action" expression="execution(* *.*(..))"/> <!-- 声明前置通知 (在切点方法被执行前调用)--> <aop:before method="beforeNotice" pointcut-ref="action"/> <!-- 声明后置通知 (在切点方法被执行后调用)--> <aop:after method="afterNotice" pointcut-ref="action"/> <!--环绕通知--> <!-- <aop:around method="aroundNotice" pointcut-ref="action"/> --> <!--异常通知--> <!-- <aop:after-throwing method="afterThrowingNotice" pointcut-ref="action"/> --> </aop:aspect> </aop:config> </beans>
三、基于注解的aop配置
- 定义切面类
注意观察与基于xml的aop配置中的切面类的不同 需要注意的是如果同一个切点中(execution表达式一样)如果同时定义了环绕通知和前置通知,会出现前置通知不生效的问题
。package com.haici.spring.demo.aop; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; import java.util.Arrays; /** * 切面类 * @Aspect 注解用来标识 */ @Aspect @Component public class LoginAspect { /** * 定义一个切点 */ @Pointcut("execution(public * com.haici.spring.demo.aop.LoginService.login(java.lang.String, java.lang.String))") public void pointcut() { } /** * execution(* *.*(..)) 切入点表达式 * @Before 前置通知 在方法执行前运行该方法 * 通知方法 前置通知 */ @Before("pointcut()") public void beforeNotice(JoinPoint point){ System.out.println("执行前置通知 入参 = " + Arrays.toString(point.getArgs())); } /** * execution(* *.*(..)) 切入点表达式 * @After 后置通知 在方法执行之后运行 * 通知方法 后置通知 */ @After("pointcut()") public void afterNotice(JoinPoint point){ System.out.println("执行后置通知 "); } /** * 不通过切点的方式 直接在注解上配置切点表达式也是可以的 * @Around 环绕通知 在方法前后运行 * 通知方法 环绕通知 如果加入了换肉通知之后 前置通知是不会生效的(对于同一个切点来说) */ //@Around("execution(public * com.haici.spring.demo.aop.LoginService.login(java.lang.String, java.lang.String))") public void around(JoinPoint point){ } }
- LoginService
@Service public class LoginService { public String login(String userName, String password){ System.out.println("用户"+userName+"登录"); return "success"; } }
- aop xml配置
<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:util="http://www.springframework.org/schema/util" 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/util http://www.springframework.org/schema/util/spring-util-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd"> <!-- 开启基于注解的aop功能--> <aop:aspectj-autoproxy/> </beans>
四、IOC容器如何处理代理对象
在上面的基础上做这样一个单元测试, 可以发现最后打印出来的LoginService的class不是真实的LoginService的class 而是一个代理类的Class, 这就说明IOC容器将动态代理后的代理对象装配到了容器中。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class LoginServiceTest {
@Autowired
private LoginService loginService;
@Test
public void test(){
loginService.login("zhangsan", "1234");
System.out.println(loginService.getClass()); //会发现打印出来的类型是代理对象的类型 而不是LoginService的Class
System.out.println();
}
}
四、最后
aop可以分为四步
- 导入aop依赖
- 导入aop的xsd合命名空间
- 编写切面类 切面类需要被spring管理(可以通过<aop:aspect>或者@Aspect注解来标识)
- 开启基于注解的aop功能 第四步只有基于注解的aop配置才会有
另外 本次示例只是展示了aop的一个基本用法,关于aop中的切面表达式execution(* *.*(..))
的介绍见后面的博客。