Spring2
AOP
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
OOP(Object Oriented Programming)面向对象编程
作用:在不惊动原始设计的基础上为其进行功能增强。简单的说就是在不改变方法源代码的基础上对方法进行功能增强。
Spring理念:无入侵式/无侵入式
连接点(JoinPoint):正在执行的方法,例如:update()、delete()、select()等都是连接点。
切入点(Pointcut):进行功能增强了的方法,例如:update()、delete()方法,select()方法没有被增强所以不是切入点,但是是连接点。
在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法。
一个具体方法:com.test.dao包下的BookDao接口中的无形参无返回值的save方法。
匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法。
通知(Advice):在切入点前后执行的操作,也就是增强的共性功能。
在SpringAOP中,功能最终以方法的形式呈现。
通知类:通知方法所在的类叫做通知类。
切面(Aspect):描述通知与切入点的对应关系,也就是哪些通知方法对应哪些切入点方法。
1 <dependencies> 2 <!--spring核心依赖,会将spring-aop传递进来--> 3 <dependency> 4 <groupId>org.springframework</groupId> 5 <artifactId>spring-context</artifactId> 6 <version>5.2.10.RELEASE</version> 7 </dependency> 8 <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法--> 9 <dependency> 10 <groupId>org.aspectj</groupId> 11 <artifactId>aspectjweaver</artifactId> 12 <version>1.9.4</version> 13 </dependency> 14 </dependencies>
1 public interface BookDao { 2 public void save(); 3 public void update(); 4 } 5 6 @Repository 7 public class BookDaoImpl implements BookDao { 8 public void save() { 9 System.out.println(System.currentTimeMillis()); 10 System.out.println("book dao save ..."); 11 } 12 public void update(){ 13 System.out.println("book dao update ..."); 14 } 15 }
1 //通知类必须配置成Spring管理的bean 2 @Component 3 public class MyAdvice { 4 public void method(){ 5 System.out.println(System.currentTimeMillis()); 6 } 7 }
1 //通知类必须配置成Spring管理的bean 2 @Component 3 //设置当前类为切面类类 4 @Aspect 5 public class MyAdvice { 6 //设置切入点,@Pointcut注解要求配置在方法上方 7 @Pointcut("execution(void com.test.dao.BookDao.update())") 8 private void pt(){} 9 10 //设置在切入点pt()的前面运行当前操作(前置通知) 11 @Before("pt()") 12 public void method(){ 13 System.out.println(System.currentTimeMillis()); 14 } 15 }
说明:切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际逻辑
1 @Configuration 2 @ComponentScan("com.test") 3 //开启注解开发AOP功能 4 @EnableAspectJAutoProxy 5 public class SpringConfig { 6 }
AOP工作流程【理解】
1. Spring容器启动
2. 读取所有切面配置中的切入点
3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点
匹配失败,创建原始对象
匹配成功,创建原始对象(目标对象)的代理对象
4. 获取bean执行方法
获取的bean是原始对象时,调用方法并执行,完成操作
获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
目标对象(Target):被代理的对象,也叫原始对象,该对象中的方法没有任何功能增强。
代理对象(Proxy):代理后生成的对象,由Spring帮我们创建代理对象。
切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
描述方式一:执行com.test.dao包下的BookDao接口中的无参数update方法
execution(void com.test.dao.BookDao.update())
描述方式二:执行com.test.dao.impl包下的BookDaoImpl类中的无参数update方法
execution(void com.test.dao.impl.BookDaoImpl.update())
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
execution(public User com.test.service.UserService.findById(int))
动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
访问修饰符:public,private等,可以省略
返回值:写返回值类型
包名
类/接口名
方法名
参数:直接写参数的类型,多个类型用逗号隔开
异常名:方法定义中抛出指定异常,可以省略
通配符
可以使用通配符描述切入点,快速描述。
*:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
匹配com.test包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法
execution(public * com.test.*.UserService.find*(*))
..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
execution(public User com..UserService.findById(..))
+:专用于匹配子类类型
execution(* *..*Service+.*(..))
AOP通知类型【重点】
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置。
AOP通知共分为5种类型
前置通知:在切入点方法执行之前执行
后置通知:在切入点方法执行之后执行,无论切入点方法内部是否出现异常,后置通知都会执行。
环绕通知(重点):手动调用切入点方法并对其进行增强的通知方式。
返回后通知(了解):在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行。
抛出异常后通知(了解):在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行。
AOP通知详解
前置通知
名称:@Before
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行
范例:
1 @Before("pt()") 2 public void before() { 3 System.out.println("before advice ..."); 4 }
后置通知
名称:@After
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行
范例:
1 @After("pt()") 2 public void after() { 3 System.out.println("after advice ..."); 4 }
环绕通知
名称:@Around(重点,常用)
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行
范例:
1 @Around("pt()") 2 public Object around(ProceedingJoinPoint pjp) throws Throwable { 3 System.out.println("around before advice ..."); 4 Object ret = pjp.proceed(); 5 System.out.println("around after advice ..."); 6 return ret; 7 }
注:
1. 环绕通知方法形参必须是ProceedingJoinPoint,表示正在执行的连接点,使用该对象的proceed()方法表示对原始对象方法进行调用,返回值为原始对象方法的返回值。
2. 环绕通知方法的返回值建议写成Object类型,用于将原始对象方法的返回值进行返回,哪里使用代理对象就返回到哪里。
返回后通知
名称:@AfterReturning(了解)
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行
范例:
1 @AfterReturning("pt()") 2 public void afterReturning() { 3 System.out.println("afterReturning advice ..."); 4 }
名称:@AfterThrowing(了解)
作用:设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行
范例:
1 @AfterThrowing("pt()") 2 public void afterThrowing() { 3 System.out.println("afterThrowing advice ..."); 4 }
环绕通知获取接口/类全限定名、方法名
1 @Component 2 @Aspect 3 public class ProjectAdvice { 4 //匹配业务层的所有方法 5 @Pointcut("execution(* com.test.service.*Service.*(..))") 6 private void servicePt(){} 7 8 //设置环绕通知,在原始操作的运行前后记录执行时间 9 @Around("ProjectAdvice.servicePt()") //本类类名可以省略不写 10 public void runSpeed(ProceedingJoinPoint pjp) throws Throwable { 11 //获取执行的签名对象 12 Signature signature = pjp.getSignature(); 13 //获取接口/类全限定名 14 String className = signature.getDeclaringTypeName(); 15 //获取方法名 16 String methodName = signature.getName(); 17 //记录开始时间 18 long start = System.currentTimeMillis(); 19 //执行万次操作 20 for (int i = 0; i < 10000; i++) { 21 pjp.proceed(); 22 } 23 //记录结束时间 24 long end = System.currentTimeMillis(); 25 //打印执行结果 26 System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms"); 27 } 28 }
AOP切入点数据获取
1 @Before("pt()") 2 public void before(JoinPoint jp) { 3 Object[] args = jp.getArgs(); //获取连接点方法的参数们 4 System.out.println(Arrays.toString(args)); 5 }
ProceedJointPoint是JoinPoint的子类(适用于环绕通知)
1 @Around("pt()") 2 public Object around(ProceedingJoinPoint pjp) throws Throwable { 3 Object[] args = pjp.getArgs(); //获取连接点方法的参数们 4 System.out.println(Arrays.toString(args)); 5 args[0]=666; 6 //Object ret = pjp.proceed(); 7 Object ret = pjp.proceed(args); //可通过该方式对数据进行处理再返回 8 return ret; 9 }
1 @AfterReturning(value = "pt()",returning = "ret") 2 public void afterReturning(Object ret) { //变量名要和returning="ret"的属性值一致 3 System.out.println("afterReturning advice ..."+ret); 4 }
注:如参数JoinPoint与返回值ret都存在,JoinPoint一定要在前,否则报错。如存在参数JoinPoint或ProceedingJoinPoint必须在第一位。
1 @AfterReturning(value = "pt()",returning = "ret") 2 public void afterReturning(JoinPoint jp,Object ret) { 3 System.out.println("afterReturning advice ..."+ret); 4 }
环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
1 @Around("pt()") 2 public Object around(ProceedingJoinPoint pjp) throws Throwable { 3 // 手动调用连接点方法,返回值就是连接点方法的返回值 4 Object ret = pjp.proceed(); 5 return ret; 6 }
获取异常
抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
1 @AfterThrowing(value = "pt()",throwing = "t") 2 public void afterThrowing(Throwable t) {//变量名要和throwing = "t"的属性值一致 3 System.out.println("afterThrowing advice ..."+ t); 4 }
环绕通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象
1 @Around("pt()") 2 public Object around(ProceedingJoinPoint pjp) { 3 Object ret = null; 4 //此处需要try...catch处理,catch中捕获到的异常就是连接点方法中抛出的异常 5 try { 6 ret = pjp.proceed(); 7 } catch (Throwable t) { 8 t.printStackTrace(); 9 } 10 return ret; 11 }
事务作用:在数据层保障一系列的数据库操作同成功同失败。
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败。
1 public interface AccountDao { 2 @Update("update tbl_account set money = money + #{money} where name = #{name}") 3 void inMoney(@Param("name") String name, @Param("money") Double money); 4 5 @Update("update tbl_account set money = money - #{money} where name = #{name}") 6 void outMoney(@Param("name") String name, @Param("money") Double money); 7 } 8 9 public interface AccountService { 10 /** 11 * 转账操作 12 * @param out 传出方 13 * @param in 转入方 14 * @param money 金额 15 */ 16 public void transfer(String out,String in ,Double money) ; 17 } 18 19 @Service 20 public class AccountServiceImpl implements AccountService { 21 @Autowired 22 private AccountDao accountDao; 23 24 public void transfer(String out,String in ,Double money) { 25 accountDao.outMoney(out,money); 26 int i = 1/0; 27 accountDao.inMoney(in,money); 28 } 29 }
1 public interface AccountService { 2 //配置当前接口方法具有事务 3 @Transactional 4 public void transfer(String out,String in ,Double money) ; 5 }
注:
1. Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合。
2. 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务。
1 //配置事务管理器,mybatis使用的是jdbc事务 2 @Bean 3 public PlatformTransactionManager transactionManager(DataSource dataSource){ 4 DataSourceTransactionManager dtm = new DataSourceTransactionManager(); 5 dtm.setDataSource(dataSource); 6 return dtm; 7 }
注:
1. 事务管理器要根据实现技术进行选择
2. MyBatis框架使用的是JDBC事务
1 @Configuration 2 @ComponentScan("com.test") 3 @PropertySource("classpath:jdbc.properties") 4 @Import({JdbcConfig.class,MybatisConfig.class}) 5 //开启注解式事务驱动 6 @EnableTransactionManagement 7 public class SpringConfig { 8 }
1 @RunWith(SpringJUnit4ClassRunner.class) 2 @ContextConfiguration(classes = SpringConfig.class) 3 public class AccountServiceTest { 4 @Autowired 5 private AccountService accountService; 6 7 @Test 8 public void testTransfer() throws IOException { 9 accountService.transfer("Tom","Jerry",100D); 10 } 11 }
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法
1 public interface AccountService{ 2 @Transactional(readOnly = true,timeout = -1) 3 public void tranfer(String out,String in,Double money) throws IOException; 4 }
说明:对于RuntimeException类型异常或者Error错误,Spring事务能够进行回滚操作。但是对于编译器异常,Spring事务是不进行回滚的,所以需要使用rollbackFor来设置要回滚的异常。
1 public interface AccountService{ 2 @Transactional(rollbackFor = {IOException.class}) 3 public void tranfer(String out,String in,Double money) throws IOException; 4 }
事务传播行为
事务传播行为:事务协调员对事务管理员所携带事务的处理态度。
在业务层接口上添加Spring事务,设置事务传播行为REQUIRES_NEW (需要新事务):
1 public interface LogService { 2 //propagation设置事务属性:传播行为设置为当前操作需要新事务 3 @Transactional(propagation = Propagation.REQUIRES_NEW) 4 void log(String out, String in, Double money); 5 }