Software_programming_Java_framework_Spring
2019-10-09
Spring
2019-10-09
reference 《Spring in action》
Spring Bean Management
Spring Expression Language, SpEL
- 使用 bean 的 ID 来引用 bean,
- 调用方法和访问对象的属性
- 对值进行算数,关系和逻辑运算
- 正则表达式匹配
- 集合操作
Spring MVC 应用中使用 Thymeleaf 模板作为视图的话,这些模板可用SpEL 表达式引用模型数据。
#{ ... }
#{T(System).currentTimeMillis() } T() 表达式会将 java.lang.System 视为 Java 中对应的类型,
T( java.lang.Math) , 结果会是一个 Class 对象,需要的话可以装配到一个Class类型的 bean 属性中。
将 PI 值装配到 bean属性中
T(java.lang.Math).PI
T() 能够访问目标类型的静态方法和常量,
#{beanIDproperty.attribute} SpEL 表达式引用 bean的属性,该表达式会计算 beanIDproperty 的 bean的 attribute 的属性
#{systemProperties['disc.title'] } 通过 systemProperties 对象引用系统属性。
1. 通过组件扫描创建bean的话,在注入属性和构造器参数时,可以使用 @Value注解,类似与属性占位符,但我们使用 SpEL。

2. XML 配置中, 将 SpEL 传入 <property> 或 <constructor-arg> 的 value 属性中,或将其作为 p- 命名红箭或 c- 命名空间条目的值。
SpEL 可以调用 bean 上的方法,以及相应的 Java 方法,
为避免NullPointerException, 可以使用类型安全的运算符:
#{ artistSelector.selectArtist()?.toUpperCase() }
" ?. " 符号,能够在访问它右边的内容前,确保对应的元素不是 null, 若返回为null, 则不会调用 toUpperCase() 方法。表达式的返回值会是 null.
SpEL 与集合和数组相关操作。
按照索引获取元素 []
查询运算符 ( .?[ ] ) 对集合进行过滤,得到集合的一个子集。
. ^[ ] 和 .$ [ ] 用在集合中查询第一个匹配项和最后一个 匹配项
.! [ ] 投影运算符, 从集合的每个成员中选择特定的属性放到另外的一个集合中。
保持 SpEL 简洁, 毕竟只是 String 类型的值。 测试复杂性。
===============================================================================================================
2019-10-10
AoP
编译期注入
运行时 代理模式(Spring ) ,只能在方法上标记切点,无法在 构造器和属性字段上
Spring 会是延迟实例化 bean.
注解其表达式:带限定范围

Spring 使用 AspectJ注解来声明通知方法
| anotation | advise |
| @After | 在目标方法返回或抛出异常后调用 |
| @AfterReturning | 在目标方法返回后调用 |
| @AfterThrowing | 在目标方法抛出异常后调用 |
| @Around | 将目标方法封装起来 |
| @Before | 在目标方法调用之前执行 |
实践:
自定义切面
package framework.Spring.AOP; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; @Aspect public class Audience { @Before("execution(**concert.Performance.perofrm(..))") public void silenceCellPhones(){ System.out.println("Silencing cell phones."); } @Before("execution(**concert.Performance.perform(..))") public void takeSeats(){ System.out.println("Taking seats."); } @AfterReturning("execution(**concert.Performance.perform(..))") public void applause(){ System.out.println("CLAP CLAP CLAP !"); } @AfterThrowing("execution(**concert.Performance.perform(..))") public void demandRefund(){ System.out.println("Demanding a refund."); } }
切点表达式 重复了四次 (”execution(**concert.Performance.perform(..)"))
@Pointercut 注解 在一个 @AspectJ 切面内定义可重用的切点。

note : performancePct() 方法的实际内容并不重要,应该是空的,只是一个标识,供@Pointcut 注解依附
除了注解和没有实际操作的 performancePct() ,Audience 类依然是一个 POJO,能够像使用其他的Java类那样
调用它的方法,它的方法也能够独立地进行单元测试,只是通过注解表明作为切面使用。
它可以装配为 Spring 中的 bean:
@Bean
public Audience auditence(){
return new Audience();
}
使用 JavaConfig 的时候,在配置类的类级别上通过使用 EnableAspectJ-AutoProxy 注解启用自动代理功能:

AspectJ 自动代理会为使用@Aspect注解的 bean 创建一个代理,这个代理会围绕着所有该切面的切点所匹配的
bean, 这种情况下,会为 Concertbean 创建一个代理,Audience类中的通知方法将会在 perform() 调用前后执行。
本质上是依然是 Spring 基于代理的切面。
环绕通知 实际上就是在 一个通知方法中同时编写前置通知和后置通知。
1 package framework.Spring.AOP; 2 3 import org.aspectj.lang.ProceedingJoinPoint; 4 import org.aspectj.lang.annotation.*; 5 6 @Aspect 7 public class Audience { 8 9 @Pointcut("execution(**concert.Performance.perform(..))") 10 public void performance(){} 11 12 @Around("performance()") 13 public void watchPerformance(ProceedingJoinPoint jp){ 14 try{ 15 System.out.println("Silence cell phones."); 16 System.out.println("Taking seats."); 17 jp.proceed(); 18 System.out.println("CLAP CLAP CLAP !"); 19 }catch (Throwable e){ 20 System.out.println("Demanding a refound"); 21 } 22 } 23 }
注意 ProceedingJoinPoint 参数, 该对象是必须的,要在通知中通过它来调用被通知的方法。通知方法中可以
做任何时请,当要将控制权交给被通知的方法时,需要调用 ProceedingJoinPoint的 Proceed() 方法。
注意别忘记调用 proceed() 方法。不调用的后果是,阻塞对被通知方法的调用。更多情况下时希望在某个点上执行被通知的方法。
可以不调用 proceed() 方法,从而阻塞对被通知方法的访问,与之类似,可以在通知中对它进行多次调用,
这样做的一个场景就是实现重试逻辑,在被通知方法失败后,进行重复尝试。


1 package framework.Spring.AOP.concert; 2 3 import org.aspectj.lang.annotation.Aspect; 4 import org.aspectj.lang.annotation.Before; 5 import org.aspectj.lang.annotation.Pointcut; 6 7 import java.util.HashMap; 8 import java.util.Map; 9 10 @Aspect 11 public class TrackCounter { 12 13 private Map<Integer,Integer> trackCounts = 14 new HashMap<>(); 15 16 @Pointcut( 17 "execution(* framework.Spring.IoC.springActiveConfig.BlankDisc.playTrack(int))" + 18 "&& args(trackNumber))") 19 20 public void trackPlayed(int trackNumber){} 21 22 @Before("trackPlayed(trackNumber)") 23 public void countTrack(int trackNumber){ 24 int currentCount = getPlayCount(trackNumber); 25 trackCounts.put(trackNumber, currentCount + 1); 26 } 27 28 private int getPlayCount(int trackNumber) { 29 return trackCounts.containsKey(trackNumber) 30 ? trackCounts.get(trackNumber) : 0; 31 } 32 }
表明传递给 playTrack() 方法的 int 类型参数也会传递到通知中去。参数的名称 trackNumber 也与切点方法签名中的参数相匹配。
这个参数会传递到 通知方法中去, 此例子中通过 @Before 注解和命名切点 trackPlayed(trackNumber) 定义。
切点定义中的参数与切点方法中的参数名称时一样的。这样就完成了从命名切点到通知方法的参数转移。
可在 Spring 配置中将 BlankDisc 和 TrackCounter 定义为 bean, 并启用 AspectJ 自动代理。
===========================================================================
为现有的对象添加功能, Ruby 和 Groovy 有开放类的理念,不用直接修改对象或类的定义就能够为对象或类增加新的方法。
但Java 不是动态语言,一旦类编译完成了,就很难再为该类添加新的功能。
但 AOP 的 引入, 切面可为 Spring bean 添加新的方法。

注意:当引入接口的方法被调用时,代理会把此调用委托给实现了新接口的某个其他对象。
实际上,一个bean的实现被分拆到了多个类中。

使用 AOP 的引入功能,可以不必在设计上妥协或者侵入性地改变现有的实现。
创建一个新的切面。

和其他切面一样,需要在 Spring应用中将 EncoreableIntroducer 声明为一个bean
Spring 的自动代理机制会获取到它的声明,当Spring 发现了一个bean 使用@Aspect 注解时,Spring就会创建一个代理,然后将调用委托给被代理的bean或被引入的实现。这取决于调用的方法属于被代理的bean 还是属于被引入的接口。
注意:面向注解的切面声明有一个明显的劣势: 必须能够为通知类添加注解。意为 要有源码。
若没有源码,或者不想将 AspectJ 注解放到你的代码之中,Spring 为切面提供了 在 Spring XML 配置文件中声明切面。
构造器切点非常方便,不像某些其他面向对象语言中的构造器,Java构造器不同于其他的正常方法。使得Spring基于代理的 AOP 无法把通知应用于对象的创建过程。
AspectJ切面与 Spring 是相互独立的。 在应用 Aspect 切面时几乎不会涉及到 Spring。
精心设计且有意义的切面很可能依赖其他类来完成它们的工作。
如果在执行通知时,切面依赖于一个或多个类,可以在切面内部实例化这些协作的对象,但更好的方式是,
借助 Spring 依赖注入把 bean 装配进 AspectJ 切面中。

浙公网安备 33010602011771号