解析Spring第三天(面向切面AOP)
面向切面:AOP
在不修改源代码的基础上,对方法进行增强。AOP的底层原理就是代理技术(第一种:jdk的动态代理(编写程序必须要有接口)。第二种:cglib代理技术(生成类的子类)。如果编写的程序有借口,则spring框架会自动使用jdk的动态代理技术增强,)。
Joinpoint(连接点) 所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点) -- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
Advice(通知/增强)-- 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
Target(目标对象)-- 代理的目标对象
Weaving(织入)-- 是指把增强应用到目标对象来创建新的代理对象的过程
Proxy(代理)-- 一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面)-- 是切入点和通知的结合,以后自己来编写和配置的
创建一个普通的Maven项目工程引入坐标
1 <dependencies> 2 <dependency> 3 <groupId>org.springframework</groupId> 4 <artifactId>spring-context</artifactId> 5 <version>5.0.2.RELEASE</version> 6 </dependency> 7 <dependency> 8 <groupId>commons-logging</groupId> 9 <artifactId>commons-logging</artifactId> 10 <version>1.2</version> 11 </dependency> 12 <dependency> 13 <groupId>log4j</groupId> 14 <artifactId>log4j</artifactId> 15 <version>1.2.12</version> 16 </dependency> 17 <dependency> 18 <groupId>org.springframework</groupId> 19 <artifactId>spring-test</artifactId> 20 <version>5.0.2.RELEASE</version> 21 </dependency> 22 <dependency> 23 <groupId>junit</groupId> 24 <artifactId>junit</artifactId> 25 <version>4.12</version> 26 </dependency> 27 28 <!-- AOP联盟 --> 29 <dependency> 30 <groupId>aopalliance</groupId> 31 <artifactId>aopalliance</artifactId> 32 <version>1.0</version> 33 </dependency> 34 <!-- Spring Aspects --> 35 <dependency> 36 <groupId>org.springframework</groupId> 37 <artifactId>spring-aspects</artifactId> 38 <version>5.0.2.RELEASE</version> 39 </dependency> 40 <!-- aspectj --> 41 <dependency> 42 <groupId>org.aspectj</groupId> 43 <artifactId>aspectjweaver</artifactId> 44 <version>1.8.3</version> 45 </dependency> 46 </dependencies>
-
创建Spring的配置文件,引入具体的AOP的schema约束
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:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xsi:schemaLocation=" 7 http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans.xsd 9 http://www.springframework.org/schema/context 10 http://www.springframework.org/schema/context/spring-context.xsd 11 http://www.springframework.org/schema/aop 12 http://www.springframework.org/schema/aop/spring-aop.xsd"> 13 14 </beans>
- 创建包结构,编写具体的接口和实现类
1 package cn.tx.demo2; 2 public class UserServiceImpl implements UserService { 3 4 @Override 5 public void save() { 6 System.out.println("业务层:保存用户..."); 7 } 8 9 }
- 将目标类配置到Spring中
<bean id="userService" class="cn.tx.demo2.UserServiceImpl"/>
- 自定义切面类
1 package cn.tx.demo2; 2 3 /** 4 * 自定义切面类 = 切入点(表达式) + 通知(增强的代码) 5 */ 6 public class MyXmlAspect { 7 8 /** 9 * 通知 10 */ 11 public void log(){ 12 // 发送手机短信 13 // 发送邮件/记录日志/事务管理 14 15 System.out.println("增强的方法执行了..."); 16 } 17 18 }
-
在配置文件中定义切面类
1 <bean id="myXmlAspect" class="cn.tx.demo2.MyXmlAspect"/>
-
在配置文件中完成aop的配置
1 <!--配置AOP的增强--> 2 <aop:config> 3 <!--配置切面 = 切入点 + 通知组成--> 4 <aop:aspect ref="myXmlAspect"> 5 <!--前置通知:UserServiceImpl的save方法执行前,会增强--> 6 <aop:before method="log" pointcut="execution(public void cn.tx.demo2.UserServiceImpl.save())" /> 7 </aop:aspect> 8 </aop:config>
- 对增强进行测试
package cn.tx.test; import cn.tx.demo2.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext_demo2.xml") public class Demo2 { @Autowired private UserService userService; /** * 测试 */ @Test public void run1(){ userService.save(); } }
- 切入点的表达式格式:
-
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
-
修饰符可以省略不写,不是必须要出现的。
-
返回值类型是不能省略不写的,根据你的方法来编写返回值。可以使用 * 代替。
-
包名例如:com.tx.demo3.BookDaoImpl
- 首先com是不能省略不写的,但是可以使用 * 代替
- 中间的包名可以使用 * 号代替
- 如果想省略中间的包名可以使用 ..
-
类名也可以使用 * 号代替,也有类似的写法:*DaoImpl
-
方法也可以使用 * 号代替
-
参数如果是一个参数可以使用 * 号代替,如果想代表任意参数使用 ..
-
- 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:context="http://www.springframework.org/schema/context" 5 xmlns:aop="http://www.springframework.org/schema/aop" 6 xsi:schemaLocation=" 7 http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans.xsd 9 http://www.springframework.org/schema/context 10 http://www.springframework.org/schema/context/spring-context.xsd 11 http://www.springframework.org/schema/aop 12 http://www.springframework.org/schema/aop/spring-aop.xsd"> 13 14 <!--bean管理--> 15 <bean id="userService" class="cn.tx.demo1.UserServiceImpl" /> 16 17 <!--=================编写AOP配置文件====================--> 18 <!--先配置切面类--> 19 <bean id="myXmlAspect" class="cn.tx.demo1.MyXmlAspect" /> 20 21 <!--配置AOP的增强--> 22 <aop:config> 23 <!--正在配置切面,引入真正切面对象--> 24 <aop:aspect ref="myXmlAspect"> 25 <!--配置的是前置通知:目标对象方法执行前,先增强。method="切面类中通知的方法" pointcut="切入点的表达式"--> 26 <!-- 27 切入点的表达式 28 execution() 写法是固定的 29 public 可以省略不写的 30 方法返回值 void int String * 通用的写法 31 包名 * 通用的写法 32 类名 * 推荐的写法 *ServiceImpl 例如:UserServiceImpl DeptServiceImpl 33 方法名称 * 推荐写法:save* 34 方法参数列表 .. == Object... obj Object类型的可变参数 35 <aop:before method="log" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 36 --> 37 38 <!-- 39 通知类型 40 前置通知:目标对象方法执行前,先增强。 41 <aop:before method="log" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 42 43 最终通知:目标对象方法执行成功或者失败,都会增强。finally 44 <aop:after method="log" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 45 46 后置通知:目标对象方法执行成功,才会增强。 47 <aop:after-returning method="log" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 48 49 异常通知:目标对象方法执行失败,才会增强。 50 <aop:after-throwing method="log" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 51 52 <aop:before method="begin" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 53 <aop:after-returning method="commit" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 54 <aop:after-throwing method="rollback" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 55 <aop:after method="close" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 56 57 环绕通知:自己决定增强的位置。使用了环绕通知,目标对象的方法默认没有执行的,需要自己手动执行目标对象方法。 58 --> 59 60 <aop:around method="logAroud" pointcut="execution(* cn.tx.*.*ServiceImpl.save*(..))" /> 61 62 </aop:aspect> 63 </aop:config> 64 65 </beans>
Spring的AOP技术-注解方式
- 同样创建一个普通的Maven工程,导入坐标,编写接口,同上
- 编写一个切面类,给切面类添加注解 @Aspect,编写增强的方法,使用通知类型注解声明
1 package cn.tx.demo3; 2 3 import org.aspectj.lang.annotation.Aspect; 4 import org.aspectj.lang.annotation.Before; 5 import org.springframework.stereotype.Component; 6 12 @Component // 把该类交给IOC去管理 13 @Aspect // 声明是切面类 == <aop:aspect ref="myXmlAspect"> 14 public class MyAnnoAspect { 15 16 /** 17 * 通知的方法 18 */ 19 // @Before(value = "切入点的表达式") 20 @Before(value = "execution(public * cn.tx.demo3.OrderServiceImpl.save(..))") 21 public void log(){ 22 System.out.println("增强了..."); 23 } 24 25 } 26
-
配置文件中开启自动代理
<aop:aspectj-autoproxy/>
-
测试方法
1 2 package cn.tx.test; 3 4 import cn.tx.demo2.UserService; 5 import cn.tx.demo3.OrderService; 6 import org.junit.Test; 7 import org.junit.runner.RunWith; 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.test.context.ContextConfiguration; 10 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 12 13 @RunWith(SpringJUnit4ClassRunner.class) 14 @ContextConfiguration("classpath:applicationContext_demo3.xml") 15 public class Demo3 { 16 17 @Autowired 18 private OrderService orderService; 19 20 /** 21 * 测试 22 */ 23 @Test 24 public void run1(){ 25 orderService.save(); 26 } 27 28 }
-
@RunWith(SpringJUnit4ClassRunner.class) //在使用所有注释前必须使用@RunWith(SpringJUnit4ClassRunner.class),让测试运行于spring测试环境
- @ContextConfiguration 用来指定加载的Spring配置文件位置,会加载默认配置文件。
-
@ContextConfiguration有两个常用的属性,locations、inheritLocations
locations:可以通过该属性手工指定spring配置文件所在的位置,可以指定一个或多个spring的配置文件,要使用【,】隔开。 例如:@ContextConfiguration(locations={“aa/aa.xml”,” aa/bb.xml”})
inheritLocations:是否要继承父测试用例中的Spring 配置文件,默认是true。
-
- 拓展注解解析:
- @DirtiesContext(classMode = ClassMode.AFTER_CLASS): //@DirtiesContext 在测试方法上出现这个注解时,表明底层Spring容器在该方法的执行中被“污染”,从而必须在方法执行结束后重新创建(无论该测试是否通过)。
- @TransactionConfiguration( transactionManager = "transactionManager" , defaultRollback = false)
@TransactionConfiguration为配置事物性测试定义了类级别的元数据。PlatformTransactionManager默认的实例叫transactionManager,如果需要的PlatformTransactionManager不 是“transactionManager”的话,那么可以显示配置驱动事物的PlatformTransactionManager的bean的名字。此外,可以将defaultRollback标志改为false,表示不回滚。通常, @TransactionConfiguration与@ContextConfiguration搭配使用。
@ContextConfiguration
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=false)】
-
通知类型注解
1 @Before -- 前置通知 2 3 @AfterReturing -- 后置通知 4 5 @Around -- 环绕通知(目标对象方法默认不执行的,需要手动执行) 6 7 @After -- 最终通知 8 9 @AfterThrowing -- 异常抛出通知
-
纯注解的方式
package cn.tx.demo3; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; @Configuration // 配置类 @ComponentScan(value = "cn.tx.demo3") // 扫描包 @EnableAspectJAutoProxy // 开启自动代理 == <aop:aspectj-autoproxy /> public class SpringConfig { }