第二章 装配bean java
声明beanweb
构造器注入和setter方法注入正则表达式
装配beanspring
控制bean的建立和销毁express
1、Spring配置的可选方案编程
主要的装配机制:安全
一、在xml中进行显式配置session
二、在Java中进行显式配置app
三、隐式的bean发现机制和自动装配框架
尽量的用自动配置的机制。
2、自动化装配bean
一、建立可被发现的bean
Spring从两个角度来实现自动化装配:
组件扫描:spring会自动发现应用上下文中所建立的bean。
自动装配:spring自动知足bean之间的依赖。
组件扫描和自动装配组合在一块儿就能发挥出强大的威力,它们可以将你的显示装配降到最低。
① 由于使用了@Component注解,因此spring会为你把事情处理稳当。
② @ComponentScan注解启用了组件扫描
③ 经过xml启用组件扫描
二、为组件扫描的bean命名
强烈推荐@Component命名
三、设置组件扫描的基础包
@Configuration @ComponentScan("soundsystem") public class CDPlayConfig{ }
设置组件扫描的基础包
@Configuration @ComponentScan(basePackageClasses={CDPlayer.class,DVDPlayer.class}) public class CDPlayerConfig{ }
四、经过为bean添加注解实现自动装配
@Autowired
将required属性设置为false时,Spring会尝试执行自动装配,可是若是没有匹配的bean的话,spring将会让这个bean处于未装配的状态。可是此时要进行null值检查,不然会出现空指针异常。
@Inject注解来源于Java依赖注入规范
spring同时支持@Autowired和@Inject。
五、验证自动装配
3、经过Java代码装配bean
当要将第三方库中的组件装配到程序中时,这种状况下是没办法加@Component和@Autowired注解的,所以不能自动化装配。
显式配置时,JavaConfig是更好的方案,JavaConfig是配置代码,JavaConfig要放到单独的包中。
一、建立配置类
建立JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解代表这个类是一个配置类,该类应该包含在Spring应用上下文中如何建立bean的细节。
二、声明简单的bean
三、借助JavaConfig实现注入
@Bean注解代表这个方法会建立一个bean实例并将其注册到Spring应用上下文中。
四、经过xml装配bean
① 建立XML配置规范
② 声明一个简单的bean
③ 借助构造器注入初始化bean
④ 设置属性
推荐构造器注入
五、导入和混合配置
① 在JavaConfig中引用XML配置
② 在XML配置中引用JavaConfig
六、小结
Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会建立这些组件并保证他们的依赖可以获得知足,这样的话,组件才能完成预约的任务。在本章中,咱们看到了在Spring中装配bean的三种主要方式,这些技术描述了Spring应用中的组件以及这些组件之间的关系。
尽量使用自动化配置,以免显示配置所带来的维护成本。可是,若是你确实须要显示配置Spring的话,应该优先选择基于Java的配置,它比基于CML的配置更增强大、类型安全而且易于重构。
第三章 高级装配
Spring profile
条件化的bean声明
自动装配与歧义性
bean的做用域
Spring表达式语言
1、环境与profile
一、spring中的profile是什么?
profile能够理解为咱们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中。举个更具体的例子,咱们以前定义bean的时候,当spring容器以启动的时候,就会一股脑的所有加载这些信息完成对bean的建立;而使用profile后,它会将bean的定义进行更细粒度的划分,将这些定义的bean划分为几个不一样的组,当spring容器加载信息的时候,首先查找激活的profile,而后只会去加载被激活的组中所定义的bean信息,而不被激活的profile中所定义的bean是不会加载用于建立bean的。
二、为何要是用profile
由于须要啥就加载啥,不须要的就不用了加载了
三、配置spring profile
在3.1版本中,spring引入了bean profile的功能。要使用profile,首先要将全部不一样的bean定义整理到一个或多个profile中,将应用部署到每一个环境中,确保对应的profile处于激活(active)的状态。
在java配置中,可使用@profile注解指定某个bean属于哪个profile。
四、激活profile
spring在肯定哪一个profile处于激活状态时,须要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。若是配置了spring.profiles.active属性的话,那么它的值就会用来肯定哪一个profile是激活的。可是若是没有配置spring.profiles.active,那spring将会查找spring.profiles.default的值。
有两种方式来设置这两个属性:
我所喜欢的一种方式是使用DispatcherServlet的参数将spring.profiles.default设置为开发环境的profile,我会在servlet上下文中进行设置(为了兼顾到ContextLoaderListener)。例如,在web应用中,设置spring.profiles.default的web.xml文件会以下所示:
在web应用的web.xml文件中设置默认的profile
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/root-context.xml</param-value> </context-param> <context-param> <param-name>spring.profiles.default</param-name><!--为上下文设置默认的profile--> <param-value>dev</param-value> </context-param> <listen> <listen-class> org.springframework.web.context.ContextLoaderListenr </listen-class> </listen> <servlet> <servlet-name>appServlet</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>spring.profiles.default</param-name><!--为servlet设置默认的profile--> <param-value>dev</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>appServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </beans>
按照这种方式设置spring.profiles.default,全部开发人员都能从版本控制软件中得到应用程序源码,并使用开发环境的设置运行代码,而不须要额外的配置。
系统会优先使用spring.profiles.active中所设置的profile。
spring提供了@ActiveProfiles注解,咱们可使用它来指定运行测试时要激活哪一个profile。在集成测试时,一般想要激活的是开发环境的profile。
在条件化建立bean的时候,spring的profile机制是一种很棒的方法,这里的条件要基于哪一个profile处于激活状态来判断。
2、条件化的bean
spring4引入了一个新的@Conditional注解,它能够用到带有@Bean注解的方法上。若是给定的条件计算结果为true,就会建立这个bean,否者,这个bean就会被忽略。
例如,假设有一个名为MagicBean的类,咱们但愿只有设置了magic环境属性的时候,Spring才会实例化这个类。若是环境中没有这个属性,那么MagicBean将会被忽略。
条件化的建立bean:
@Bean @Conditional(MagicExistsCondition.class)//条件化地建立bean public MagicBean magicBean(){ return new MagicBean(); }
能够看到,@Conditional中给定了一个Class,它指明了条件,在本例中,也就是MagicExistsCondition。@Conditional将会经过Condition接口进行条件对比:
public interface Condition{ boolean matches(ConditionContext ctxt,AnnoatedTypeMetadata metadata); }
设置给@Conditional的类能够是任意实现了Condition接口的类。能够看出,这个接口实现起来很简单直接,只需提供matches()方法的实现便可。若是matches()方法返回true,那么就会建立带有@Conditional注解的bean。若是matches()方法返回false,将不会建立bean。
在condition中检查是否存在magic属性
public class MagicExistsCondition implements Condition{ public boolean matches(ConditionContext ctxt,AnnoatedTypeMetadata metadata){ Environment env = context.getEnvironment(); return env.containsProperty("magic");//检查magic属性 } }
在上面的程序中,matches()方法很简单但功能很强大。它经过给定的ConditionContext对象进而获得Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。若是返回true,就表示@Conditional注解上引用MagicExistsCondition的bean都会被建立。
ConditionContext是一个接口,大体以下所示:
public interface ConditionContext{ //检查bean定义 BeanDefinitionRegistry getRegistry(); //检查bean是否存在,检查bean的属性 ConfigurableListableBeanFactory getBeanFactory(); //检查环境变量是否存在以及它的值是什么 Environment getEnvironment(); //读取ResourceLoader加载的资源 ResourceLoader getResourceLoader(); //加载并检查类是否存在 ClassLoader getClassLoader(); }
AnnotatedTypeMetadata则可以让咱们检查带有@bean注解的方法上是否还有其余的注解。像ConditionContext同样,AnnotatedTypeMetadata也是一个接口。
public interface AnnotatedTypeMetadata{ //借助isAnnotated()方法,判断带有@bean注解的方法是否还有其余特定的注解。 boolean isAnnotated(String annotationType); //检查@bean注解的方法上其余注解的属性 Map<String,Object> getAnnotationAttributes(String annotationType); Map<String,Object> getAnnotationAttributes(String annotationType,boolean classValuesAsString); MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType); MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType,boolean classValuesAsString); }
从spring4开始,@profile注解进行了重构,使其基于@Conditional和Condition实现。来看一下,spring4中@profile是如何实现的。
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE,ElementType.METHOD}) @Documented @Conditional(ProfileCondition.class) public @interface Profile{ String[] value(); }
@Profile自己也使用了@Condition注解,而且引用ProfileCondition做为Condition实现。以下所示,ProfileCondition实现了Condition接口,而且考虑到了ConditionContext和AnnotatedTypeMatadata中的多个元素。
ProfileCondition检查某个bean profile是否可用:
class ProfileCondition implements Condition{ public boolean matches(ConditionContext context,AnnotatedTypeMetadata){ if(context.getEnvironment()!=null){ MultiValueMap<String,Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if(attrs!=null){ for(Object value:attrs.get("value")){ if(context.getEnvironment().acceptsProfiles(String[] value)){ return true; } } return false; } } return true; } }
咱们能够看到,ProfileCondition经过AnnotatedTypeMetadata获得了用于@Profile注解的全部属性,借助这些信息,它明确的检查value属性,该属性包含了bean的Profile名称,而后,它经过ConditionContext获得的Environment来检查[借助accpetsProfiles()方法]该profile是否处于激活状态。
3、处理自动装配的歧义性
若是不只有一个bean可以匹配结果的话,这种歧义性会阻碍spring自动装配属性、构造器参数或方法参数。
若是三个实现类均使用了@Component注解,在组件扫描的时候,可以发现他们并将其建立为Spring应用上下文里面的bean,而后,当spring自动装配的时候,它并无惟1、无歧义的可选值。此时,spring会抛出异常。
就算歧义性是个问题,但实际开发中不多碰见,给定的类型只有一个实现类就行了嘛,自动装配可以很好地运行。
可是,当歧义性确实发生时,spring提供了多种可选方案来解决这样的问题。你能够将可选bean中的某一个设为首选(primary)的bean,或者使用限定符(qualifier)来帮助spring将可选的bean的范围缩小到只有一个bean。
一、标识首选的bean
在声明bean的时候,经过将其中一个可选的bean设置为首选(primary)bean可以避免自动装配时的歧义性。当遇到歧义性的时候,Spring将会使用首选的bean,而不是其余可选的bean。
@Bean @Primary public class IceCrean implements Dessert{ }
若是XML配置bean的话,一样能够实现此功能。<bean>元素有一个primary属性来指定首选的bean。
二、限定自动装配的bean
@Autowired @Qualifier("iceCream") public void setDessert(Dessert dessert){ this.dessert = dessert; }
@Qualifier注解所设置的参数就是想要注入对的bean的ID。全部使用@Component注解声明的类都会建立bean,而且bean的ID为首字母变为小写的类名。所以,@Qualifier(“iceCream”)指向的是组件扫描时锁建立的bean,而且这个bean时IceCream类的实例。
@Component--把这些类归入进spring容器中管理。
建立自定义的限定符
咱们能够为bean设置本身的限定符,而不是依赖将beanID做为限定符。在这里须要加上@Qualifier,与@Component配合使用
@Component @Qualifier("sexy") public class IceCream implements Dessert{...}
在这种状况下,sexy限定符分配给了IceCreambean。由于它没有耦合类名,所以你能够随意重构IceCream的类名,而没必要担忧会破坏自动装配。在注入的地方,引用sexy限定符便可:
@Component @Qualifier("sexy") public class setDessert(Dessert dessert){ this.dessert = dessert; }
当配置显式bean的时候,@Qualifier也能够与@bean注解一块儿使用:
@Bean @Qualifier("sexy") public Dessert iceCream{ return new IceCream(); }
当使用@Qualifier值时,最佳实践是为bean选择特征性或描述性的术语,而不是使用随便的名字。
经过声明自定义的限定符注解,咱们能够同时使用多个限定符,不会再有Java编译器的限制或错误。与此同时,相对于使用原始的@Qualifier并借助string类型来指定限定符,自定义的注解更为安全。
为了建立自定义的条件化注解,咱们建立了一个新的注解并在这个注解上添加了@Conditional。为了建立自定义的限定符注解,咱们建立了一个新的注解并在这个注解上添加了@Qualifier。这种技术能够用到不少spring注解中,从而可以将它们组合在一块儿造成特定目标的自定义注解。
如今咱们来看一下如何在不一样的做用域中声明bean。
4、bean的做用域
默认状况下,spring应用上下文中全部bean都是做为以单例(singleton)的形式建立的。也就是说,无论给定的一个bean被注入到其它bean多少次,每次所注入的都是同一个实例。
spring定义了多种做用域,能够基于这些做用域建立bean,包括:
单例是默认的做用域,对于易变的类型,这并不合适。若是选择其余的做用域,要使用@Scope注解,它能够与@Component或@bean一块儿使用。
@Component @Scope(ConfigurableBeanFacory.SCOPE_PROTOTYPE) public class Notepad{ }
使用ConfigurableBeanFacory类的SCOPE_PROTOTYPE常量设置了原型做用域。你固然也可使用@Scope(“prototype”),可是使用SCOPE_PROTOTYPE常量更加安全而且不易出错。
一样也可使用XML来配置bean:
<bean id="notepad" class="com.oschina.Notepad" scope="prototype"></bean>
无论你使用哪一种方式来声明原型做用域,每次注入或从spring应用上下文中检索该bean的时候,都会建立新的实例。这样所致使的结果就是每次操做都能获得本身的notepad实例。
一、使用会话和请求做用域
在web应用中,若是可以实例化在会话和请求范围内共享的bean,那将时很是有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean表明用户的购物车。若是购物车是单例的,那么将会致使全部的用户都会向同一个购物车中添加商品。另外一方面,若是购物车是原型做用域的,那么在应用中某一个地方网购物车中添加物品,在应用的另一个地方可能就不可用了,由于这里注入的是另外一个原型做用域的购物车。
就购物车bean来讲,会话做用域是最为合适的,由于它与给定的用户关联性最大。要指定会话做用域,咱们可使用@Scope注解,它的使用方式与指定原型做用域是相同的:
@Component @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopeProxyMode.INTERFACES) public ShoppingCart cart(){ }
咱们将value设置成了WebApplicationContext中的SCOPE_SESSION常量,这回告诉spring为web应用中每个会话建立一个ShoppingCart。这会建立多个ShoppingCart的bean实例,可是对于给定的会话只是建立了一个实例,在当前会话中,这个bean至关于单例的。
要注意的是,@Scope同时还有一个proxyMode属性,它被设置成了ScopeProxyMode.INTERFACES。这个属性解决了将会话或请求做用域的bean注入到单例bean中所遇到的问题。在描述proxyMode属性以前,咱们先来看一下ProxyMode所解决的场景。
假设咱们将shoppingCart bean注入到单例StoreService bean的setter方法中,以下所示:
@Component public class StoreService{ @Autowired public void setShoppingCart(ShoppingCart shoppingCart){ this.shoppingCart=shoppingCart; } ... }
@Autowired是用在Javabean中的注解,经过byType形式,用来给指定的字段或方法注入所需的外部资源。
先看一下bean实例化和@Autowired装配过程:
一、一切都是从bean工厂的getBean方法开始的,一旦该方法调用总会返回一个bean实例 。
二、实例化和装配过程当中会屡次递归调用getBean方法来解决类之间的依赖。
三、@Autowired是根据类型来查找和装配的,可是咱们设置了<beans default-autowired="byName"/>后会影响最终的类型匹配查找。由于在前面有根据BeanDefinition的autowire类型设置PropertyValue值的一步,其中会有新实例的建立和注册。就是那个autowireByName方法
由于StoreService是一个单例bean,会在spring应用上下文加载的时候建立,当它建立的时候,spring会试图将ShoppingCart bean注入到setShoppingCart()方法中。 但ShoppingCart bean是会话做用域的,此时并不存在。直到某个用户进入系统,建立了会话以后,才会出现ShoppingCart实例。
另外,系统中将会有多个ShoppingCart实例,每一个用户一个,咱们并不想让spring注入某个固定的ShoppingCart实例到storeService中。咱们但愿的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例刚好是当前会话所对应的那一个。
二、在xml中声明做用域代理
若是你须要使用XML来声明会话或请求做用域的bean,那么就不能使用@Scope注解及其ProxyMode属性了。<bean>元素的scope属性可以设置bean的做用域,可是该怎样指定代理模式呢?
要设置代理模式,咱们须要使用Spring aop命名空间的一个新元素:
<bean id="cart" class="com.oschina.ShoppingCart" scope="session"> <aop:scoped-proxy/> </bean>
<aop:scoped-proxy/>是与@Scope注解的ProxyMode属性功能相同的Spring XML配置元素。它会告诉spring为bean建立一个做用域代理。默认状况下,它会使用CGLib建立目标类的代理。可是咱们也能够将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:
<bean id="cart" class="com.oschina.ShoppingCart" scope="session"> <aop:scoped-proxy proxy-target-class="false" /> </bean>
为了使用<aop:scoped-proxy>元素,咱们必须在XML配置中声明spring的aop命名空间:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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"> ... </beans>
在第4章中,咱们使用spring和切面编程的时候,会讨论spring aop命名空间的更多知识,如今咱们来看一下spring高级配置的另一个可选方案:spring表达式语言(spring expression language)。
5、运行时值注入
一、注入外部的值
在Spring中,处理外部值最贱的方式就是声明属性源并经过spring的environment来检索属性。
package com.oschina @Configuration //声明属性源 @PropertySource("classpath:/com/oschina/app.properties") public class ExpressiveConfig{ @Autowired Environment env; @Bean public BlankDisc disc(){ //检索属性值 return new BlankDisc(env.getProperty("disc.title"),env.getProperty("disc.artist")); } }
深刻学习spring的environment
直接从environment中检索属性是很是方便的,尤为是在Java配置中装配bean的时候。可是spring也提供了经过占位符装配属性的方法,这些占位符的值会来源于一个属性源。
解析占位符
在spring装配中,占位符的形式为使用“${...}”包装的属性名称。
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist} />"
二、使用spring表达式语言进行装配
spring3引入了spring表达式语言(spring expression language ,SpEL),它可以以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程当中使用的表达式会在运行时计算的获得值,使用SpEL,你能够实现超乎想象的装配效果,这是使用其余的装配技术难以作到的。
SpEL拥有不少特性,包括:
SpEL可以用在依赖注入之外的其余地方,例如,spring Security支持使用SpEL表达式定义安全限制规则。另外,若是你在spring MVC应用中使用Thymeleaf模板做为视图的话,那么这些模板可使用SpEL表达式引用模板数据。
SpEL样例
SpEL是一种很是灵活的表达式语言,因此本书中不可能面面俱到的介绍它的各类用法。可是咱们能够展现几个基本的例子,这些例子会激发你的灵感,有助于编写本身的表达式。
SpEL表达式使用"#{...}",这与属性占位符有些相似,属性占位符是"${...}"。
除去"#{...}"标记以后,剩下的就是SpEL表达式体了,也就是一个数字常量。这个表达式的计算结果就是数字1,这恐怕不会让你感到丝毫惊讶。
#{T(System).currentTimeMillis()}
它的最终结果是计算表达式的那一刻的当前时间的毫秒数。T()表达式会将java.lang.System视为Java中对应的类型,所以能够调用其static修饰的currentTimeMillis()方法。
SpEL表达式也能够应用其余的bean或其余bean的属性。例如,以下的表达式会计算获得ID为sgtPeppers的bean的artist属性:
#{sgtPeppers.artist}
咱们还能够经过systemProperties对象引用系统属性:
#{systemProperties['disc.title']}
这只是SpEL的几个基础样例。
若是经过组件扫描建立bean的话,在注入属性和构造器参数时,咱们可使用@Value注解,这与以前看到的属性占位符很是类似。不过,在这里咱们所使用的不是占位符表达式,而是SpEL表达式。例如,下面的样例展现了BlankDisc,它会从系统属性中获取专辑名称和艺术家的名字:
public BlankDisc(@Value{'#systemProperties['disc.title']'} string title, @Value{'#systemProperties['disc.artist']'} string artist){ this.title = title; this.artist=artist; }
在XML配置中,你能够将SpEL表达式传入<property>或<constructor-arg>的value属性中,或者将其做为p-命名空间或c-命名空间条目的值。例如,在以下BlankDisc bean的XML声明中,构造器参数就是经过SpEL表达式设置的:
<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:title="#systemProperties['disc.title']" c:_artist="#[systemProperties['disc.artist']]">
如今咱们来学一下SpEL所支持的基础表达式:
表示字面值
实际上能够用来表示浮点值、string值以及Boolean值。
下面的SpEL表达式样例所表示的就是浮点数:
#{3.141592657}
....
SpEL所能作到的另一件基础的事情就是经过ID引用其余的bean。例如,你可使用SpEL将一个bean装配到另外一个bean的属性中,此时要是用bean ID做为SpEL表达式
SpEL运算符
例子:#{2*T(java.lang.Math).PI*circle.radius}
这不只是使用SpEL中乘法运算符的绝佳样例,它也为你展现了如何将简单的表达式组合为更为复杂得表达式。
SpEL还提供了三元运算符
6、小结
咱们在本章介绍了许多背景知识,在第二章所介绍的基本bean装配基础之上,又学习了一些强大的高级装配技巧。
首先,咱们学习了spring profile,它解决了spring bean要跨各类部署环境的通用问题。在运行时,经过将环境相关的bean与当前激活的profile进行匹配,spring可以让相同的部署单元跨多种环境运行,而不须要进行从新构建。
Profile bean是在运行时条件化建立bean的一种方式,可是spring4提供了一种更为通用的方式,经过这种方式能可以声明某些bean的建立与否要依赖于给定条件的输出结果。结合使用@Conditional注解和Spring Condition接口的实现,可以为开发人员提供一种强大和灵活的机制,实现条件化的建立bean。
咱们还看了两种解决自动装配歧义性的方法:首选bean以及限定符。尽可能将某个bean设置为首选bean是很简单的,单这种方式也有其局限性,因此咱们讨论了如何将一组可选的自动装配bean,借助限定符将其范围缩小到只有一个符合条件的bean。除吃以外,咱们还看到了如何建立自定义的限定符注解,这些限定符描述了bean的特性。
尽管大多数的spring bean都是单例的方式建立的,但有的时候其它的建立策略更为合适。spring可以让bean以单例、原型、请求做用域或会话做用域的方式来建立。在声明请求做用域或会话做用域的bean的时候,咱们还学习了如何建立做用域代理,它分为基于类的代理和基于接口的代理两种方式。
最后咱们还学习了spring表达式语言,他可以在运行时计算要注入到bean属性中的值。
对于bean装配,咱们已经掌握了扎实的基础知识,如今咱们将注意力转向面向切面编程(AOP)。依赖注入可以将组建及其协做的其余组件解耦,与之相似,AOP有助于将应用组件与跨多个组件的任务进行解耦。在下一章,咱们将学习在spring中如何建立和使用切面。