那些年,让我们一起着迷的Spring
http://spring.io
https://spring.io/projects/spring-framework
spring是一个开源框架,是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架
-提供了面向切面编程的丰富支持,允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发
-包含并管理应用对象的配置和生命周期,这个意义上是一种容器
-将简单的组件配置、组合成为复杂的应用,这个意义上是框架
Java 程序员必须掌握的 5 个注解!
**基础:两个包 **
-org.springframework.beans
-org.springframework.context
-BeanFactory提供配置结构和基本功能,加载并初始化Bean
-ApplicationContext保存了Bean对象并在Spring中被广泛使用
**方式,ApplicationContext **
-Web应用中依赖servlet或Listener
Bean容器初始化
FileSystemXmlApplicationContext context= new FileSystemXmlApplicationContext("F:/workspace/appcontext.xml");
caspatxnlApplicstionc ontext context= new ClaspathxmlAplicationcontextrclasspathispring-contextxml);
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>
org.springframework.web.context.ContextLoaderServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
Spring注入
Spring注入是指在启动Spring容器加载bean配置的时候,完成对变量的赋值行为
<?xml version="1.0"encoding="UTF-8?>
<beans xmlns="http:/www.springframework.org/schema/beans"
xmlns:xsi="htto://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http:/www.springframework.org/schema/beans
http:/www.springframeworkorg/schema/beans/spring-beans.xsd">
<bean id="injectionService"class='com.imooc.ioc.injection.service.InjiectionServicelmp/>
<property name="injection0AO"ref=tipjection0Ag'1p</property>
</bean>
<bean id=ipjection0AO"class="com.imooc.iodinjiection.dao.lpjection0AOlmp/></bean>
</beans>
import com.imooc.ioc.injection.dao.InjectionDAO;
public class InjectionServicelmpl implements InjectionService{
private InjectionDAO injectionDAO;
//设值注入
public void setlnjectionDAO(InjectionDAO injectionDAO)
{
this.injectionDAO=injectionDAO;
}
public void save(String arg)
{
//模拟业务操作
System.outprintin(CService接收参数:+arg);
arg=arg +":"+this.hashCode0;
injectionDAO.save(arg);
}
}
-构造注入
<?xml version="1.0"encoding="UTF-8?>
<beans xmlns="http:/www.springframework.org/schema/beans"
xmlns:xsi="htto://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http:/www.springframework.org/schema/beans
http:/www.springframeworkorg/schema/beans/spring-beans.xsd">
<bean id="injectionService"class='com.imooc.ioc.injection.service.InjiectionServicelmp/>
<constructor-arg name="injection0AO"ref=iniection0AQ>I</constructor-arg>
</bean>
<bean id=ipjection0AO"class="com.imooc.iodinjiection.dao.lpjection0AOlmp/></bean>
</beans>
import com.imooc.ioc.injection.dao.InjectionDAO;
public class InjectionServicelmpl implements InjectionService{
private InjectionDAO injectionDAO;
//构造器注入
public InjectionServicelmpl(InjectionDAO injectionDAO)
{
this.injectionDAO=injectionDAO;
}
public void save(String arg)
{
//模拟业务操作
System.outprintin(CService接收参数:+arg);
arg=arg +":"+this.hashCode0;
injectionDAO.save(arg);
}
}
Bean装配,从Spring到Spring Boot
Bean配置项
Initialization/destruction method
Bean的作用域
singleton:单例,指一个Bean容器中只存在一份
prototype:每次请求(每次使用)创建新的实例,destroy 方式不生效
request:每次http请求创建一个实例且仅在当前request内 有效
session:同上,每次http请求创建,当前session内有效
global session:基于portlet的web中有效(portlet定义了 global session),如果是在web中,同session
<bean id="beanScope"class="com.imooc.bean.BeanScope" scope=singletor'"></bean>
Spring中提供了一些以Aware结尾的接口,实现了Aware接 口的bean在被初始化之后,可以获取相应资源
通过Aware接口,可以对Spring相应资源进行操作(一定要慎 重)
Spring中的aware接口
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class MoocBeanName implements BeanNameAware, ApplicationContextAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("MoocBeanName : " + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
System.out.println("setApplicationContext : " + applicationContext.getBean(this.beanName).hashCode());
}
}
针对于资源文件的统一接口Resources
-UrlResource:URL对应的资源,根据一个URL地址即可构建
-ClassPathResource:获取类路径下的资源文件
-FileSystemResource:获取文件系统里面的资源
-ServletContextResource:ServletContext封装的资源,用于访问ServletContext环境下的资源
-InputStreamResource:针对于输入流封装的资源
-ByteArrayResource:针对于字节数组封装的资源
ResourceLoader
所有的application contexts都实现了ResourceLoader接口,因此在Resource实例中都可以使用application contexts。
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");
Resource template=ctx.getResource("file:/some/resource/path/myTemplate.txt");
import java.io.IOException;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
public class MoocResource implements ApplicationContextAware {
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
this.applicationContext = applicationContext;
}
public void resource() throws IOException {
Resource resource = applicationContext.getResource("config.txt");
System.out.println(resource.getFilename());
System.out.println(resource.contentLength());
}
}
Classpath扫描与组件管理
从Spring3.0开始,Spring JavaConfig项目提供了很多特性 ,包括使用java而不是XML定义bean,比如@Configuration,@Bean,@Import,@DependsOn
@Component是一个通用注解,可用于任何bean
@Repository,@Service,@Controller是更有针对性的注解
-@Repository通常用于注解DAO类,即持久层
-@Service通常用于注解Service类,即服务层
-@Controller通常用于Controller类,即控制层(MVC)
类的自动检测及Bean的注册
·Spring可以自动检测类并注册Bean到ApplicationContext中
@Service
bublic class SimpleMovielister{
private MovieFinder movieFinder;
@Autovired
public SimpleMovieLister(MovieFinder movieFinder){
this.movieFinder=movieFinder;
}
}
@Repository public class JpaMovieFinder implements MovieFinder{
//implementation elided for clarity
}
context:annotation-config
通过在基于XML的Spring配置如下标签(请注意包含上下文 命名空间)
<context:annotation-config/>仅会查找在同一个applicatcontext中的Bean注解
<?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/>
</beans>
类的自动检测及Bean的注册
为了能够检测这些类并注册相应的Bean,需要下面内容
<beans>
<context:component-scan base-package="org.example"/>
</beans>
<context:component-scan>包含<context:annotation- config>,通常在使用前者后,不用再使用后者,前者包含后者全部功能
AutowiredAnnotationBeanPostProcessor和 CommonAnnotationBeanPostProcessor也会被包含进来
使用过滤器进行自定义扫描
默认情况下,类被自动发现并注册bean的条件是:使用 @Component,@Repository,@Service,@Controller注解或者使用@Component的自定义注解
可以通过过滤器修改上面的行为,如:下面例子的XML配置忽略所有的@Repository注解并用“Stub”代替
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex" expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan>
</beans>
还可使用use-default-filters="false"禁用自动发现与注册
定义Bean
扫描过程中组件被自动检测,那么Bean名称是由 BeanNameGenerator生成的(@Component, @Repository,@Service,@Controller都会有个name属性用 于显式设置Bean Name)
@Service("myMovieLister")
Repository public class SimpleMovieLister {}
@Repository
public class MovieFinderImpl implements MovieFinder{}
可自定义bean命名策略,实现BeanNameGenerator接口, 并一定要包含一个无参数构造函器
<beans>
<context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator"/>
</beans>
作用域(Scope)
通常情况下自动查找的Spring组件,其scope是singleton,Spring2.5提供了一个标识scope的注解@Scope
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder{}
也可以自定义scope策略,实现ScopeMetadataResolver接 口并提供一个无参构造器,比如在多线程中
<beans>
<context:component-scan base-package="org.example” scope-resolver="org.example.MyScopeResolver"/>
</beans>
proxyMode属性
@Scope("prototype" proxyMode=ScopedProxyMode.TARGET_CLASS )
public class MovieFinderImpl implements MovieFinder{}
可以使用@Autowired注解那些众所周知的解析依赖性接口, 比如:BeanFactory,ApplicationContext,Environment, ResourceLoader,ApplicationEventPublisher,and MessageSource
public class MovieRecommender {
@Autovired private ApplicationContext context;
public MovieRecommender(){}
}
可以通过添加注解给需要该类型的数组的字段或方法,以提供ApplicationContext中的所有特定类型的bean
private Set<Moviecatalog> movieCatalogs;
@Autovired
public void seMovieCatalogs(Set<MovieCatalog> movieCatalogs){(
this.movieCatalogs=movieCatalogs;
}
private Map<String,Movlecatalog> moviecatalogs;
@Aucovired
pablic void setMovieCatalogs(Map<String,MovieCatalog > movieCatalogs){
this.movieCatalogs=movieCatalogs;
}
如果希望数组有序,可以让bean实现 org.springframework.core.Ordered接口或使用的@Order注解,并且只针对数组有效
@Autowired是由Spring BeanPostProcessor处理的,所以不能在自己的BeanPostProcessor或 BeanFactoryPostProcessor类型应用这些注解,这些类型必须通过XML或者Spring的@Bean注解加载
public interface BeanInterface {}
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Order(2)
@Component
public class BeanImplOne implements BeanInterface {
}
@Order(1)
@Component
public class BeanImplTwo implements BeanInterface {
}
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class BeanInvoker {
@Autowired
private List<BeanInterface> list;
public void say() {
if (null != list && 0 != list.size()) {
System.out.println("list...");
for (BeanInterface bean : list) {
System.out.println(bean.getClass().getName());
}
} else {
System.out.println("List<BeanInterface> list is null");
}
}
}
list...
com.imooc.beanannotation.multibean.BeanImplTwo
com.imooc.beanannotation.multibean.BeanImplOne
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class BeanInvoker {
@Autowired
private Map<String, BeanInterface> map;
public void say() {
if (null != map && 0 != map.size()) {
System.out.println("map...");
for (Map.Entry<String, BeanInterface> entry : map.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue().getClass().getName());
}
} else {
System.out.println("Map<String, BeanInterface> map is null");
}
}
}
map...
beanImplTwo com.imooc.beanannotation.multibean.BeanImplTwo
beanImplOne com.imooc.beanannotation.multibean.BeanImplOne
按类型自动装配可能多个bean实例的情况,可以使用Spring的@Qualifier注解缩小范围(或指定唯一),也可以用于指定单独的构造器参数或方法参数
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/>
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/>
</bean>
如果通过名字进行注解注入,主要使用的不是@Autowired( 即使在技术上能够通过@Qualifier定bean的名字),替代方式是使用JSR-250@Resource注解,它是通过其独特的名称 来定义来识别特定的目标(这是一个与所声明的类型是无关的 匹配过程)
因语义差异,集合或Map类型的bean无法通过@Autowired 来注入,因为没有类型匹配到这样的bean,为这些bean使用 @Resource注解,通过唯一名称引用集合或Map的bean
@Autowired适用于fields,constructors,multi-argument methods这些允许在参数级别使用@Qualifier注解缩小范围的情况
@Resource适用于成员变量、只有一个参数的setter方法,所以在目标是构造器或一个多参数方法时,最好的方式是使用qualifiers
@Component
public class BeanImplOne implements BeanInterface {
}
@Component
public class BeanImplTwo implements BeanInterface {
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
public class BeanInvoker {
@Autowired
@Qualifier("beanImplTwo")
private BeanInterface beanInterface;
public void say() {
if (null != beanInterface) {
System.out.println(beanInterface.getClass().getName());
} else {
System.out.println("beanInterface is null...");
}
}
}
com.imooc.beanannotation.multibean.BeanImplTwo
使用@ImportResource和@Value 注解进行资源文件读取
<beans>
<context:annotation-config/>
<context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
<bean class="com.acme.AppConfig"/>
<bean class=lWorg.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="S{jdbc.password)"/>
</bean>
</beans>
等价于
@Configuration
ImportResource("classpath:/com/acme/properties-config.xm1")
public class AppConfig{
@Value("$(jdbc.url)")
private String url;
@Value("$(jdbc.username)")
private String username;
@Value("jdbc.password)")
private String password;
@Bean
public DataSource dataSource(){
return new DriverManagerDataSource(url,username,password);
}
}
# properties-config.xm1
jdbc.username=root
password=root
url=127.00.1
public interface Store<T> {}
public class StringStore implements Store<String> {}
public class IntegerStore implements Store<Integer> {}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource("classpath:config.xml")
public class StoreConfig {
@Autowired
private Store<String> s1;
@Autowired
private Store<Integer> s2;
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
@Bean(name = "stringStoreTest")
public Store stringStoreTest() {
System.out.println("s1 : " + s1.getClass().getName());
System.out.println("s2 : " + s2.getClass().getName());
return new StringStore();
}
}
CustomAutowireConfigurer
CustomAutowireConfigurer是 BeanFactoryPostProcessor的子类,通过它可以注册自己的 qualifier注解类型(即使没有使用Spring的@Qualifier 注解)
<bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
该AutowireCandidateResolver决定自动装配的候选者
-每个bean定义的autowire-candidate值
-任何中的default-autowire-candidates
-@Qualifier注解及使用CustomAutowireConfigurer的自定义类型
public class AspectBiz {
public void biz() {
System.out.println("AspectBiz biz.");
// throw new RuntimeException();
}
}
import org.aspectj.lang.ProceedingJoinPoint;
public class MoocAspect {
public void before() {
System.out.println("MoocAspect before.");
}
public void afterReturning() {
System.out.println("MoocAspect afterReturning.");
}
public void afterThrowing() {
System.out.println("MoocAspect afterThrowing.");
}
public void after() {
System.out.println("MoocAspect after.");
}
}
<bean id="moocAspect" class="com.imooc.aop.schema.advice.MoocAspect"></bean>
<bean id="aspectBiz" class="com.imooc.aop.schema.advice.biz.AspectBiz"></bean> <aop:config>
<aop:aspect id="moocAspect4op" ref="moocAspect">
<aop:pointcut expression="execution(* com.imooc.aop.schema.advice.biz.*Biz(..))" id="moocPiontcut"/>
<aop:before method="before" pointcut-ref="moocPiontcut"/>
<aop:after-returning method="afterReturning" pointcut-ref="moocPiontcut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="moocPiontcut"/>
<aop:after method="after" pointcut-ref="moocPiontcut"/>
</aop:aspect>
</aop:config>
// 输出
MoocAspect before.
AspectBiz biz.
MoocAspect afterReturning.
MoocAspect after
Around advice
通知方法的第一个参数必须是ProceedingJoinPoint类型
<aop:around method="around" pointcut-ref="moocPiontcut"/>
public Object around(ProceedingJoinPoint pjp) {
Object obj = null;
try {
System.out.println("MoocAspect around 1.");
obj = pjp.proceed();
System.out.println("MoocAspect around 2.");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
Advice parameters
<aop:around method="aroundInit"
pointcut="execution(* com.imooc.aop.schema.advice.biz.AspectBiz.init(String, int)) and args(bizName, times)"/>
public class AspectBiz {
public void biz() {
System.out.println("AspectBiz biz.");
}
public void init(String bizName, int times) {
System.out.println("AspectBiz init : " + bizName + " " + times);
}
}
import org.aspectj.lang.ProceedingJoinPoint;
public class MoocAspect {
public Object aroundInit(ProceedingJoinPoint pjp, String bizName, int times) {
System.out.println(bizName + " " + times);
Object obj = null;
try {
System.out.println("MoocAspect aroundInit 1.");
obj = pjp.proceed();
System.out.println("MoocAspect aroundInit 2.");
} catch (Throwable e) {
e.printStackTrace();
}
return obj;
}
}
允许一个切面声明一个实现指定接口的通知对象,并且提供了一个接口实现类来代表这些对象
由<aop:aspect>中的<aop:declare-parents>元素声明
该元素用于声明所匹配的类型拥有一个新的parent(因此得名)
schema-defined aspects只支持singleton model
<aop:config>
<aop:aspect id="moocAspectAOP" ref="moocAspect">
<aop:declare-parents
types-matching="com.imooc.aop.schema.advice.biz.*(+)"
implement-interface="com.imooc.aop.schema.advice.Fit"
default-impl="com.imooc.aop.schema.advice.FitImpl"/>
</aop:aspect>
</aop:config>
public interface Fit {
void filter();
}
public class FitImpl implements Fit {
@Override
public void filter() {
System.out.println("FitImpl filter.");
}
}
@Test
public void testFit0{
Fit fit=(Fit)super.getBean("aspectBiz");
fit.filter0;
}
// 输出
FitImpl filter.
advisor就像一个小的自包含的方面,只有一个advice
切面自身通过一个bean表示,并且必须实现某个advice 接口,同时,advisor也可以很好的利用AspectJ的切入 点表达式
Spring通过配置文件中<aop:advisor>元素支持advisor 实际使用中,大多数情况下它会和transactional advice 配合使用
为了定义一个advisor的优先级以便让advice可以有序, 可以使用order属性来定义advisor的顺序
<?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"
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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.imooc.aop.schema">
</context:component-scan>
<aop:config>
<aop:aspect id="concurrentOperationRetry"
ref="concurrentOperationExecutor">
<aop:pointcut id="idempotentOperation"
expression="execution(* com.imooc.aop.schema.advisors.service.*.*(..)) " />
<aop:around pointcut-ref="idempotentOperation"
method="doConcurrentOperation" />
</aop:aspect>
</aop:config>
<bean id="concurrentOperationExecutor"
class="com.imooc.aop.schema.advisors.ConcurrentOperationExecutor">
<property name="maxRetries" value="3" />
<property name="order" value="100" />
</bean>
</beans>
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.core.Ordered;
import org.springframework.dao.PessimisticLockingFailureException;
public class ConcurrentOperationExecutor implements Ordered {
private static final int DEFAULT_MAX_RETRIES = 2;
private int maxRetries = DEFAULT_MAX_RETRIES;
private int order = 1;
public void setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
int numAttempts = 0;
PessimisticLockingFailureException lockFailureException;
do {
numAttempts++;
System.out.println("Try times : " + numAttempts);
try {
return pjp.proceed();
} catch (PessimisticLockingFailureException ex) {
lockFailureException = ex;
}
} while (numAttempts <= this.maxRetries);
System.out.println("Try error : " + numAttempts);
throw lockFailureException;
}
}
import org.springframework.dao.PessimisticLockingFailureException;
import org.springframework.stereotype.Service;
@Service
public class InvokeService {
public void invoke() {
System.out.println("InvokeService ......");
}
public void invokeException() {
throw new PessimisticLockingFailureException("");
}
}
@Test
public void testSave0{
InvokeService service=super.getBeaninvokeService");
service.invoke0;
System.out printin0.
service.invokeException0;
}
// 输出
Try times:1
InvokeService...
Try times:1
Try times:2
Try times:3
Try times:4
Try error:4