Spring 基础知识
Spring
Spring就是一个Bean工厂,Spring容器中的产品就是bean,Sprint能生产什么产品,完全取决于配置文件中的配置;对于开发者来说,使用Spring框架主要做两件事:开发bean,配置bean。对于Spring容器来说,它要做的是,根据配置文件来创建bean实例(Ioc:Inversion of Control),并调用bean实例的方法完成“依赖注入”(Dependency Injection)。Spring(特点)包括控制反转、依赖注入、AOP、与多种持久层技术整合。
一、Bean的装配
Spring具有非常大的灵活性,它提供了三种主要的装配机制:
1) 在XML中进行显式配置。
2) 在Java中进行显式配置。
3) 隐式的bean发现机制和自动装配(注解)。
1:在XML中进行显示配置
2:在Java中进行显式配置
2.1 在进行显式配置时,JavaConfig是更好的方案,因为它更为强大、类型安全并且对重构友好。因为它就是Java代码,就像应用程序中的其他Java代码一样。
@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。
使用JavaConfig声明Bean:
@Bean注解会告诉Spring这个方法将会返回一个对象,该对象要注册为Spring应用上下文中的bean。方法体中包含了最终产生bean实例的逻辑。
默认情况下,bean的ID与带有@Bean注解的方法名是一样的。在本例中,bean的名字将会是sgtPeppers。如果你想为其设置成一个不同的名字的话,那么可以重命名该方法,也可以通过name属性指定一个不同的名字。
1 package soundsystem; 2 import org.springframework.context.annotation.Bean; 3 import org.springframework.context.annotation.Configuration; 4 5 @Configuration 6 public class CDPlayerConfig { 7 8 //在JavaConfig中声明bean 9 @Bean(name="loneyHearsClubBand") 10 public CompactDisc compactDisc() { 11 return new SgtPeppers(); //这里源码没有贴出,CompactDisc是一个接口,SgtPeppers是这个接口的一个实现类 12 } 13 14 15 @Bean 16 public CDPlayer cdPlayer(CompactDisc compactDisc) { //JavaConfig的注入: 17 return new CDPlayer(compactDisc); 18 } 19 20 }
cdPlayer()方法请求一个CompactDisc作为参数。当Spring调用cdPlayer()创建CDPlayerbean的时候,它会自动装配一个CompactDisc到配置方法之中。
3:自动装配
3.1 Spring从两个角度来实现自动化装配:
3.1.1 创建bean,定义普通的JAVA类,并在类上加上@Component 注解即可,这样Spring就会把这个当初Bean自动创建实例了。
3.1.2 启用组件扫描(component scanning):Spring会自动发现应用上下文中所创建的bean。
spring会去自动扫描base-package对应的路径或者该路径的子包下面的java文件,如果扫描到文件中带有@Service,@Component,@Repository,@Controller等这些注解的类,则把这些类注册为bean;
组件扫描的启动方式又有两种,
a)一是在spring的xml配置文件中通过 <context:component-scan>标签配置:使用xml方式启动组件扫描,配置完成后,Spring启动就会自动扫描指定包以及子包下的所有Bean类,要注意配置上语义约束(代码5/8/9行);
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 8 http://www.springframework.org/schema/context 9 http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 10 11 <!-- 自动扫描指定包及子包下所有Bean类 --> 12 <context:component-scan base-package="com.linwei.mybeans" use-default-filters="true"/> 13 14 </beans>
注意: 测试的时候发现使用Spring3.0,如果使用了xml配置启用组件扫描,就不能在相同的包下面再使用 @ComponentScan注解 在Java类中启用组件扫描,否则会出现如下图所示的错误,使用Spring4.0.6版本就不会出现这种报错。
测试代码如下:引入相关JAR包,测试可以通过。测试代码如下:
1 package com.linwei.test; 2 3 import static org.junit.Assert.*; 4 5 import org.junit.Test; 6 import org.junit.runner.RunWith; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.test.context.ContextConfiguration; 9 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 11 import com.linwei.mybeans.Person; 12 13 14 @RunWith(SpringJUnit4ClassRunner.class) //创建spring上下文 15 //@ContextConfiguration(classes=CdPlayConfig.class) //告诉Spring在CdPlayConfig类中加载配置 16 @ContextConfiguration(locations="classpath*:/applicationContext.xml") 17 public class CdPlayTest { 18 19 @Autowired 20 private Person p ; 21 22 @Test 23 public void cdShouldNotBeNull(){ 24 // assertNotNull(p); //断言 25 System.out.println(p); 26 } 27 }
b)二是使用注解 @ComponenScan annotation (这中方式在《Spring实战第四版》这本书上按照书上的例子做实验需要使用Spring4.0.6或更新版本,若使用Spring3.0会报错,因找不到bena而无法创建Spring上下文,就是跟上面那个图片一样的错误。)
自定义Bean:
1 package com.linwei.mybeans; 2 3 import org.springframework.stereotype.Component; 4 5 /* 该类使用了@Component注解 6 * 这个简单的注解表明该类会作为组件类,并告知Spring要为这个类创建bean。 7 * 没有必要显式配置SgtPeppersbea 8 */ 9 @Component 10 public class Person { 11 public String name ; 12 13 public String getName() { 14 return name; 15 } 16 17 public void setName(String name) { 18 this.name = name; 19 } 20 21 }
Spring应用上下文中所有的bean都会给定一个ID。上面例子中没有明确地为 Person 设置ID,但Spring会根据类名为其指定一个ID。具体来讲,这个bean所给定的ID为 person,也就是将类名的第一个字母变为小写,如果想为这个bean设置不同的ID,这样使用Component标签 @Component("yongPerson")。
定义AppConfig配置类:
1 package com.linwei.mybeans; 2 3 import org.springframework.context.annotation.ComponentScan; 4 import org.springframework.context.annotation.Configuration; 5 6 /* 7 * AppConfig类并没有显式地声明任何bean,只不过它使用了@ComponentScan注解 8 * 这个注解能够在Spring中启用组件扫描,相当于在xml配置了 context:component-scan 标签; 9 * @ComponentScan默认会扫描与配置类相同的包(当前包以及这个包下的所有子包),查找带有@Component注解的类,Spring中自动为其创建一个bean 10 */ 11 12 @Configuration 13 @ComponentScan //该注解启用组件扫描 14 //@ComponentScan(basePackages="com.linwei.mybeas","com.linwei.otherBeans") //指定扫描基础包位置 15 //@ComponentScan(basePackageclasses={Person.class,Animal.class}) //数组中类所在的包作为扫描的基础包位置 16 public class AppConfig { 17 18 }
定义测试类:
1 package com.linwei.test; 2 3 import static org.junit.Assert.*; 4 5 import org.junit.Test; 6 import org.junit.runner.RunWith; 7 import org.springframework.beans.factory.annotation.Autowired; 8 import org.springframework.test.context.ContextConfiguration; 9 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 10 11 import com.linwei.mybeans.AppConfig; 12 import com.linwei.mybeans.Person; 13 14 15 @RunWith(SpringJUnit4ClassRunner.class) //创建spring上下文 16 @ContextConfiguration(classes=AppConfig.class) //告诉Spring在CdPlayConfig类中加载配置 17 public class CdPlayTest { 18 19 @Autowired 20 private Person p ; 21 22 @Test 23 public void cdShouldNotBeNull(){ 24 // assertNotNull(p); //断言 25 System.out.println(p); 26 } 27 }
Spring配置文件:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:p="http://www.springframework.org/schema/p" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 8 http://www.springframework.org/schema/context 9 http://www.springframework.org/schema/context/spring-context-3.0.xsd"> 10 11 12 </beans>
运行结果:
3.1.3 自动装配(autowiring):通过使用 @Autowired 注解实现自动装配;
构造器上添加了@Autowired注解,这表明当Spring创建CDPlayerbean的时候,会通过这个构造器来进行实例化并且会传入一个可设置给CompactDisc类型的bean。
package soundsystem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component public class CDPlayer implements MediaPlayer { private CompactDisc cd; @Autowired public CDPlayer(CompactDisc cd) { this.cd = cd; } public void play() { cd.play(); } }
@Autowired注解不仅能够用在构造器上,还能用在属性的Setter方法上。比如说,如果CDPlayer有一个setCompactDisc()方法,那么可以采用如下的注解形式进行自动装配
@Autowired public void setCompactDisc(CompactDisc cd){ this.cd = cd ; }
二、依赖注入DI(Dependency Injection)
2、配置依赖(两种注入方式):setter注入、构造注入
* 2.1 设值(setter)注入:使用属性的set方法来注入被依赖对象,并在bean.xml中配置好<bean.../>的子表签<proterty.../>,简单直观;
2.1.1 Spring可以为任何java对象注入任何类型的属性,只要该Java对象为该属性提供了对应的set方法即可。当我们在XML文件中使用<proterty.../> 元素为配置依赖注入时,其底层的本质就是调用该Bean对象的Setter方法,每个<proterty.../>对应调用一次setter方法。
2.1.2 设置属性的值(value/ref/bean/list、set、map、props):
- value:用于指定字符串类型、基本数据类型的值; 如:下配置文件:
<bean id="exampleBean" class="com.crazyit.app.service.ExampleBean"> <!-- 指定int类型的值 --> <property name="integerProperty" value="1" /> <!-- 指定 double 类型的值 --> <property name="doubleProperty" value="2.3" /> </bean>
- ref:用于指定引用类型的属性值;
<bean id="steelAxe" class="com.crazyit.app.service.impl.SteelAxe" /> <bean id="chinese" class="com.crazyit.app.service.impl.Chinese"> <!-- 引用容器中另一个bean --> <property name="axe" ref="steelAxe" /> </bean>
- 自动装配注入:Spring可以自动装配Bean与Bean之间的依赖关系,即无须使用ref显示指定依赖Bean。通过<beans../> 元素的 default-autowire属性指定,也可通过<bean../>元素的autowire属性指定,同一个容器里,可以让某些Bean自动装配,而另一些Bean不用自动装配; autowire 可以分为 byType 和 byName;
- byName:根据属性名自动装配,BeanFactory查找容器中全部Bean,找出其中 id 属性与属性同名的Bean来完成注入,如果找不到就不进行注入;
<?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/benas" 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-3.0.xsd> <bean id="chinese" class="com.crazyit.app.service.impl.Chinese" autowire="byName" /> <bean id="gundog" class="com.crazyit.app.service.impl.Gundog" />
</beans>
上面的配置文件指定了byName自动装配策略,则要求com.crazyit.app.service.impl.Chinese类中提供如下依赖注入方法:
/* * 依赖关系必需的setter方法,因为要通过名字装配 set + Bean名,首字母大写 */ public void setGundog(Dog dog){ this.dog = dog; }
-
- byType:根据属性类型自动装配,BeanFactory查找容器中全部Bean,如果找到一个与依赖属性类型相同的Bean,就自动注入该属性;如果找不到就不进行装配,如果找到多个就会抛出异常;
<?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/benas" 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-3.0.xsd> <bean id="chinese" class="com.crazyit.app.service.impl.Chinese" autowire="byType" /> <bean id="gundog" class="com.crazyit.app.service.impl.Gundog" /> </beans>
/* * 依赖关系必需的setter方法 * 使用按类型自动装配,setter方法的参数类型与容器中的Bean类型相同 * 程序中Gundog实现了Dog接口; */ public void setDog(Dog dog){ this.dog = dog; }
- 注入集合值
如果Bean的属性是个集合,则可以使用元素,<list.../>、<set.../>、<map.../>、<props.../>元素分别设置类型为List、Set、Map、Properties的集合属性值;
<?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/benas" 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-3.0.xsd> <!--定义Bean --> <bean id="stoneAxe" class="com.crazyit.app.service.impl.StoneAxe" /> <bean id="steelAxe" class="com.crazyit.app.service.impl.SteelAxe" /> <!--定义chinese Bean --> <bean id="chinese" class="com.crazyit.app.service.impl.Chinese" > <property name="schools"> <!-- 为 List 属性配置属性值 --> <list> <value>小学</value> <value>中学</value> <value>大学</value> </list> </porperty> <property name="scores"> <!-- 为 Map 属性配置属性值 --> <map> <entry key="数学" value="87" /> <entry key="语文" value="89" /> <entry key="英语" value="80" /> </map> </porperty> <property name="phaseAxes"> <!-- 为 Map 属性配置属性值 --> <map> <entry key="原始社会" value-ref="stoneAxe" /> <entry key="农业社会" value-ref="steelAxe" /> </map> </porperty> <property name="health"> <!-- 为 Porperties 属性配置属性值 --> <props> <prop key="身高" >175</prop> <prop key="体重" >50</prop> </props> </porperty> <property name="axes"> <!-- 为 Set 属性配置属性值 --> <set> <value>普通字符串</va;ue> <bean class="com.crazyit.app.service.impl.SteelAxe" /> <ref local="stoneAxe"> </set> </porperty> </bean> </beans>
由于集合元素可以是基本数据类型,也可以是引用数据类型,所以<list.../>、<set.../>、<map.../>可接受如下子元素;(Properties类型的key和value都只能是字符串)
- value:指定集合元素是基本数据类型或者字符串类型;
- ref: 指定集合元素是容器中另一个Bean实例;
- bean: 指定集合元素是一个嵌套Bean;
- list/set/map及props:指定集合元素又是集合;
* 2.2 构造注入:提供一个构造注入所需的带参数的构造方法,并在bean.xml中配置好<bean.../>的子表签<construtor.../>,通过此方法可以控制注入顺序;
3、ApplicationContext 是 BeanFactory 的子接口,增强了BeanFactory的功能,因此ApplicationContext完全可以作为Spring的容器来使用,而且功能更强。(也就是说BeanFactory 和 ApplicationContext都可作为Spring的容器)
4、ApplicationContext的事件处理机制是观察者模式的实现,通过ApplicationEvent类(容器事件)、ApplicationListener接口(监听器),可以实现ApplicationContext的事件处理。如果容器中有一个ApplicationListener Bean,每当ApplicationContext发布ApplicationEvent时,ApplicationListener Bean 将自动被触发。
5、Spring容器中Bean的作用域:
通过Spring创建Bean实例时,不仅可以完成Bean的实例化工作,还可以为Bean指定作用域。
- singleton 单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例;【常用,且是Spring的缺省作用域】
- prototyp 原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例;【常用】
- request 对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效;
- session 对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效;
- global session 每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效;
<bean id="empServiceImpl" class="cn.csdn.service.EmpServiceImpl" scope="singleton">
Java在创建实例时,需要进行内存申请,销毁实例时,需要完成垃圾回收,这些工作都将会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大,而singleton作用域的实例一旦创建成功,可以重复使用,因此,除非必要,否则尽量避免将Bean设置成prototype作用域。
request、session作用域的Bean只对Web应用才真正有效,实际上通常只会将Web应用的控制器Bean才制定成request作用域,而且必须在Web应用中增加额外的配置才会生效哦,必须将HTTP请求对象绑定到为该请求提供服务的线程上,这使得具有request和session作用域的Bean实例能够在后面的调用链中被访问到。
为此,可以有两种方式配置:采用Listener配置,或采用Filter配置。采用任何一种配置,程序就可以在spring配置文件中使用request,session作用域了。
a、在Web应用的web.xml文件中增加如下Listener配置:
<web-app> ... <listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> </listener> </web-app>
b、Filter配置方式:
<web-app> ... <filter> <filter-name>requestContextFilter</filter-name> <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class> </filter> ... </web-app>
6、Spring的注解介绍:“零配置”即只使用注解,不用在XML文件中进行配置了。
如果不使用XML配置,Spring怎么知道应该把哪些java类当成Bean类处理呢?这就需要使用Annotation(注解)了,Spring通过使用一些特殊的Annotation来标注Bean类,Spring提供了如下借个Annotation来标注Spring Bean;
- @Component :标注一个普通的Spring Bean类;
- @Controller :标注一个控制器组组件类;
- @Service : 标注一个业务逻辑组件类;
- @Repository :标注一个DAO组件类;
如果我们定义一个普通的Java bean,则直接使用 @Component 标注即可,但如果用@Controller、@Service、@Repository来标注这些Bean类,这些Bean类将被作为特殊的JavaEE组件对待,也许更好地被工具处理,或与切面进行关联。 在Spring未来的版本中,@Controller、@Service、@Repository也许还能携带更多的语义,因此,如果需要在JavaEE应用中使用这些标注时,尽量考虑使用@Controller、@Service、@Repository来代替通用的 @Component 标注。
接下来需要在Spring 的配置文件中指定搜索路径,Spring将会自动搜索该路径下的所有Java类,并根据这些java类来创建bean实例。
<?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/benas" 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-3.0.xsd> <!-- 自动扫描指定包及子包下所有Bean类 --> <context:component-scan base-package="com.crazyit.app.service"/> </beans>
@Resurce 有一个name属性,在默认情况下,Spring将这个值解释为需要被注入的Bean实例的名字,也就是说,使用 @Resurce 与 <property.../>元素的 ref 属性有相同的效果,如下Bean类:
@Resurce Annotation 指定 stoneAxe 注入set方法,也就是将容器中的 stoneAxe Bean 作为setAxe方法的参数传入;
@Componet public class Chinese implements Person{ private Axe axe; @Resurce(name="stoneAxe") public void setAxe(Axe axe){ this.axe = axe; } //实现Person的useAxe方法 public void useAxe(){ //调用axe的chop()方法; System.out.println(axe.shop()); } }
@Resurce 不仅可以修饰setter方法,也可以直接修饰 filed ,使用 @Resurce 时还可以忽略name属性(如果修饰filed省略name属性,则name属性默认与filed同名;如果修饰setter方法省略name属性,则name属性默认是该setter方法去掉前面set子串、首字母小写后得到的字符串)。
@Componet public class Chinese implements Person{ @Resurce(name="stoneAxe") private Axe axe; //实现Person的useAxe方法 public void useAxe(){ //调用axe的chop()方法; System.out.println(axe.shop()); } }
二、AOP
1、Spring的两种‘后处理器’:
- Bean后处理器:对容器中的Bean进行后处理,对Bean功能进行额外强化 ;
- 容器后处理器:这种后处理器对IoC容器进行后处理,用于增强容器功能;
a)Bean后处理器会在Bean实例创建成功之后,对Bean实例进行进一步的增强处理,Bean后处理器必须实现BeanPostProcessor接口,BeanPosstProcessor接口包含下面两个方法;
- Object postProcessBeforInitalization(Object bean,String name)throws BeansException;
- Object postPorcessAfterInitalization(Object bean,String name)throws BeansException;
Bean后处理器必须实现这两个方法,才能在目标Bean初始化之前、之后分别被回调,用于对容器中的Bean实例进行增强处理。
1 public class MyBeanPostProcessor implements BeanPostProcessor{ 2 3 @Override 4 public Object postProcessBeforeInitialization(Object bean, String beanName) 5 throws BeansException { 6 // TODO Auto-generated method stub 7 System.out.println("Bean后处理器在初始化之前对"+beanName+"进行增强处理..."); 8 return null; 9 } 10 11 @Override 12 public Object postProcessAfterInitialization(Object bean, String beanName) 13 throws BeansException { 14 // TODO Auto-generated method stub 15 System.out.println("Bean后处理器在初始化之后对"+beanName+"进行增强处理..."); 16 if(bean instanceof HelloWorld){ 17 HelloWorld hello = (HelloWorld)bean; 18 hello.setInfo("后处理修改bean实例的属性值"); 19 } 20 return null; 21 } 22 23 }
b)容器后处理器;必须要实现BeanFactoryPostProcessor接口,该接口包含下面这个方法:
postProcessBeanFactory(ConfigurableBeanFactory beanFractory);使用ApplicationContext作为Spring容器(BeanFactory也可作为Spring的容器),那么该Spring容器会自动搜索容器所有实现了BeanPostPorcessor接口的类,并将它们注册成Bean后处理器,也会自动搜索容器中所有实现了BeanFactoryPostProcessor接口的类,并将它注册成容器后处理器。(如果使用BeanFactory作为容器,则后处理器的Bean在XML配置文件里配置,配置方法和普通bean的配置一样;)
Spring已经自己提供了几个常用的容器后处理器,程序可以配置多个容器后处理器,可设置order属性来控制容器后处理器的执行顺序。
- PropertyPlaceholderConfigurer:属性占位符配置器(下面会介绍);
- PorpertyOverrideConfigurer:重写占位符配置器(自己查资料);
下面详细介绍一下属性占位符配置器:
PropertyPlaceholderConfigurer,它负责读取Propertries属性文件里的属性值,并将这些属性值设置成Spring配置文件的元数据。这种配置的优势是可以将部分类似的配置(比如数据库的URL,用户名密码)放倒特定的属性文件中,如果只需求修改这部分配置,则无须修改Spring配置文件,修改属性文件即可。
如下applicationContext.xml配置了PropertyPlaceholderConfigurer后处理器,在配置数据源Bean时,使用了属性文件中的属性值。
1 <xml version="1.0" encoding="GBK"?> 2 <!-- Spring配置文件根元素,使用spring-beans-3.0.xsd语义约束 --> 3 <beans xmls:....> 4 5 ... 6 <bean calss="org.springframwork.beans.factory.config.PropertyPlaceholderConfigurer"> 7 <property name="locations"> 8 <list> 9 <value>dbconn.properties</value> 10 <!-- 如果有多个属性文件,依次在下面列出来 --> 11 <value>wawa.properties</value> 12 </list> 13 </property> 14 </bean> 15 ... 16 <bean id="dataSource" calss="com.mchange.v2.c3p0.ComboPooledDataSource" destory-method="close"> 17 <property name="driverClass" value="${jdbc.driverClassName}"/> 18 <property name="jdbcUrl" value="${jdbc.url}"/> 19 <property name="user" value="${jdbc.username}"/> 20 <property name="password" value="${jdbc.password}"/> 21 </bean> 22 23 </beans>
可以看到上面配置文件中,配置driverClass、jdbcUrl等信息时,没有直接设置属性值,这表明Spring容器将从propertyConfigurer指定属性文件中搜索这些Key对应的value,并为该Bean的属性值设置这些Value值。ApplicationContext会自动检测部署在容器中的容器后处理器,无须额外注册,因此,只需提供如下Properties文件即可;
1 jdbc.dirverClassName=com.mysql.jdbc.Driver 2 jdbc.url=jdbc:mysql://localhost:3306/javaee 3 jdbc.username=root 4 jdbc.password=123456
2、AOP的概念
面向切面编程的术语:
- 切面Aspect:业务流程运行的某个特定步骤,也就是应用运行过程中的关注点,关注点可以横切多个对象。
- 连接点Joinpoint:程序执行过程中明确的点,如方法的调用,异常的抛出,Spring AOP中,连接点只是方法的调用;
- 增强处理Advice:AOP框架在特定的切入点执行的增强处理。处理有 around 、befor 、after等类型;
- 切入点Pointcut:可以插入增强处理的连接点。也就是,当某个连接点满足制定要求时,该连接点将被添加增强处理,该连接点也就成了切入点了;例如:
pointcout xxxPointcut():excution(void H*.say*())
每个方法的调用都是相当于连接点,但是,如果该方法属于 H 开头的类,并且方法名以 say 开头,该方法的执行就变成了切入点。
- 目标对象:被AOP框架增强处理的对象,也被成为被增强对象。
- AOP代理:AOP框架创建的对象,简单说,代理就是对目标对象的加强,Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。
Spring默认使用Java动态代理来创建AOP代理,这样就可以为任何接口实例创建代理了。Spring推荐使用面向接口编程,因此业务对象通常都会实现一个或多个接口,此时默认使用JDK动态代理,但也可以强制使用CGLIB。
AOP编程的三个部分:
(1)定义普通业务组件;
(2)定义切入点,一个切入点可能横切多个业务组件;
(3)定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作。
上面第一个部分是最平常不过的事情,所以无须额外说明,那么进行AOP编程的关键就是定义切入点和定义增强处理。一旦定义了切入点和增强处理,AOP框架就会自动生成AOP代理,Spring有如下两种方式定义切入点和增强处理:
(a)基于Annotation的零配置方式,使用@Aspect,@Pointcut等Annotation来标注切入点和增强处理。
(b)基于XML配置文件管理方式;使用Spring配置文件来定义切入点和增强处理。
2.1 使用AspectJ实现AOP
AspectJ是一个基于JAVA语言的AOP框架,提供了强大的AOP功能,AspectJ采用的是编译时增强,所以AspectJ需要使用自己的编译器来编译Java问价,还需要织入器;而Spring AOP采用的是运行时生成动态代理的发那个是来增强目标对象,Spring只是使用了和AspectJ一样的注解,Spring框架可识别并根据这些Annotation来生成AOP代理。但是并没有AspectJ的编译器或者织入器。
2.2 Spring AOP
Spring中的AOP代理由Spring的IoC容器负责生成、管理,其依赖关系也由IoC容器负责管理,Spring默认用Java动态代理来创建AOP代理,也可以使用CGLIB代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理。
Spring目前仅支持将方法作为连接点,如果需要把对Field的访问和更新也作为增强处理的连接点,则可以考虑使用AspectJ。
2.2.1 基于Annocation的零配置方式
1.修改Spring配置文件,启用AspectJ支持;
<?xml version="1.0" encoding="GBK"?> <beans xmlns="http://www.springframework.org/schema/benas" 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-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <!-- 启动@AspectJ支持,自动增强 --> <aop:aspectj-autoproxy/> <bean class="org.springframework.aop.aspectj.annotion.AnnotationAwareAspectJAutoProxyCreator"/> </beans>
上面配置文件中的 AnnotationAwareAspectJAutoProxyCreator 是一个Bean后处理器,该Bean后处理器将会为容器中的Bean生成AOP代理。
2.定义切面Bean
使用@Aspect标注一个java类,Spring将会自动识别该java类,并将该Bean作为切面处理。这种bean与普通Bean的配置没有任何区别。一样使用<bean.../>元素配置;
当我们使用@Aspect来修饰一个java类后,spring将不会把该Bean当作组件Bean处理,因此负责自动增强的后处理Bean将会略过该Bean,不会对该Bean进行任何增强处理。
3.定义增强处理
3.1定义Before增强处理
当我们在一个切面类里使用@Before来标注一个方法时,该方法将作为Befroe增强处理。使用@Before标注时,通常需要制定一个value属性值,该属性值指定一个切入点表达式(即可以是一个已有的切入点,也可以直接定义切入点表达式),用来制定该增强处理将被织入到哪些切入点。
1 //定义切面 2 @Aspect 3 public class BeforeAdviceTest{ 4 //匹配org.crazyit.app.service.impl 包下所有类的所有方法的执行作为切入点 5 @Before("excution(* org.crazyit.app.service.impl.*.*(..))") 6 public void authority(){ 7 System.out.println("模拟执行权限检查"); 8 } 9 10 }
3.2定义AfterReturing增强处理
1 //定义一个切面 2 @Aspect 3 public class AfterReturningAdiviceTest{ 4 //匹配org.crazyit.app.service.impl包下所有类的所有方法的执行作为切入点 5 @AfterReturining(returning="rvt",pointcut="excution(* org.crazyit.app.service.imp.*.*(..))") 6 public void log(){ 7 System.out.println("获取目标方法返回值:"+rvt); 8 System.out.println("模拟记录日志功能...."); 9 } 10 11 }
3.3定义AfterThrowing增强处理
3.4定义After增强处理
3.5Around增强处理
2.2.2 基于XML配置文件的处理 <aop:config.../>元素
实际上,使用XML配置文件和上面的@AspectJ方式的实质是一样的,同样需要指定相关数据:配置切面、切入点、增强处理,所需要的信息完全一样,只是提供这些信息的位置不同而已,使用XML配置方式是通过XML文件来提供这些信息;使用@AspectJ方式则是通过Annotation来提供这些信息;
(一般情况下使用XML配置方式多一些,因为增强处理可能是别人已经写好的,你需要用的时候直接配置上去即可,特别是一些已经封装好的增强处理;除非是需要你自己写切面类和增强处理。)
需要注意的是:当我们使用<aop:config.../>方式进行配置时,可能与Spring的自动代理方式相互冲突,例如我们使用了<aop:aspectj-autoproxy/>或者类似方式显式启动了自动代理,则它可能会导致问题(例如有些增强处理没有被织入),因此建议:要么全部使用<aop:config.../>配置方式,要么全部使用自动代理方式,不要把两者混合使用。
a)定义一个普通的Bean,然后配置成切面Bean
当普通Bean定义完成后,通过在<aop:aspect.../>元素中使用ref属性来引用该Bean,就可以将该Bean转换成一个切面Bean了。
<aop:config> <!-- 将容器中的afterAdviceBean转换成切面Bean,切面Bean的新名称为:afterAdviceAspect --> <aop:aspect id="afterAdviceAspect" ref="afterAdviceBean"> <!-- 定义一个Before增强处理直接制定切入点表达式,以切面Bean中的authority()方法作为增强处理方法 --> <aop:befor pointcut="excution(* org.crazyit.app.service.impl.*.*(..))" method="authority" /> ... </aop:aspect> </aop:cofig> <!-- 定义一个普通Bean实例,该Bean实例将作为Aspect Bean --> <bean id="afterAdviceBean" class="lee.AfterAdviceTest" />
<aop:aspect.../>元素的属性:id 定义该切面的标识名;ref 指定所引用的普通Bean作为切面Bean;order指定该切面的Bean的优先级,order属性值越小,该切面的优先级越高。
b)配置增强处理(上面xml配置蓝色部分)
使用XML一样可以配置Before、After、AfterReturning、AfterThrowing和Around 5种增强处理:
<aop:befor.../> <aop:after.../> <aop:after-returning.../> <aop:after-throwing.../> <aop:around.../> 这五个元素都不支持子元素,但通常可指定如下属性:
- pointcut:指定一个切入表达式,Spring将在匹配该表达式的连接点时织入该增强处理;
- pointcut-ref:指定一个已经存在的切入点名称,通常pointcut和poincut-ref两个属性只需要其中之一;
- method:指定一个方法名,指定该切面Bean的这个方法作为增强处理。
- throwing:该属性只对<after-throwing../>元素有效,用于指定一个形参名,AfterThrowing增强处理方法可通过该形参访问目标方法所抛出的异常;
- returning:该属性只对<after-returning../>元素有效,用于指定一个形参名,AfterReturning增强处理方法可通过该形参访问目标方法的返回值。
既然应用选择使用XML配置来配置增强处理,所有切面类里定义面、切入点和增强处理的Annotation全都可删除了。
c)配置切入点
类似与@AspectJ方式,允许定义切入点来重用切入点表达式(上面2.2.1中很好理解并没有记录下来),Spring提供了<aop:pointcut.../>元素来定义切入点。当把<aop:pointcut.../>元素作为<aop:config.../>的子元素定义时,表明该切入点可被多个切面共享;当把<aop:pointcut.../>元素作为<aop:aspect.../>子元素定义时,表明该切入点只能在对应的切面中有效。
<!-- 定义一个简单的切入点 --> <aop:pointcut id="mypointcut" expression="exuction{* org.crazyit.app.service.impl.*.*(..)}">