Spring Framework Runtime
首先需要对Spring FrameWok框架有个直观的认识
Java日志框架的发展史
在读到Spring依赖JCL的时候,对Java的日志系统做点普及!
最 早出现的日志框架是apache提供的log4j,使用最为广泛,成为了Java日志的事实上的标准;然而当时Sun公司在jdk1.4中增加了 JUL(java.util.logging),企图对抗log4j,于是造成了混乱,当然此时也有其它的一些日志框架的出现,如simplelog等, 简直是乱上加乱。
解决这种混乱的方案出现了:抽象出一个接口层:于是开源社区提供了commons-logging,被称为JCL。抽象时参考了log4j、JUL、simplelog,对它们进行了适配或转接,这样就一统江湖了。
看 上去现在已经非常完美了,但好景不长,log4j的作者(Ceki Gülcü)觉得JCL不够优秀,他要搞出一套更优雅的出来,于是slf4j就出现了,并且亲自实现了一个亲子——logback(有点,老子又回来了的 感觉^_^)。好吧,确实更优雅了,但混乱局面又出现了,之前使用JCL的怎么办呢,于是Ceki Gülcü在slf4j又对JCL作了桥接转换,然而事情还没完,Ceki Gülcü又回来拯救自己的“大阿哥”——log4j,于是log4j2就诞生了,同时log4j2也加进了slf4j体系中。
PS:SLF4J是在Compile绑定实现的,而JCL是Runtime时绑定的。
Spring中如何使用日志系统
- 使用JCL
由于Spring-core依赖JCL,所以可以直接配置JCL的实现,比如log4j:<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> </dependency>
- 使用SLF4J
如果你想使用SLF4J需要三步来做:先排除JCL<dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.1.2.RELEASE</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency>
<dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.5.8</version> </dependency>
最后再引入SLF4J的组合,这个组合有比较多,你参考官网,如使用SLF4J-LOG4J<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.7</version> </dependency>
Spring资源
- Resource
Spring的Resource接口以及父接口,如下public interface Resource extends InputStreamSource { boolean exists(); boolean isOpen(); URL getURL() throws IOException; File getFile() throws IOException; Resource createRelative(String relativePath) throws IOException; String getFilename(); String getDescription(); } public interface InputStreamSource { InputStream getInputStream() throws IOException; }
- ClassPathResource可用来获取类路径下的资源文件。假设我们有一个资源文件test.txt在类路径下,我们就可以通过给定对应资源文件在类路径下的路径path来获取它,new ClassPathResource(“test.txt”)。
- FileSystemResource 可用来获取文件系统里面的资源。我们可以通过对应资源文件的文件路径来构建一个FileSystemResource。 FileSystemResource还可以往对应的资源文件里面写内容,当然前提是当前资源文件是可写的,这可以通过其isWritable()方法来 判断。FileSystemResource对外开放了对应资源文件的输出流,可以通过getOutputStream()方法获取到。
- UrlResource可用来代表URL对应的资源,它对URL做了一个简单的封装。通过给定一个URL地址,我们就能构建一个UrlResource。
- ByteArrayResource是针对于字节数组封装的资源,它的构建需要一个字节数组。
- ServletContextResource 是针对于ServletContext封装的资源,用于访问ServletContext环境下的资源。ServletContextResource持 有一个ServletContext的引用,其底层是通过ServletContext的getResource()方法和 getResourceAsStream()方法来获取资源的。
- InputStreamResource是针对于输入流封装的资源,它的构建需要一个输入流。
- ResourceLoader
Spring定义了ResourceLoader接口来加载资源public interface ResourceLoader { Resource getResource(String location); }
Resource template = ctx.getResource("some/resource/path/myTemplate.txt");
Resource template = ctx.getResource("classpath:some/resource/path/myTemplate.txt");
classpath:com/myapp/config.xml
file:///data/config.xml
http://myserver/logo.pngResourceLoader resourceLoader=new DefaultResourceLoader(); Resource resource=resourceLoader.getResource("/a.xml"); System.out.println(resource.exists());
- ApplicationContext
但是ApplicationContext不会因为Resource的不同而相互转换,如ApplicationContext ctx = new FileSystemXmlApplicationContext("classpath:conf/appContext.xml");
PS:classpath*是加载多个资源,而且可以使用通配符。
Spring IOC
- BeanFactory 是获取bean的接口,而ApplicationContext是BeanFactory的子接口,它增加了更多企业级操作。 ApplicationConext可以看做是IOC的container,它装载配置资源,并根据配置的逻辑来装配各部件,实现用户的业务。
- 配置有三种方式:Annotation-based(Spring2.5支持)、Java-based(Spring3.0)、XML-based配置。
- XML-based方式,XML可以使用import引用别的XML
<beans> <import resource="services.xml"/> <import resource="resources/messageSource.xml"/> <import resource="/resources/themeSource.xml"/> <bean id="bean1" class="..."/> <bean id="bean2" class="..."/> </beans>
- bean 的定义设计到的字段:class、name、scope、constructor arguments、properties、autowiring mode、lazy-initialization mode、initialization method、destruction method。
- Bean 可以使用name、id来唯一标示,name和id也可以同时使用,如果bean definition只有一个class,也可以不用任何标示。但有时只有name和id仍然不够个性化命名,而且name和id必须遵循Java的命名 规范,因此别名就营运而生了。
<alias name="fromName" alias="toName"/>
- bean的定义示例
- 构造函数bean
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/>
- 静态工厂bean
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/> public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; } }
- 实例工厂bean
<!-- the factory bean, which contains a method called createInstance() --> <bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --> </bean> <!-- the bean to be created via the factory bean --> <bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/> public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; } }
PS:理论上静态工厂的bean也可以像实例工厂那样配置,因为我认为static既属于类也属于实例,但我的DEMO没通过,其实在JAVA代码中也不建议使用实例对象去访问类的静态方法,因此Spring更规范化了。
- 构造函数bean
- 依赖注入(DI)
DI分为两类:基于构造器的注入和基于Setter的注入,静态工厂和实例工厂都归属于构造器注入。
- 构造器注入
package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } }
<beans> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> </bean> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> </beans>
package examples; public class ExampleBean { // Fields omitted @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg value="7500000"/> <constructor-arg value="42"/> </bean>
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean>
public class ExampleBean { // a private constructor private ExampleBean(...) { ... } // a static factory method; the arguments to this method can be // considered the dependencies of the bean that is returned, // regardless of how those arguments are actually used. public static ExampleBean createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; } }
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> <constructor-arg ref="anotherExampleBean"/> <constructor-arg ref="yetAnotherBean"/> <constructor-arg value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
- Setter注入
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; } }
<bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested ref element --> <property name="beanOne"> <ref bean="anotherExampleBean"/> </property> <!-- setter injection using the neater ref attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
- 构造器注入
- idref
用于<constructor-arg/> 或者 <property/>,使用方式同value,只是增加了校验idref的bean存不存在,如<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean" /> </property> </bean>
<bean id="theTargetBean" class="..." /> <bean id="client" class="..."> <property name="targetName" value="theTargetBean" /> </bean>
- 内部类注入
<bean id="outer" class="..."> <!-- instead of using a reference to a target bean, simply define the target bean inline --> <property name="target"> <bean class="com.example.Person"> <!-- this is the inner bean --> <property name="name" value="Fiona Apple"/> <property name="age" value="25"/> </bean> </property> </bean>
- 集合类注入
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setAdminEmails(java.util.Properties) call --> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.org</prop> <prop key="support">support@example.org</prop> <prop key="development">development@example.org</prop> </props> </property> <!-- results in a setSomeList(java.util.List) call --> <property name="someList"> <list> <value>a list element followed by a reference</value> <ref bean="myDataSource" /> </list> </property> <!-- results in a setSomeMap(java.util.Map) call --> <property name="someMap"> <map> <entry key="an entry" value="just some string"/> <entry key ="a ref" value-ref="myDataSource"/> </map> </property> <!-- results in a setSomeSet(java.util.Set) call --> <property name="someSet"> <set> <value>just some string</value> <ref bean="myDataSource" /> </set> </property> </bean>
- null和空值的注入
空值<bean class="ExampleBean"> <property name="email" value=""/> </bean>
<bean class="ExampleBean"> <property name="email"> <null/> </property> </bean>
- 用p-namespace可以简化对setter注入的XML配置,Spring2.0及之后的版本支持,p-namespace并不是被定义在xsd文件,而是存在于Spring的core。因此不能添加一个对应的p:schemaLocation,因为p命名空间在Spring的XSD中是没有定义的,仅仅存在于Spring Core中。换个角度想,如果指定了schemaLocation,那么就要有一个真实存在的XSD但必须引入
xmlns:p="http://www.springframework.org/schema/p
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="john-classic" class="com.example.Person"> <property name="name" value="John Doe"/> <property name="spouse" ref="jane"/> </bean> <bean name="john-modern" class="com.example.Person" p:name="John Doe" p:spouse-ref="jane"/> <bean name="jane" class="com.example.Person"> <property name="name" value="Jane Doe"/> </bean> </beans>
xmlns:c="http://www.springframework.org/schema/c"
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> <!-- traditional declaration --> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> <constructor-arg value="foo@bar.com"/> </bean> <!-- c-namespace declaration --> <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/> <!-- c-namespace index declaration --> <bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz" c:_2="foo@bar.com" /> </beans>
- 复合注入
<bean id="foo" class="foo.Bar"> <property name="fred.bob.sammy" value="123" /> </bean>
- depends-on
当前的bean实例化前,它depend-on的bean必须先实例化,而且可以depend-on多个bean,以空格、“,”、“;”隔开,如:<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao"> <property name="manager" ref="manager" /> </bean> <bean id="manager" class="ManagerBean" /> <bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
- 延迟初始化bean
当ApplicationContext在startup时就会初始化已经定义的bean,这叫预初始化,但你可以通过添加lazy-init属性来设置是否预初始化,如果设置为true,只有当bean在第一次使用的时候才会初始化。<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
- 自动装配(AutoWire)
自动装配是使用bean的autowire的属性来设置的,autowire共有4个值no(默认值)、byName、byType、constructor。其中no不自动装配,需要自己手动来装配;byName和byType都是对setter注入来说的;constructor内部也是使用byType方式来自动装配的,只是用于构造器注入。当根据byType(constructor通用)类型装配时,当在容器内找到多个匹配的类型时会抛出异常信息的。因此此时可以使用autowire-candidate="false"来设置此bean不允许被自动装配到其它bean或设置primary="true"来设置此bean作为首要被自动装配到其它bean。
Bean scopes
在Spring文档中明明列出6种开箱即用的scope,但不知道为什么却只说有5种?
6种分别如下:
- singleton:单例模式,当spring创建applicationContext容器的时候,spring会欲初始化所有的该作用域实例,加上lazy-init就可以避免预处理;只要applicationContext容器在,该实例一直存在;
- prototype:原型模式,每次通过getBean获取该bean就会新产生一个实例,创建后spring将不再对其管理;
- request:请求模式,只用于Web,每个http请求都产生一个新的实例,创建后spring将不再对其管理;
- session:会话模式,只用于Web,每个session产生一个新的实例,创建后spring将不再对其管理;
- global session:全局会话模式,只用于Web,整个session产生一个新的实例,创建后spring将不再对其管理;
- application:全局会话模式,只用于Web,每个ServletContext产生一个新的实例,创建后spring将不再对其管理;
示例
<bean id="user" class="com.byd.springdemo.User" scope="prototype"> <property name="name" value="kevin"></property> </bean>
PS:当需要把一个http级别的scope的对象注入到其他bean中的时候,需要在声明的http级别的scope的对象中加入,如下面的userPreferences对象
<!-- an HTTP Session-scoped bean exposed as a proxy --> <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <!-- this next element effects the proxying of the surrounding bean --> <aop:scoped-proxy/> </bean> <!-- a singleton-scoped bean injected with a proxy to the above bean --> <bean id="userService" class="com.foo.SimpleUserService"> <!-- a reference to the proxied userPreferences bean --> <property name="userPreferences" ref="userPreferences"/> </bean>
这样做的原因 是正常情况下singleton的userService中有一个session级别的对象,这样singleton的userService只初始化一次,而其所依赖的session级别的userPreferences也只初始化一次。这就与我们所定义的每个session对应一个对象的初衷相违背了,而使用<aop:scoped-proxy/>的时候,就会在实际调用的时候每次使用代理去代理userPreferences调用其对应的方法,代理访问的是对应的session中的对象,这样就可以实现每个session对应一个对象。而在代理的时候有两种方式,一种是基于JDK的interface的,一种是CGLIB形式的,如果要代理的类是面向对象的,就可以直接使用JDK的代理,否则就需要开启对CGLIB代理的支持,同时要引入CGLIB的jar包。
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session"> <aop:scoped-proxy proxy-target-class="false"/><!-- 为true则为开启对CGLIB的支持 --> </bean>
Scope也自己自定义的,但需要实现“org.springframework.beans.factory.config.Scope”接口,具体可参考Spring官方文档。
自定义Bean的特性
在Bean的生命周期中可以定义各种回调函数,方便来定制Bean,下面我们来一一列举。
- 初始化回调
可以通过实现org.springframework.beans.factory.InitializingBean接口来定制,InitializingBean接口只有一个方法void afterPropertiesSet() throws Exception;
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
- 同上,销毁时回调也可以使用org.springframework.beans.factory.DisposableBean接口、@PreDestroy注解和XML中destroy-method来实现。Bean是单例模式,而且只有在容器关闭时才会调用destroy-method。可以使用ConfigurableApplicationContext,它扩展于ApplicationContext,它新增加了两个主要的方法:refresh()和close(),让ApplicationContext具有启动、刷新和关闭应用上下文的能力。在应用上下文关闭的情况下调用refresh()即可启动应用上下文,在已经启动的状态下,调用refresh()则清除缓存并重新装载配置信息,而调用close()则可关闭应用上下文,这些接口方法为容器的控制管理带来了便利。
PS:
- 如果初始化回调和销毁时回调在各个Bean中的方法都一样的,还可以在beans使用default-init-method和default-destroy-method统一设置,如果某个Bean没有回调,则忽略。
- 当使用注解PostConstruct和PreDestroy时,由于Spring不能默认地感知到注解,因此需要注册”CommonAnnotationBeanPostProcessor“ 或配置 “<context:annotation-config />”如
<?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"> <bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor" /> <bean id="user" class="com.byd.springdemo.User"> <property name="name" value="kevin"></property> </bean> </beans>
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config /> <bean id="user" class="com.byd.springdemo.User"> <property name="name" value="kevin"></property> </bean> </beans>
xmlns:context="http://www.springframework.org/schema/context" http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
- 还有一些其他的回调,分别实现相应的接口,如Lifecycle、LifecycleProcessor 、SmartLifecycle等。
Bean的继承
Bean可以继承父的配置,比如
<bean id="parentUser" abstract="true"> <property name="name" value="kevin"></property> </bean> <bean id="user" class="com.byd.springdemo.User" parent="parentUser"> <property name="age" value="20"></property> </bean>
如果父bean不使用class,则需要使用abstract来生命为抽象Bean,abstract的Bean不能实例化。
Spring容器扩展
- 通过使用BeanPostProcessor接口来定制bean
public class MyProceesor implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println("beanName Before:"+beanName+" bean:"+bean); return bean; } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("beanName After:"+beanName+" bean:"+bean); return bean; } }
<bean class="net.oseye.springdemo.MyProceesor"></bean>
- 使用PropertyPlaceholderConfigurer获取property的配置
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:com/foo/jdbc.properties"/> </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
基于Java标注配置
虽然Spring提供了基于Java标注的配置,但一般不建议这样使用,因为这样造成可读性不强、代码入侵、不能部署时灵活配置等缺点。
- @Required
该标注常用于setter,用于必须提供属性,否则报“NullPointerException”异常public class User { private String name; public String getName() { return name; } @Required public void setName(String name) { this.name = name; } }
- @Autowired
你可以使用该标注来配置传统的setter,如public class User { private String name; @Autowired public void setName(String name) { this.name = name; } public String getName() { return name; } }
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config /> <bean id="firstuser" class="net.oseye.springdemo.User"></bean> <bean class="java.lang.String"> <constructor-arg value="kevin"></constructor-arg> </bean> </beans>
public class User { @Autowired private String name; public String getName() { return name; } }
public class User { private String name; public String getName() { return name; } @Autowired public User(String name){ this.name=name; } }
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }
@Autowired默认是使用byType,但如果有多个则自动根据byName,如果没有name则报异常。但可以使用@Qualifier来配置使用哪个public class User { @Autowired @Qualifier("username") private String name; public String getName() { return name; } }
public class User { private String name; public String getName() { return name; } @Autowired public void setName(@Qualifier("username")String name) { this.name = name; } }
- @Resource
上面@Autowired和@Qualifier的组合也可以使用@Resourcepublic class User { private String name; public String getName() { return name; } @Resource(name="username") public void setName(String name) { this.name = name; } }
- @PostConstruct和@PreDestroy参考前文。
- 由于不推荐使用Java标注配置,Spring文档中5.9 - 5.16不作为重点学习,因此@Service、@Repository、@Configuration、@ComponentScan、@Bean、@Component、@Scope、@Inject、@Named、@Import、@ImportResource、 @Value、@Profile、@PropertySource、@EnableLoadTimeWeaving等,如有兴趣可自行学习。