Spring学习笔记
2023-01-10 14:40:11 21 举报Spring学习笔记
Spring
SpringBoot
模版推荐
作者其他创作
大纲/内容
第一部分Spring核心
装配bean
Spring配置的可选方案
XML显示配置
具体怎么配置省略
导入和混合配置
在JavaConfig中引用XML配置
使用@ImportResource注解
javaconfig两个配置类组合
方法1 在AConfig中用@Import注解导入BConfig
方法2创建一个更高级别的CConfig,在CConfig这个类中使用@Import<br>将两个配置类组合在一起 <br>@Import({AConfig.class,BConfig.class})
在XML配置中引用JavaConfig
可以用<import>元素将Java配置(Java配置作为<bean>)导入到XML配置
在Java中显示配置
步骤一创建配置类
创建JavaConfig类的关键在于为其添加@Configuration注<br>解,<font color="#ff0000">@Configuration注解表明这个类是一个配置类</font>,该类应该包含在<br>Spring应用上下文中如何创建bean的细节。
步骤二声明简单的bean
<font color="#ff0000">要在JavaConfig中声明bean</font>,我们需要编写一个方法,这个方法会创建<br>所需类型的实例,然后给这个方法添加@Bean注解。<br>@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为<br>Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻<br>辑。<br>默认情况下,bean的ID与带有@Bean注解的方法名是一样的。<br>那么可以重命名该方法,也可以通过name属性指定一个不同的<br>名字:<br>
步骤三借助Java Config实现注入<br>引用创建bean的方法<br>
cdPlayer()方法像sgtPeppers()方法一样,同样使用了@Bean注解,这表<br>明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。所<br>创建的bean ID为cdPlayer,与方法的名字相同。
通过调用方法来引用bean的方式有点令人困惑。其实还有一<br>种理解起来更为简单的方式:
如果你想通过<br>Setter方法注入CompactDisc的话,那么代码看起来应该是这样的:<br>
隐式的bean发现机智和自动装配
组件扫描(component scanning):<br>Spring会自动发现应用上下文中<br>所创建的bean。
@ComponentScan注解启用了组件扫描<br>创建XXconfig类并且使用注解@ComponentScan<br>和注解@Configration
如果不为@ComponentScan设置任何属性。<br>按照默认规则,它会以配置类所在的包作为基础包(base package)来<br>扫描组件
@ComponentScan的value属性中指明包的名称
在@ComponentScan的value属性中指明包的名称
@ComponentScan还提供了另外一<br>种方法,那就是将其指定为包中所包含的类或接口
@Component注解。<br>这个简单的注解表明该<br>类会作为组件类,并告知Spring要为这个类创建bean。<br>有这个注解的类可以被Spring发现
为组件扫描的bean命名<br>如果想为这个bean设置不同的ID,你所要做的就是将期望的ID作为值传<br>递给@Component注解。@Component("car")<br>
还有另外一种为bean命名的方式,使用Java依赖注入规范<br>中所提供的@Named注解来为bean设置ID@Named("car")<br>
自动装配(autowiring):<br>Spring自动满足bean之间的依赖。
@Autowired
@Autowired注解可以用在类的<br>任何方法上<br>包括构造器和属性的Setter方法
假如有且只有一个bean匹配依赖需求的话,那么这<br>个bean将会被装配进来。<br>如果没有匹配的bean,那么在应用上下文创建的时候,Spring会抛出一<br>个异常。为了避免异常的出现,你可以将@Autowired的required属性设<br>置为false:<br>将required属性设置为false时,Spring会尝试执行自动装配,但是如果<br>没有匹配的bean的话,Spring将会让这个bean处于未装配的状态。但<br>是,把required属性设置为false时,你需要谨慎对待。如果在你的代<br>码中没有进行null检查的话,这个处于未装配状态的属性有可能会出现<br>NullPointerException。
@Autowired是Spring特有的注解。如果你不愿意在代码中到处使用Spring<br>的特定注解来完成自动装配任务的话,那么你可以考虑将其替换<br>为@Inject:<br>@Inject注解来源于Java依赖注入规范,该规范同时还为我们定义了<br>@Named注解。在自动装配中,Spring同时支持@Inject和@Autowired。尽<br>管@Inject和@Autowired之间有着一些细微的差别,但是在大多数场景<br>下,它们都是可以互相替换的。<br>
@Resource
@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按byName自动注入罢了。@Resource有两个属性是比较重要的,分是name和type,Spring将@Resource注解的name属性解析为bean的名字。<br>
二者具体区别:<br>1、@Autowired与@Resource都可以用来装配bean.都可以写在字段上,或写在setter方法上。<br>2、@Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false),如果我们想使用名称装配可以结合@Qualifier注解进行使用。<br>3、@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
推荐排名:自动化配置>java配置>XML配置
高级装配
Spring profile<br>(Spring4.0以前)
profile bean 功能可以<font color="#ff0000">使同一个部署单元能够适应于所有的环境</font>,没有必要进行重新构建
在配置类中使用@Profile注解指定某个bean属于哪一个profile。<br>在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。<br>当对应的profile没有激活时,带有@bean注解的方法会被忽略<br>
从Spring3.2开始,可以在方法级别上使用@Profile注解,与@Bean注解一同使<br>用,能将这两个bean的声明放到同一个配置类
激活profile:
步骤一:需要依赖两个独立的属<br>性:<font color="#ff0000">spring.profiles.active和spring.profiles.default</font>
如果设置了<br>spring.profiles.active属性的话,那么它的值就会用来确定哪个<br>profile是激活的。但如果没有设置spring.profiles.active属性的话,<br>那Spring将会查找spring.profiles.default的值。如果<br>spring.profiles.active和spring.profiles.default均没有设置的话,<br>那就没有激活的profile,因此只会创建那些没有定义在profile中的<br>bean
当应用程序部署到QA、生产或其他环境之中时,负责部署的人根据情<br>况使用系统属性、环境变量或JNDI设置spring.profiles.active即可。<br>当设置spring.profiles.active以后,至于spring.profiles.default置<br>成什么值就已经无所谓了;系统会优先使用spring.profiles.active中<br>所设置的profile。
你可能已经注意到了,在spring.profiles.active和<br>spring.profiles.default中,profile使用的都是复数形式。这意味着你<br>可以同时激活多个profile,这可以通过列出多个profile名称,并以逗号<br>分隔来实现。当然,同时启用dev和prod profile可能也没有太大的意<br>义,不过你可以同时设置多个彼此不相关的profile。
步骤二:怎么设置这两个属性
作为DispatcherServlet的初始化参数;<br>作为Web应用的上下文参数;<br>作为JNDI条目;<br>作为环境变量;<br>作为JVM的系统属性;<br>在集成测试类上,使用@ActiveProfiles注解设置。
Spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时<br>要激活哪个profile。在集成测试时,通常想要激活的是开发环境的<br>profile。例如,下面的测试类片段展现了使用@ActiveProfiles激活dev<br>profile:
条件化的bean声明<br>(Springt 4开始)
使用@Conditional注解条件化地配置
设置给@Conditional的类可以是任意实现了Condition接口的类型。可以<br>看出来,这个接口实现起来很简单直接,只需提供matches()方法的实<br>现即可。如果matches()方法返回true,那么就会创建带有@Conditional<br>注解的bean。如果matches()方法返回false,将不会创建这些bean。
举例:
在上面的程序清单中,matches()方法很简单但功能强大。它通过给定<br>的ConditionContext对象进而得到Environment对象,并使用这个对象检<br>查环境中是否存在名为magic的环境属性。在本例中,属性的值是什么<br>无所谓,只要属性存在即可满足要求。如果满足这个条件的<br>话,matches()方法就会返回true。所带来的结果就是条件能够得到满<br>足,所有@Conditional注解上引用MagicExistsCondition的bean都会被<br>创建。<br>话说回来,如果这个属性不存在的话,就无法满足条件,matches()方<br>法会返回false,这些bean都不会被创建。
ConditionContext
MagicExistsCondition中只是使用了ConditionContext得到的<br>Environment,但Condition实现的考量因素可能会比这更多。matches()<br>方法会得到ConditionContext和AnnotatedTypeMetadata对象用来做出决<br>策。ConditionContext是一个接口,大致如下所示:
借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;<br>借助getBeanFactory()返回的ConfigurableListableBeanFactory检<br>查bean是否存在,甚至探查bean的属性;<br>借助getEnvironment()返回的Environment检查环境变量是否存在以<br>及它的值是什么;<br>读取并探查getResourceLoader()返回的ResourceLoader所加载的资<br>源;<br>借助getClassLoader()返回的ClassLoader加载并检查类是否存在。
AnnotatedTypeMetadata
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有<br>什么其他的注解。像ConditionContext一样,AnnotatedTypeMetadata也<br>是一个接口。它如下所示:
借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是不是还<br>有其他特定的注解。借助其他的那些方法,我们能够检查@Bean注解的<br>方法上其他注解的属性。
Spring4中@Profilede注解重构<br>其基于@Conditional和Condition实现<br>
@Profile注解如下所示:
ProfileCondition检查某个bean profile是否可用<br>我们可以看到,ProfileCondition通过AnnotatedTypeMetadata得到了用<br>于@Profile注解的所有属性。借助该信息,它会明确地检查value属<br>性,该属性包含了bean的profile名称。然后,它根据通过<br>ConditionContext得到的Environment来检查[借助acceptsProfiles()方<br>法]该profile是否处于激活状态。<br>
自动装配与歧义性
当自动装配出现歧义,有多个bean符合条件时,<br>Spring将会抛出NoUniqueBeanDefinitionException
解决方式:
方式一:使用@Primary标识首选的bean
1.@Primary能够与@Component组合用在组件扫描的bean<br>上,在编写类时直接加上@Primary
2.通过Java配置显示声明<br>在XXConfig文件中,与@Bean注解一同使用
方式二:使用@Qualifier注解限定自动装配的ban<br><br>
1.<font color="#ff0000">基于默认的bean ID作为限定符</font><br>为@Qualifier注解所设置的参数就是<br>想要注入的bean的ID()<br>可以与@Autowired和@Inject协同使用<br>
2.创建自定义的限定符<br>
1.@Qualifier与@Component组合使用,给类设置自己的限定符
2.当通过Java显示定义bean时,@Qualifier可以与@Bean注解一起使用
3.使用自定义的限定符注解:
创建一个注解,使用@Qualifier注解来标注。<br>这样我们就拥有了一个新的@Cold注解,它具有<br>@Qualifier注解的特性,它本身就成为了限定符注解
Java不允许在同一个条目上重复出现相同类型的多个注解<br>但如果声明自定义限定符注解,我们同时可以使用多个限定符<br>
与此同时,相对于使用原始的@Qualifier<br>并借助String类型来指定限定符,自定义的注解也更为类型安全。<br>
bean的作用域
作用域类型
1.单例(Singleton):在整个应用中,只创建bean的一个实例。<br><font color="#ff0000"></font>
2.原型(Prototype):每次注入或者通过Spring应用上下文获取的时<br>候,都会创建一个新的bean实例。
3.会话(Session):在Web应用中,为每个会话创建一个bean实例。<br>
4.请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
改变作用域<br>
单例Singleton->原型Prototype
1.使用@Scope注解,它可以与@Component<br>或@Bean一起使用。<br>这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了<br>原型作用域。你当然也可以使用@Scope("prototype"),但是使<br>用SCOPE_PROTOTYPE常量更加安全并且不易出错。<br>
2.想在Java配置中将Notepad声明为原型bean,那么可以组合使<br>用@Scope和@Bean来指定所需的作用域
使用会话和请求作用域
指定会话作用域,我们可以使用@Scope注解<br>它的使用方式与指定原型作用域是相同<br>
这里,我们将value设置成了WebApplicationContext中的SCOPE_SESSION<br>常量(它的值是session)。这会告诉Spring为Web应用中的每个会话创<br>建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对<br>于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean<br>实际上相当于单例的。
<font color="#ff0000">proxyMode属性<br></font>@Scope同时还有一个proxyMode属性,它被设置成了<br><font color="#ff0000">ScopedProxyMode.INTERFACES</font>。这表<br>明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。<br>
将proxyMode属性设置为<font color="#ff0000">ScopedProxyMode.TARGET_CLASS</font>,<br>以此来表明要以生成目标类扩展的方式创建代理。
请求作用域的bean应该也以作用域代理的方式进行注入
注入外部的值
Spring表达式语言<br>(Spring Expression <br>Language,SpEL)<br>
SpEL拥有很多特性,包括:<br>1.使用bean的ID来引用bean;<br>2.调用方法和访问对象的属性;<br>3.对值进行算术、关系和逻辑运算;<br>4.正则表达式匹配;<br>5.集合操作。
SpEL基础样例
需要了解的第一件事情就是SpEL表达式要放到“#{ ... }”之中,这与属<br>性占位符有些类似,属性占位符需要放到“${ ... }”之中。
它的最终结果是计算表达式的那一刻当前时间的毫秒数。T()表达式会<br>将java.lang.System视为Java中对应的类型,因此可以调用其static修<br>饰的currentTimeMillis()方法。
SpEL表达式也可以引用其他的bean或其他bean的属性。例如,如下的表<br>达式会计算得到ID为sgtPeppers的bean的artist属性:
我们还可以通过systemProperties对象引用系统属性:
在bean装配时使用SpEL表达式
如果通过组件扫描创建bean的话,在注入属性和构造器参数时,我们可<br>以使用@Value注解,这与之前看到的属性占位符非常类似。不过,在这<br>里我们所使用的不是占位符表达式,而是SpEL表达式。例如,下面的<br>样例展现了BlankDisc,它会从系统属性中获取专辑名称和艺术家的名<br>字:
SpEL所支持的基础表达式
表示字面值
SpEL表达式样例所表示的就是浮点值 #{3.14159}
数值还可以使用科学记数法的方式进行表示。如下面的表达式计算得到<br>的值就是98,700:#{9.87E4}<br>
SpEL表达式也可以用来计算String类型的字面值,如 #{'hello'}
最后,字面值true和false的计算结果就是它们对应的Boolean类型的<br>值。例如:#{'false'}
引用bean、属性和方法
通过ID引用其他的bean。例<br>如,你可以使用SpEL将一个bean装配到另外一个bean的属性中,此时要<br>使用bean ID作为SpEL表达式(在本例中,也就是sgtPeppers)<br>#{sgtPeppers}<br>
现在,假设我们想在一个表达式中引用sgtPeppers的artist属性:<br>#{sgtPeppers.artist}<br>表达式主体的第一部分引用了一个ID为sgtPeppers的bean,分割符之后<br>是对artist属性的引用。<br><br>
除了引用bean的属性,我们还可以调用bean上的方法。例如,假设有另<br>外一个bean,它的ID为artistSelector,我们可以在SpEL表达式中按照<br>如下的方式来调用bean的selectArtist()方法:<br>#{sgtPeppers.selectArtist()}<br>
对于被调用方法的返回值来说,我们同样可以调用它的方法。例如,如<br>果selectArtist()方法返回的是一个String,那么可以调<br>用toUpperCase()将整个艺术家的名字改为大写字母形式:<br>#{sgtPeppers.selectArtist().tuUpperCase()}
如果selectArtist()的返回值不是null的话,这没有什么问题。为了避<br>免出现NullPointerException,我们可以使用类型安全的运算符:<br>#{sgtPeppers.selectArtist()?.tuUpperCase()}<br>
与之前只是使用点号(.)来访问toUpperCase()方法不同,<font color="#ff0000">现在我们使<br>用了“?.”运算符。这个运算符能够在访问它右边的内容之前,确保它所<br>对应的元素不是null</font>。所以,如果selectArtist()的返回值是null的<br>话,那么SpEL将不会调用toUpperCase()方法。表达式的返回值会是<br>null。
在表达式中使用类型
如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键<br>的运算符。例如,为了在SpEL中表达Java的Math类,需要按照如下的方<br>式使用T()运算符:T(java.lang.Math)
这里所示的T()运算符的结果会是一个Class对象,代表了<br>java.lang.Math。如果需要的话,我们甚至可以将其装配到一个Class类<br>型的bean属性中。<font color="#ff0000">但是T()运算符的真正价值在于它能够访问目标类型<br>的静态方法和常量。</font>
假如你需要将PI值装配到bean属性中。如下的SpEL就能完成该任<br>务:T(java.lang.Math).PI
与之类似,我们可以调用T()运算符所得到类型的静态方法。我们已经<br>看到了通过T()调用System.currentTimeMillis()。如下的这个样例会计<br>算得到一个0到1之间的随机数:T(java.lang.Math).random()
SpEL运算符
SpEL还提供了三元运算符(ternary),它与Java中的三元运算符非常类<br>似。例如,如下的表达式会判断如果scoreboard.score>1000的话,计算<br>结果为String类型的“Winner!”,否则的话,结果为Loser:
三元运算符的一个常见场景就是检查null值,并用一个默认值来替代<br>null。例如,如下的表达式会判断disc.title的值是不是null,如果<br>是null的话,那么表达式的计算结果就会是“Rattle and Hum”:
计算正则表达式<br>当处理文本时,有时检查文本是否匹配某种模式是非常有用的。SpEL<br>通过matches运算符支持表达式中的模式匹配。matches运算符对String类<br>型的文本(作为左边参数)应用正则表达式(作为右边参<br>数)。matches的运算结果会返回一个Boolean类型的值:如果与正则表<br>达式相匹配,则返回true;否则返回false。
为了进一步解释matches运算符,假设我们想判断一个字符串是否包含<br>有效的邮件地址。在这个场景下,我们可以使用matches运算符,如下所示
计算集合
最简单的事情可能就是引用列表中的一个元素了
“[]”运算符用来从集合或数组中按照索引获取元素,实际上,它还可以<br>从String中获取一个字符。比如:<br>这个表达式引用了String中的第四个(基于零开始)字符,也就是“s”。
SpEL还提供了查询运算符(.?[]),它会用来对集合进行过滤,得到集<br>合的一个子集。作为阐述的样例,假设你希望得到jukebox中artist属性<br>为Aerosmith的所有歌曲。如下的表达式就使用查询运算符得到了<br>Aerosmith的所有歌曲:
SpEL还提供了另外两个查询运算符:“.^[]”和“.$[]”,它们分别用来在<br>集合中查询第一个匹配项和最后一个匹配项。例如,考虑下面的表达<br>式,它会查找列表中第一个artist属性为Aerosmith的歌曲:
最后,SpEL还提供了投影运算符(.![]),它会从集合的每个成员中选<br>择特定的属性放到另外一个集合中。作为样例,假设我们不想要歌曲对<br>象的集合,而是所有歌曲名称的集合。如下的表达式会将title属性投<br>影到一个新的String类型的集合中:
实际上,投影操作可以与其他任意的SpEL运算符一起使用。比如,我<br>们可以使用如下的表达式获得Aerosmith所有歌曲的名称列表:
使用@PropertySource注解和Environment
声明属性源并通过Spring的Environment来检索属性<br>
在本例中,@PropertySource引用了类路径中一个名为app.properties的文<br>件。它大致会如下所示:
getProperty()方法有四个重载的变种形式
面向切面的Spring
面向切面编程的基本原理
什么是面向切面编程
如果要重用通用功能的话,最常见的面向对象技术是继承<br>(inheritance)或委托(delegation)。但是,如果在整个应用中都使用<br>相同的基类,继承往往会导致一个脆弱的对象体系;而使用委托可能需<br>要对委托对象进行复杂的调用。<br>切面提供了取代继承和委托的另一种可选方案,而且在很多场景下更清<br>晰简洁。在使用面向切面编程时,我们仍然在一个地方定义通用功能,<br>但是可以通过声明的方式定义这个功能要以何种方式在何处应用,而无<br>需修改受影响的类。横切关注点可以被模块化为特殊的类,这些类被称<br>为切面(aspect)。这样做有两个好处:首先,现在每个关注点都集中<br>于一个地方,而不是分散到多处代码中;其次,服务模块更简洁,因为<br>它们只包含主要关注点(或核心功能)的代码,而次要关注点的代码被<br>转移到切面中了。
定义AOP术语
通知(advice)
切面也有目标——它必须要完成的工作。在AOP术语中,切面<br>的工作被称为通知。
Spring切面可以应用5种类型的通知:<br>前置通知(Before):在目标方法被调用之前调用通知功能;<br>后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;<br>返回通知(After-returning):在目标方法成功执行之后调用通知;<br>异常通知(After-throwing):在目标方法抛出异常后调用通知;<br>环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
连接点(join point)<br>
我们的应用可能也有数以千计的时机应用通知。这些时机被称为<br>连接点。<font color="#ff0000">连接点是在应用执行过程中能够插入切面的一个点</font>。这个点可<br>以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利<br>用这些点插入到应用的正常流程之中,并添加新的行为。
切点(pointcut)<br>
一个切面并不需要通知应用的所有连接点。切点有助于缩小切<br>面所通知的连接点的范围。
如果说通知定义了切面的“什么”和“何时”的话,那么切点就定义了“何<br>处”。切点的定义会匹配通知所要织入的一个或多个连接点。我们通常<br>使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法<br>名称来指定这些切点。有些AOP框架允许我们创建动态的切点,可以根<br>据运行时的决策(比如方法的参数值)来决定是否应用通知。
切面(Aspect)
切面是通知和切点的结合。通知和切点共同定义了切面的全部内容——<br>它是什么,在何时和何处完成其功能。
引入(Introduction)
引入允许我们向现有的类添加新方法或属性。例如,我们可以创建一<br>个Auditable通知类,该类记录了对象最后一次修改时的状态。这很简<br>单,只需一个方法,setLastModified(Date),和一个实例变量来保存这<br>个状态。然后,这个新方法和实例变量就可以被引入到现有的类中,从<br>而可以在无需修改这些现有的类的情况下,让它们具有新的行为和状<br>态。
织入(Weaving)
织入是把切面应用到目标对象并创建新的代理对象的过程。切面在指定<br>的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以<br>进行织入:
编译期:切面在目标类编译时被织入。这种方式需要特殊的编译<br>器。AspectJ的织入编译器就是以这种方式织入切面的。<br>类加载期:切面在目标类加载到JVM时被织入。这种方式需要特殊<br>的类加载器(ClassLoader),它可以在目标类被引入应用之前增<br>强该目标类的字节码。AspectJ 5的加载时织入(load-time<br>weaving,LTW)就支持以这种方式织入切面。<br>运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入<br>切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring<br>AOP就是以这种方式织入切面的。
Spring 对AOP的支持
基于代理的经典Spring AOP;<br>纯POJO切面;<br>@AspectJ注解驱动的切面;<br>注入式AspectJ切面(适用于Spring各版本)。<br>前三种都是Spring AOP实现的变体,Spring AOP构建在动态代理基础之<br>上,因此,Spring对AOP的支持局限于方法拦截<br>
通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的<br>bean中。如图4.3所示,代理类封装了目标类,并拦截被通知方法的调<br>用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调<br>用目标bean方法之前,会执行切面逻辑。
直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的<br>是ApplicationContext的话,在ApplicationContext从BeanFactory中加<br>载所有bean的时候,Spring才会创建被代理的对象。因为Spring运行时<br>才创建代理对象,所以我们不需要特殊的编译器来织入Spring AOP的切<br>面。
Spring只支持方法级别的连接点
通过切点来选择连接点
Spring借助AspectJ的切点表达式语言来定义Spring切面
arg() 限制连接点匹配参数为指定类型的执行方法<br>@args() 限制连接点匹配参数由指定注解标注的执行方法<br>execution() 用于匹配是连接点的执行方法<br>this() 限制连接点匹配AOP代理的bean引用为指定类型的类<br>target 限制连接点匹配目标对象为指定类型的类<br>@target() 限制连接点匹配特定的执行对象,这些对象对应的类要具有指定类型的注解<br>within() 限制连接点匹配指定的类型<br>@within() 限制连接点匹配指定注解所标注的类型(当使用Spring AOP时,方法定义在由指定的注解所标注的类里)<br>@annotation 限定匹配带有指定注解的连接点<br>
编写切点
1.先定义一个接口
Performance可以代表任何类型的现场表演,如舞台剧、电影或音乐<br>会。假设我们想编写Performance的perform()方法触发的通知。图4.4展<br>现了一个切点表达式,这个表达式能够设置当perform()方法执行时触发<br>通知的调用。
我们使用execution()指示器选择Performance的perform()方法。方法表<br>达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们<br>指定了全限定类名和方法名。对于方法参数列表,我们使用两个点号<br>(..)表明切点要选择任意的perform()方法,无论该方法的入参是什么。
现在假设我们需要配置的切点仅匹配concert包。在此场景下,可以使<br>用within()指示器来限制匹配,如图4.5所示。<br>
请注意我们使用了“&&”操作符把execution()和within()指示器连接在一<br>起形成与(and)关系(切点必须匹配所有的指示器)。类似地,我们<br>可以使用“||”操作符来标识或(or)关系,而使用“!”操作符来标识非<br>(not)操作。<br>因为“&”在XML中有特殊含义,所以在Spring的XML配置里面描述切点<br>时,我们可以使用and来代替“&&”。同样,or和not可以分别用来代<br>替“||”和“!”。
在切点中选择bean
在这里,我们希望在执行Performance的perform()方法时应用通知,但<br>限定bean的ID为woodstock。
在某些场景下,限定切点为指定的bean或许很有意义,但我们还可以使<br>用非操作为除了特定ID以外的其他bean应用通知:
使用注解创建切面
定义切面
Audience类使用@AspectJ注解进行了标注。<font color="#ff0000">该注解表明Audience不仅仅<br>是一个POJO,还是一个切面</font>。Audience类中的方法都使用注解来定义<br>切面的具体行为。
代码
@After 通知方法会在目标方法返回或抛出异常后调用<br>@AfterReturning 通知方法会在目标方法返回后调用<br>@AfterThrowing 通知方法会在目标方法抛出异常后调用<br>@Around 通知方法会将目标方法封装起来<br>@Before 通知方法会在目标方法调用之前执行
通过@Pointcut注解声明频繁使用的切点表达式<br>
在Audience中,performance()方法使用了@Pointcut注解。为@Pointcut<br>注解设置的值是一个切点表达式,就像之前在通知注解上所设置的那<br>样。通过在performance()方法上添加@Pointcut注解,我们实际上扩展<br>了切点表达式语言,这样就可以在任何的切点表达式中使<br>用performance()了,如果不这样做的话,你需要在这些地方使用那个<br>更长的切点表达式。我们现在把所有通知注解中的长表达式都替换成了<br>performance()。<br>performance()方法的实际内容并不重要,在这里它实际上应该是空<br>的。其实该方法本身只是一个标识,供@Pointcut注解依附。
需要注意的是,除了注解和没有实际操作的performance()方<br>法,Audience类依然是一个POJO。我们能够像使用其他的Java类那样调<br>用它的方法,它的方法也能够独立地进行单元测试,这与其他的Java类<br>并没有什么区别。Audience只是一个Java类,只不过它通过注解表明会<br>作为切面使用而已。<br>像其他的Java类一样,它可以装配为Spring中的bean<br>如果你就此止步的话,Audience只会是Spring容器中的一个bean。即便<br>使用了AspectJ注解,但它并不会被视为切面,这些注解不会解析,也不<br>会创建将其转换为切面的代理<br>
在JavaConfig中启用AspectJ注解的自动代理
如果你使用JavaConfig的话,可以在配置类的类级别上通过使<br>用EnableAspectJ-AutoProxy注解启用自动代理功能。程序清单4.3展现<br>了如何在JavaConfig中启用自动代理
创建环绕通知
环绕通知是最为强大的通知类型。它能够让你所编写的逻辑将被通知的<br>目标方法完全包装起来。实际上就像在一个通知方法中同时编写前置通<br>知和后置通知。
子主题
在这里,@Around注解表明watchPerformance()方法会作<br>为performance()切点的环绕通知。在这个通知中,观众在演出之前会<br>将手机调至静音并就坐,演出结束后会鼓掌喝彩。像前面一样,如果演<br>出失败的话,观众会要求退款。<br>可以看到,这个通知所达到的效果与之前的前置通知和后置通知是一样<br>的。但是,现在它们位于同一个方法中,不像之前那样分散在四个不同<br>的通知方法里面。
关于这个新的通知方法,你首先注意到的可能是它接受<br>ProceedingJoinPoint作为参数。这个对象是必须要有的,因为你要在通<br>知中通过它来调用被通知的方法。通知方法中可以做任何的事情,当要<br>将控制权交给被通知的方法时,它需要调用ProceedingJoinPoint的<br>proceed()方法。<br>需要注意的是,别忘记调用proceed()方法。如果不调这个方法的话,<br>那么你的通知实际上会阻塞对被通知方法的调用。有可能这就是你想要<br>的效果,但更多的情况是你希望在某个点上执行被通知的方法。<br>有意思的是,你可以不调用proceed()方法,从而阻塞对被通知方法的<br>访问,与之类似,你也可以在通知中对它进行多次调用。要这样做的一<br>个场景就是实现重试逻辑,也就是在被通知方法失败后,进行重复尝<br>试。
处理通知中的参数
在图4.6中需要关注的是切点表达式中的args(trackNumber)限定符。它<br>表明传递给playTrack()方法的int类型参数也会传递到通知中去。参数<br>的名称trackNumber也与切点方法签名中的参数相匹配。<br>这个参数会传递到通知方法中,这个通知方法是通过@Before注解和命<br>名切点trackPlayed(trackNumber)定义的。切点定义中的参数与切点方<br>法中的参数名称是一样的,这样就完成了从命名切点到通知方法的参数<br>转移。
通过注解引入新功能<br>(引入的AOP)
可以看到,EncoreableIntroducer是一个切面。但是,它与我们之前所<br>创建的切面不同,它并没有提供前置、后置或环绕通知,而是通过<br>@DeclareParents注解,将Encoreable接口引入到Performance bean中。
@DeclareParents注解由三部分组成:<br>1.value属性指定了哪种类型的bean要引入该接口。在本例中,也就<br>是所有实现Performance的类型。(标记符后面的加号表示<br>是Performance的所有子类型,而不是Performance本身。)<br>2.defaultImpl属性指定了为引入功能提供实现的类。在这里,我们<br>指定的是DefaultEncoreable提供实现。<br>3.@DeclareParents注解所标注的静态属性指明了要引入了接口。在这<br>里,我们所引入的是Encoreable接口。
需要将EncoreableIntroducer声明为一个bean
在XML中声明切面
为AspectJ切面注入依赖
第三部分后端中的Spring
Nosql数据库_Redis
连接到Redis
Spring Data Redis为<br>四种Redis客户端实现提供了连接工厂
JedisConnectionFactory<br>JredisConnectionFactory<br>LettuceConnectionFactory<br>SrpConnectionFactory
如何配置JedisConnectionFactory bean
通过默认构造器创建的连接工厂会向localhost上的6379端口创建连接,<br>并且没有密码。如果你的Redis服务器运行在其他的主机或端口上,在<br>创建连接工厂的时候,可以设置这些属性:
所有的Redis连接工厂都具有setHostName()、setPort()和setPassword()<br>方法。这样,它们在配置方面实际上是相同的。
使用RedisTemplate
Spring Data Redis提供了两个模板<br>RedisTemplate<br>StringRedisTemplate<br>
缓存数据
启用对缓存的支持
通过使用@EnableCaching启用注解驱动的缓存
工作方式:<br>创建一个切面(aspect)并触发Spring缓存注解的<br>切点(pointcut)。根据所使用的注解以及缓存的状态,这个切面会从<br>缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值。<br>它们不仅仅启用了注解驱动的缓存,还声明了一个缓存管理器(cache manager)的<br>bean。缓存管理器是Spring缓存抽象的核心,它能够与多个流行的缓存实现进行集成<br>
配置缓存管理器:<br>SimpleCacheManager<br>NoOpCacheManager<br>ConcurrentMapCacheManager<br>CompositeCacheManager<br>EhCacheCacheManager<br>RedisCacheManager(来自于Spring Data Redis项目)<br>GemfireCacheManager(来自于Spring Data GemFire项目)<br>
可以看到,在为Spring的缓存抽象选择缓存管理器时,我们有很多<br>可选方案。具体选择哪一个要取决于想要使用的底层缓存供应商。每一<br>个方案都可以为应用提供不同风格的缓存,其中有一些会比其他的更加<br>适用于生产环境。尽管所做出的选择会影响到数据如何缓存,但是<br>Spring声明缓存的方式上并没有什么差别
以Java配置的方式设置EhCacheCacheManager<br>
第四部分Spring集成
收藏
立即使用
收藏
立即使用
Collect
Get Started
Collect
Get Started
Collect
Get Started
Collect
Get Started
评论
0 条评论
下一页