Spring解读
在spring中been的装配方式有两种分别为xml,javaconfig,通常情况是使用javaconfig在其不能满足下才使用xml方式
在javaconfig中一个类定义为配置文件上面加上@configuration,如果要开启啊扫描相应的组件就得在组件上面加上@component,在配置文件上面加上@componentScan,如果没有其他配置的话会默认扫描与配置类相同的包,在相同的包中查找@component注解的类,如果是基于xml来启动组件扫描的话使用<context:component-scan base-package=“xxxx”>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=CDPlayerConfig.class)
public class CDPlayerTest{
@Autowired
private compactDisc cd;
@test
public void cdShouldNotBeNull(){
assertNotNull(cd)
}
}
CDPlayerTest使用了Spring的SpringJunit4ClassRunner,以便在测试开始的时候自动创建应用上下文,注解@ContextConfiguration会告诉它需要在CDPlayerconfig中加载配置,因为cdplayerconfig类中包含了@componentscan,因此最终的应用上下文中应该包含compactdiscbean
依赖注入使用@autowired
为组件扫描的bean命名
就是之前那个@component,spring会根据类名为其指定一个id,将类名小写,如果想要自己为其定义名字可以在@component(“xxxxx”),另一种就是使用java依赖注解规范@name(“xxxx”),但推荐使用component
设置组件扫描的基础包
@componentScan默认规则它会配置类所在的包作为基础类来扫描组件,如果不想扫描不同的包,就在@componentScan的value属性中指明包的名称,也可以用basePackages属性进行配置,这个可以扫描多个包格式为basePackages={”xxx“,“aaaa”},除了将包名配置为字符串的形式为指定包中所包含的类或接口basePackageClasses={xxx.class,aa.class},spring会扫面这些类的包
通过为been添加注解实现自动装配
简单来说,自动装配就是让spring自动满足bean依赖的一种方法,在满足依赖的过程中会在spring应用上下文中寻找匹配某个bean需求的其他bean,为了声明进行自动装配借助@autowired注解,它不仅能够用在构造器上,还能用在属性的setter方法上,如果没有匹配到bean的话spring会抛出一个异常,@autowired的required属性设置为false,(有可能出现没有bean,也有可能出现多个,从而不知如何选择),可以是使用@Inject;
(java的注解学习)
通过Java代码装配bean
尽管在很多场景下通过组件扫描和自动装配实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案行不通,比如使用第三方库的组件装配到你的应用中,这时候无法在它的类上添加@component和@autowired注解的,这时候就不能使用自动化装配方式,这时候有两种方案:java和xml,在这节中,我们将会学习如何使用java配置
就像我之前所说的。在进行显式配置时,javaConfig是更好的方案,因为它更为强大,类型安全并且对重构友好,因为它就是java代码,就像是java配置
同时,javaConfig与其他的java代码又有所区别,在概念上,它与应用程序中业务逻辑和领域代码是不同的,尽管它与其他组件一样都使用相同的语言进行表述,但javaConfig是配置代码,者意味着它不应该包含任何业务逻辑,javaconfig也不应该侵入到业务逻辑代码之中。尽管不是必须的,但通常将javaConfig放到单独的包中,使它与其他的应用程序逻辑分离开来,这样对于它的意图就不会产生困惑了
创建配置类
@configuration
public class cdplayerConfig{
@Bean
public CompactDisc sgtPeppers(){
return new sgtPeppers();
}
}
显示配置和隐式配置在于能不能使用其类加上@component组件,通常如果是自己写的或者spring自己带的,但是如果是第三方的就得需要自己显示地创建bean
在默认情况下bean的id与带有@bean注解的方法名是一样的,在本例中,bean的名字将会是sgtPeppers。如果你想为其设置成一个不同的名字的话就重命名该方法可以通过name属性指定一个不同的名字
@bean(name=“xxxxxx”)
不管你采用什么方法来为been命名,bean声明都是非常简单的
借助javaconfig实现注入
注入依赖的话直接通过方法参数的方式就行,而参数部分必须是在容器中创建的been,不管是通过什么方式得来的(扫描方式,xml方式,javaconfig),带有@bean注解的方法可以采用任何必要的java功能来产生bean实例
通过xml装配bean
古老的装配方式基于xml,xml不是第一选择方案,而是自动化配置和基于javaConfig
在javaconfig的时候需要带有@configuration而在xml配置中需要以元素作为根
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
声明简单的
要是基于xml的spring配置声明一个bean,我们要使用spring-beans模式中的另一个元素,元素类似于javaconfig中的@Bean注解,我们可以按照以下方式声明compactDiscbean:
<bean class="soundsystem.SgtPeppers">
这里声明了一个很简单的bean,要使用全限定的类名,默认根据类名进行命名,如果要引用它的化最好的方法就是借助id属性,
<bean class="soundsystem.SgtPeppers" id="compactDisc">
注意在简单的声明中,我们将bean的类型以字符串的形式设置在class属性中,谁能保证给class属性的值是真正的类,还不能在编译期中收益,这是xml配置的缺点
借助构造器注入初始化的bean
在xml配置中,只有一种声明bean的方式:使用元素并指定class属性,spring会从这里获取必要的信息来创建bean
在xml中声明di时,有两种基本配置方案可以选
元素
使用Spring3.0所引入的c-命名空间
两者区别是否冗长烦琐,前者比后者更加冗长,从而导致xml更加难以读懂,另外有些事情·可以做到后者无法做到
各自注入bean引用
<bean id="cdplayer" class="soundsystem.cdplayer">
<constructor-arg ref="compactDisc">
</bean>
当spring遇到这个元素时,它会创建一个cdplayer实例
c-命名
<bean id="cdplayer" class="soundsystem.cdplayer"
c:cd-ref="compactDisc"
/>
给构造参数赋值
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="ssssssssss"/>
<constructor-arg value="xxx"/>
</bean>
上面是按照构造方法中参数的索引先后来赋值的
使用c-命名空间
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_title="xxxxxxxx"
c:_artist="xxxxxxxx"
/>
也可以是这样
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_0="xxxxxxxx"
c:_1="xxxxxxxx"
/>
但构造器中只有一个参数的话也可以
<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_="xxxxxxxx"
/>
装配集合
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="ssssssssss"/>
<constructor-arg value="xxx"/>
<constructor-arg >
<list>
<value>xxxxxxx</value>
<value>dddddddd</value>
</list>
</constructor-arg>
</bean>
同样的方式使用set元素
<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="ssssssssss"/>
<constructor-arg value="xxx"/>
<constructor-arg >
<set>
<value>xxxxxxx</value>
<value>dddddddd</value>
</set>
</constructor-arg>
</bean>
和区别不大,或都可以用来装配list,set甚至数组
设置属性
<bean id="cdPlayer" class="soundsystem.cdplayer">
<property name="compactdisc" ref="compactDisc"/>
</bean>
我们也可以用p-命名空间
<bean id="cdPlayer" class="soundsystem.cdplayer"
p:compactdisc-ref="compactdisc"
/>
可构造方法类似属性赋值
当然我们可以使用spring util-命名空间中的一些功能来简化blankdiscbean,不管是p-空间命名还是util-命名空间都是需要在xml中声明的
<util:list id="xxxxxx">
<value>xxxxxxxxxx</value>
</util:list>
<bean id="cdPlayer" class="soundsystem.cdplayer"
p:compactdisc-ref="xxxxx"
/>
util:list只有util-命名空间中的多个元素之一
<util:constant/>
注入常量值
<bean id="..." class="...">
<property name="isolation">
<util:constant static-field="java.sql.Connection.TRANSACTION_SERIALIZABLE"/>
</property>
</bean>
<util:property-path/>
引用一个bean属性,并将其暴露为bean
<util:list/>
<!-- creates a java.util.List instance with the supplied values -->
<util:list id="emails">
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</util:list>
<util:map/>
<!-- creates a java.util.Map instance with the supplied key-value pairs -->
<util:map id="emails">
<entry key="pechorin" value="[email protected]"/>
<entry key="raskolnikov" value="[email protected]"/>
<entry key="stavrogin" value="[email protected]"/>
<entry key="porfiry" value="[email protected]"/>
</util:map>
<util:set/>
<bean id="emails" class="org.springframework.beans.factory.config.SetFactoryBean">
<property name="sourceSet">
<set>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
<value>[email protected]</value>
</set>
</property>
</bean>
<util:properties/>
<util:properties id="jdbcConfiguration" location="classpath:com/foo/jdbc-production.properties"/>
创建一个properties类型的bean
混合搭配
导入和混合配置,尽可能使用自动化和显式配置,但有时候xml却是最佳的方案,在spring中这些方案都不是互斥的,可以将这些方式混合一起
将其他的配置导入到已知类可以使用@import(xxx.class),或者是创建高级配置类,将两者导入@import{ {xxxx.class,dddd.class}}
可以同时使用c-空面命名和
也可以将xml也给导进来@importresource(“classpath:xxx.xml”)
在xml配置中引用javaconfig
在xml中导入其他xml文件可以用,想要导入javaconfig可以使用bean
也可以使用开启自动扫描context:component-scan
高级装配
profile
如在不同环境下用不同的数据库,就需要某种约定在某种环境下用某种数据库,spring引入了profile的功能
@configuration
@profile("dev")
public class developmentprofileconfig{
}
在java配置中可以使用@profile注解指定某个bean某个bean属于哪个profile,@profile注解应用在类级别上面了它会告诉spring这个配置类的bean只有在dev profile激活时才会被创建,如果dev profile没有激活的话,那么带有@bean注解的方法都会被忽略掉
方法类似上面
在xml中配置profile
我们也可以通过元素的profile元素,在xml中配置profile bean
<beans profile="development"
xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="...">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="...">
<!-- other bean definitions -->
<beans profile="development">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
<jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
</jdbc:embedded-database>
</beans>
<beans profile="production">
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
</beans>
</beans>
profile只有在执行才会被激活,但是有个问题如何激活
激活profile
spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default,如果设置了spring.profiles.active属性的话那么它的值来确定哪个profile是激活的,但如果没有设置spring.profiles.active属性的话,spring将会查找spring.profiles.default的值,如果两者都没有设置的,就不会激活
在WEB.XML文件中设置
<context-param>
<param-name>spring.profiles.defult</param-name>
<param-value>dev</param-value>
</context-param>
也可以使用注解的方式来开启@activeprofiles来
条件化的bean
假设希望一个或多个bean只有在应用的类路径下包含特定的库时才会创建,或者我们希望某个bean只有当另外某个特定的bean也声明之后才会被创建,我们还可能要求只有某个特定的环境变量设置以后才会创建某个bean,@Conditional注解,它可以用到带有@bean注解的方法上,如果给定条件计算结果为true,就会创建这个bean
在@conditional的类可以是任意实现了condition接口的类型,就是根据这个类的matches方法来决定
public class magicexistscondition implements condition{
public boolean matches(conditioncontext context,annotatedtypemetadata metadata){
environment env=context.getenvironment();
return env.containsproperty("magic")
}
}
conditioncontext是一个接口
public interface CondtionContext{
Beandifinitionregistry getregistry();
configurablelistablebeanfactory getbeanfactory();
environment getenvironment();
resourceloader getresourceloader();
classloader getclassloader()
}
常见的设计模式为谁来决定(实现condition接口的类,重写相关方法),决定的标志@condition(xxx.class),xxx.class这个类就是决定它是否执行
处理自动封装的歧义性
用最简单例子来说,一个接口,可以有多个实现类,解决方式说明白点默认指定,或者缩小范围(为什么不可以是什么情况决定的呢,但有时候真的不知道在已定的条件干啥)
首先
@primary
限定自动动装配的bean
当@primary的数量超过一个时,这时候就需要缩小范围(一个可以最终确定目标,缩小范围,缩小范围,再缩小范围),@Qualifier(“xxxx”),每个bean都会有一个限定符,这个限定符跟bean的id一样,框架会根据"xxxx"限定符找到相对应的bean,就是说无论那么been发生什么变化它的限定符就会和id一样
自定义限定符
在默认情况下,been的限定符和bean是一样的,那么如何自定义限定符呢,使用@Qualifier和@component组合使用自动装配,在进行显性配置时Qualifier可以和@Bean注解一起使用
面向特性的限定符要比基于beanID的限定符更好一些,限定符可以添加多个(限定符分那个顺序吗,应该是分的,因为限定就得区分),但在java不允许同个条目上重复出现相同类型的多个注解,还可以自定义限定符注解
@Target({
ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
这个就是自定义限定符
bean的作用域
在默认情况下,spring应用上下文中所有bean都是作为单例的形式创建的,也是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例
在大多数情况下,单例是很理想的方案,初始化和垃圾回收对象所带来的成本只留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。有时候,所用的类是易变的,它们会保持一些状态, 因此重用是不安全的
spring定义了多个作用域,可以基于这些作用域创建bean,包括:
单例:在整个应用中,只创建bean的一个实例
原型(prototype):每次注入或者通过spring应用上下文获取的时候,都会创建一个新的bean实例
会话(session):在web应用中,为每个会话创建一个bean实例
请求(request):在web应用中,为每个请求创建一个bean实例
单例是默认的作用域,但是正如之前所述,对于易变的类型并不合适,如果选择其他作用域,要使用@scope注解,它可以与@component或@bean一起使用
@component
@scop(configuraablebeanfactory.scop_prototype)
public class notepad{
......}
同样如果你使用xml来配置bean的话,可以使用元素的scope属性来设置作用域:
不管你使用哪种方式来声明原型作用域,每次注入或从spring应用上下文中的索引该bean的时候,都会创建新的实例,这样所导致的结果就是每次操作都能得到自己的notepad实例
使用会话和请求作用域
@component
@scope(value=webapplicationcontext.scope_session,proxymode=scopedproxymode.interfaces)
public shoppingcart cart(){
.....}
在使用会话作用域的时候,不会直接将创建的类注入到spring上下文中,而是用他们的代理类,由于代理的编写有一定的规则,所以可以直接生成如何生成呢,如下:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
在xml中声明作用域代理
<bean id="cart" class="com.myapp.shoppingchart" scope="session">
<aop:scoped-proxy/>
</bean>
运行时值注入
<bean id="sgtpeppers" class="soundsystem.blankdisc" c:_title="xxxxxxx" c:_artist="aaaaa"/>
最好避免这种硬编码,而是想让这些值在运行时再确定,spring提供了两种在运行时求值的方式
-
属性占位符
-
spring表达式语言(SpEL)
注入外部的值
在spring中,处理外部值的最简单方式就是声明属性源并通过
@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
@Autowired
Environment env;
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
在javaconfig可以用@propertysource(“classpath:sssssss”)相对路径下来引入,属性文件会被加载到spring的environment中,然后可以从这里检索属性,该Environment接口是集成在容器中的抽象,可对应用程序环境的两个关键方面进行建模:profile 和properties。
解析属性占位符
spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到spring bean中,在spring装配中,占位符的形式为使用"${…}",包装的属性名称,作为样例,我们可以在xml中按照如下方式解析blankdisc构造器参数
<bean id="sgtpeppers" class="sssss.dddd" c:_title="${disc.title}" c:_artist="${disc.artist}"/>
在javaconfig可以使用@value("${disc.title}") string title来表示
为了使用占位符,我们必须要配置一个propertyplaceholderconfigurer bean,如果你想使用xml配置的话,spring context命名空间中的context:propertyplaceholder元素将会为你生成
使用spring表达式语言进行装配
SpEL拥有很多特性
-
使用bean的id来引用Bean
-
调用方法和访问对象的属性
-
对值进行算术,关系和逻辑运算
-
正则表达式匹配
-
集合操作
SpEL样例
SpEL是一种非常灵活的表达式语言,SpEL表达式要放到#{…}中,解析属性占位符是放到${…},中的
#{T(S)}