1.1.1 Spring简介
Spring是一个开源的控制反转(IoC)和面向且切面(AOP)的容器框架。
IOC控制反转:应用本身不负责以来对象的创建和维护,以来对象的创建及维护由外部容器负责。这样控制权就转移到了外部容器。
Dependency Injection依赖注入:在运行期,由外部容器动态地将以来对象注入到组件中。
事务控制全部交给spring处理,不用手工编写事务的创建和提交
1.1.2 Spring配置和搭建
Eclipse下的配置
下载了spring-framework-2.5.6包,把dist文件夹下的spring.jar添加到工程,还要添加一个jar是common-logging的,在hibernate学习的时候下载过了,也添加进去。
在src目录下新建beans.xml
1 | <?xml version="1.0" encoding="UTF-8"?> |
初始化spring容器
1 | ApplicationContext context = new ClassPathXmlApplicationContext( new String[] {"services.xml", "daos.xml"});//可以通过数组传递多个配置文件。 |
下面使用JUnit4进行测试,new->JUnit Test
(不要勾选setUpBeforeClass)
SprintTest.java
1 | SprintTest.java |
运行以后测试成功
下面写一个bean
PersonService.java 放在src中service包下
1 | package service.impl; |
PersonServiceBean.java 放在src中service.impl包下
1 | package service; |
下面配置beans.xml
在没联网的情况下需要添加提示文件才会出现关键字提示。
windows->perferences->XML->XML Catalog
add
1 | Location:在spring包下找到dist/resources/spring-beans-2.5.xsd |
完成添加。
在beans标签中间添加
1 | <bean id="personService" class="service.PersonServiceBean"></bean> |
使用bean标签的时候,id和name的区别:id不可以包含特殊字符,name可以。
SpringTest.java
1 | package junit.test; |
运行成功
1.1.3 Spring管理bean的原理
简单的一套管理bean的代码,就是一个模拟的ApplicationContext类。
主要方法有两个
1 | this.readXML(filename);//读取XML配置文件 |
主要成员变量
1 | private List<BeanDefinition> beanDefines = new ArrayList<BeanDefinition>();//保存XML中的bean信息,包括id和class,组成BeanDefinition类 |
具体的代码实现不细化研究了,大概流程:
首先使用dom4j读取XML中的bean标签,把id和class保存在beanDifines数组中,然后在instanceBeans()方法中遍历beanDifines数组,取出类名,利用发射技术进行实例化,如Class.forName(beanDefinition.getClassName()).newInstance());并把实例化对象保存在sigletons的哈希表中。完成实例化
接下来就是简单的get方法来取出对应的bean供其他类调用。
可以发现bean的实例化就是在创建ApplicationContext对象的时候,通过构造方法进行实例化的。
代码:
ItcastClassPathApplicationContext.java
1 | package junit.test; |
BeanDefinition.java
1 | package junit.test; |
测试代码
1 | ItcastClassPathXMLApplicationContext ctx = new ItcastClassPathXMLApplicationContext("beans.xml"); |
1.1.4 三种实例化bean的方式
1.使用类构造器实例化
1 | <bean id="personService" class="service.impl.PersonServiceBean"></bean> |
2.使用静态工厂方法实例化
beans.xml
1 | <bean id="personService2" class="service.impl.PersonServiceBeanFactory" factory-method="createPersonServiceBean"></bean> |
PersonServiceBeanFactory.java
1 | 1. public static PersonServiceBean createPersonServiceBean(){ |
3.使用实例工厂方法实例化
beans.xml
1 | <bean id="personServiceFactory" class="service.impl.PersonServiceBeanFactory"></bean> |
PersonServiceBeanFactory.java
1 | 1. public PersonServiceBean createPersonServiceBean2(){ |
三种方式测试成功
绝大部分都使用第一种实例化方法
1.1.5 spring管理bean的作用域
来看一段代码
1 | ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); |
输出结果为true,说明getBean方法获取的是单实例
那么就来看bean的作用域
bean标签的scope属性来设置
singleton
默认情况下的单例模式,每次调用getBean方法获取的都是同一个bean对象
默认情况喜爱会在容器启动时初始化bean,但我们可以指定Bean节点的lazy-init属性来延迟初始化bean,这时候,只有第一次获取bean才会初始化bean。
1 | <bean id="xxx" class="xxx" lazy_init="true"/> |
如果对所有bean都应用延迟初始化
1 | <beans default-lazy-init="true"...> |
prototype
每次从容器获取bean都是一个新的对象
request
session
global session
1.1.6 spring管理bean的生命周期
从前面可以看到,
singleton模式下bean的实例化的时机是在ApplicationContext实例化的时候进行实例化,然而设置lazy-init=true的情况下在getBean方法调用的时候进行实例化。
prototype模式下bean的实例化时机是在getBean方法调用的时候进行实例化。
如果我们需要在bean初始化的时候,打开数据库资源等连接,那么指定一个初始化方法,这样在bean实例化的时候就会自动初始化。同样的指定一个销毁方法。
beans.xml
1 | <bean id="personService" class="service.impl.PersonServiceBean" init-method="init" destroy-method="destroy"></bean> |
PersonServiceBean.java
1 | public void init(){ |
SprintTest.java
1 | 1.AbstractApplicationContext context = new ClassPathXmlApplicationContext("beans.xml"); |
- context.close();
测试结果输出
inti初始化方法destroy销毁方法
lazy-init应该是一个优化spring很好的东西,就像hibernate里的懒加载,有些bean可能一直没有用到过,根本没必要初始化,但是视频里说尽量使用lazy-init=false,为了发现所有bean可能出现的错误,难道测试的时候要这么做?另外JUnit的测试也不是很明白。。。感觉和一般的差不多啊
1.1.7 剖析Spring依赖注入的原理
通过set方法注入
1 | package dao.impl; |
PersonServiceBean.java
1 | • package service.impl; |
beans.xml
1 | • <?xml version="1.0" encoding="UTF-8"?> |
SpringTest.java
1 | • package junit.test; |
运行结果:
dao.impl.PersonDaoBean add方法
还有一种内部bean的注入方式
1 | ·<bean id="personService" class="service.impl.PersonServiceBean" lazy-init="false" init-method="init" destroy-method="destroy"> |
即在property标签内部添加bean标签,在bean标签里写出类名即可。效果一样。
实际上spring的操作流程是吧bean中的property也保存在一个数组中,初始化的时候遍历数组,找到需要注入的bean,利用反射技术调用set方法,参数为配置文件中的ref应用的bean,就完成了注入。
1.1.8 Spring装配基本属性的原理
上一篇的注入都是对象的注入,这篇来分析基本属性的注入。
以String类型为例
PersonServiceBean.java中加入
1 | private String name; |
beans.xml
1 | ·<bean id="personService" class="service.impl.PersonServiceBean" lazy-init="false" init-method="init" destroy-method="destroy"> |
其他基本类型类似,均采用value属性方式赋值。
1.1.9 Spring如何装配各种集合类型的属性
set: private Set<String> set;
1 | · <property name="set"> |
list:
private List<String> list;
1 | · <property name="list"> |
properties:
private Properties properties;
1 | · <property name="properties"> |
map: peivate Map<String,String> map;
1 | · <property name="map"> |
1.1.10 使用构造器装配属性
PersonServiceBean.java
1 | • package service.impl; |
beans.xml
1 | • <bean id="personService" class="service.impl.PersonServiceBean"> |
index指明了参数的位置,加了index就不用加type来说明了。
1.1.11 用@Resource注解完成属性装配
java代码注入配置,需要spring解压文件夹下lib/j2ee/common-annotation.jar这个库文件,添加玩以后,修改beans.xml
1 | • <?xml version="1.0" encoding="UTF-8"?> |
由于现在家里上spring的网站总是上不去,如果要出现标签提示,那么和前面一样在本地添加spring-context-2.5.xsd
现在配置工作完成了,现在开始用java代码来完成注入
@Autowired方式:默认按类型装配,默认情况下它要求以来对象必须存在,如果允许null值,可以设置它required属性为false。如果我们想使用按名称装配,可以结合@Qualifier注解一起使用
1 | @Autowired @Qualifier("personDaoBean") |
@Resource方式:默认按名称装配,名称可以通过@Resource的name属性指定,如果没有指定的name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找以来对象,当注解标注在属性的setter方法上,即迷人取属性名作为bean名称寻找以来对象。
1 | @Resource(name="personDaoBean") |
推荐使用@Resource方式,因为@Resource是j2ee里的一个注解,而@AutoWired是spring里的注解,使用@Resource可以降低与框架的耦合度。
beans.xml
1 | • <?xml version="1.0" encoding="UTF-8"?> |
PersonServiceBean.java
1 | • package service.impl; |
运行发现注入成功,string类型就不需要用注解注入了,直接赋值就可以了。
另外吧@Resource放在setter方法上也是可以的,效果一样。
1.1.12 编码剖析@Resource注解的实现原理
ItcastResource.java
1 | • package junit.test; |
PropertyDefinition .java
1 | • package junit.test; |
BeanDefinition.java
1 | • package junit.test; |
ItcastClassPathXMLApplicationContext.java
1 | • package junit.test; |
实际上也就是通过了反射技术来构造对象并且赋值,只是用到了注解的方法,并且利用@interface来构造自定义的注解类型。
1.1.13 @Autowire注解与自动装配
使用了@Autowired的注解方式,这种默认按类型查找符合的bean注入@Autowired **private** PersonDao personDao;
使用@Qualifier注明bean名称注入 @Autowired @Qualifier("personDao") **private** PersonDao personDao;
还可以添加required属性,在没找到bean的情况下,如果required为false,则注入null,required为true,则报错。@Autowired(required=true) @Qualifier("personDao") private PersonDao personDao;
自动装配:
通过bean标签的autowire属性来配置,有5种值
no 不使用自动装配,必须通过ref元素指定依赖,默认设置。
byName 根据属性名自动装配。此选项将检查容器并根据名字查找与
属性完全一致的bean,并将其与属性自动装配。
byType 如果容器中存在一个与指定属性类型相同的bean,那么将与
该属性自动装配;如果存在多个该类型bean,那么抛出异
常,并指出不能使用byType方式进行自动装配;如果没有找
到相匹配的bean,则什么事都不发生,也可以通过设置
dependency-check=”objects”让Spring抛出异常。
constructor 与byType方式类似,不同之处在于它应用于构造器参数。如
果容器中没有找到与构造器参数类型一致的bean,那么抛出
异常。
autodetect 通过bean类的自省机制(introspection)来决定是使用
constructor还是byType方式进行自动装配。如果发现默认的
构造器,那么将使用byType方式。
1.1.14 让Spring自动扫描和管理Bean
让Spring自动扫描和管理Bean
1 | <context:component-scan base-package="cn.test"></context:component-scan> |
其中base-package为需要扫描的包(含子包)
@Service用于标注业务层组件,@Controller用于标注控制层组件(如struts中的action),@Repository用于标注数据访问组件,即DAO组件,而@Component泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。
bean的默认名称是类名,然后把第一个字母改为小写。可以通过@Service(“xxx”)修改bean名称。
这种bean默认是单例的,如果想改变,可以使用@Service(“aaaaa”) @Scope(“prototype”)来改变。
还可以通过@PostConstruct @PreDestroy设置初始化和销毁的函数
1 | • @PostConstruct |
1.1.15 使用JDK中的Proxy技术实现AOP功能
通过代理对象来调用对象的方法,从而做出权限控制。
目标对象必须实现接口才能使用proxy技术创建代理对象。
PersonService.java
1 | • package cn.pf.aop.service; |
PersonServiceBean.java
1 | • package cn.pf.aop.service.impl; |
JDKProxyFactory.java
1 | • package cn.pf.aop; |
AOPTest.java
1 | • package junit.test; |
Proxy.newProxyInstance(this.targetObject.getClass().getClassLoader(), this.targetObject.getClass().getInterfaces(), this);
创建代理对象的时候,加入了该目标对象所有的接口,即对所有的方法进行监听,任何一个方法的调用都会触发代理对象的invoke方法。this表示触发哪个代理对象的invoke方法,这里我们设置当前代理对象。
调用personService的save方法的时候,可以理解为,save方法被监听,进入代理对象的invoke方法,如果user!=null,则invoke方法中调用了personService的save方法,如果user==null,则什么也不做。
通过反射技术调用方法其实可以简单的理解为
xxx.invoke(obj,args)返回的结果是obj.xxx(args)
1.1.16 使用CGLIB实现AOP功能与AOP概念解释
前面的proxy技术必须在类实现了接口的前提下才可以实现权限的控制,cglb可以在类不实现接口的情况下完成。
在spring文件夹下lib/cglib下找到cglib的jar库文件,加入工程。
CGlibProxyFactory.java
1 | • package cn.pf.aop; |
AOPTest.java
1 | • @Test public void proxyTest2(){ |
CGlib的enhance继承了目标类所有非final方法,对这些方法进行覆盖。创建的代理对象是目标对象的子类
1.1.17 使用Spring的注解方式实现AOP入门
首先添加包
``
/spring.jar
/lib/aspectj/aspectjrt.jar
/lib/aspectj/aspectjweaver.jar
/lib/j2ee/common-annotations.jar
/lib/jakarta-commons/common_logging.jar
/lib/cglib/cglib-nodep-2.1-3.jar
``
beans.xml
1 | • <?xml version="1.0" encoding="UTF-8"?> |
PersonService.java和PersonServiceBean.java和上篇一样
MyInterceptor.java
@Ascept声明了切面,即进行拦截的类。
@Pointcut声明了切入点,即进行拦截的方法。
@Pointcut(“execution(* cn.itcast.service...(..))”)
- 代表返回值类型
cn.pf.service 需要拦截的包名
.. 代表队子包的类进行拦截
代表进行拦截的类
代表进行拦截的方法
(..) 代表方法的参数随意
(*代表任意)
下面来测试前置通知,后置通知,最终通知,例外通知以及环绕通知。
MyInterceptor.java
1 | • package cn.pf.aop.service; |
SpringAOPTest.java
1 | • import org.junit.Test; |
控制台输出:
前置通知
进入环绕方法
cn.pf.aop.service.impl.PersonServiceBean save方法
后置通知
最终通知
退出环绕方法
那么如何获得输入参数,返回值,异常呢,那么稍作修改
1 | • @Before("anyMethod() && args(name)") |
其实切面就感觉像servlet里面的过滤器,在方法的前后加上一些关卡,进行筛选,判定权限,通过指定好的一些切面后,才可以真正调用目标对象的方法。
1.1.18 基于XML配置方式声明切面
基于XML配置方式声明切面
与注释方法没什么太大的区别
1 | • <bean id=”orderservice” class=”cn.service.OrderServiceBean” /> |
1.1.19 aspectj的切入点语法定义细节
execution(* cn.pf.aop.service.impl.PersonServiceBean.*(..))
所有非final方法
execution(!void cn.pf.aop.service.impl.PersonServiceBean.*(..))
非void非final方法
execution(java.lang.String cn.pf.aop.service.impl.PersonServiceBean.*(..))
非final且返回类型为String的方法
execution(java.lang.String cn.pf.aop.service.impl.PersonServiceBean.*(java.lang.String,..))
第一个参数为String的非final方法
execution(* cn.pf.aop.service.impl..*.*(..))
对包下所有类进行拦截