Spring框架自学笔记
第一章 简介
Spring是一套建筑标准,比如承重标准、丈量标准,通风标准等,并规范了结构:框架式结构,浇筑式结构,且定义了建筑的每个组成部分名字与接口,比如电力系统的接口是220v,通风系统的孔径必须符合ISO标准等。
攻城狮是建筑的设计师,在基础框架与标准下,设计出符合客户要求的建筑图纸,并决定了使用哪个厂家生产的水泥,砖块,和电力,通风等系统。
程序猿就是泥瓦匠,电工,依照图纸,完成各个部分的堆砌,安装,部署,装饰等等。说白了,程序猿就是一板砖的。
第二章 Spring入门
2.1 先介绍单例模式
(1)创建一个Person接口
package com.interfaceRoom;
public interface Person {
public String sayHello(String name);
public String sayGoodBye(String name);
}
(2)创建一个American具体的Person实现类
package com.entity;
import com.interfaceRoom.Person;
public class American implements Person {
@Override
public String sayHello(String name) {
// TODO Auto-generated method stub
return name+",Hello";
}
@Override
public String sayGoodBye(String name) {
// TODO Auto-generated method stub
return name+",GoodBye";
}
}
(3)创建Chinese具体实现类
package com.entity;
import com.interfaceRoom.Person;
public class Chinese implements Person{
@Override
public String sayGoodBye(String name) {
// TODO Auto-generated method stub
return name+"您好";
}
@Override
public String sayHello(String name) {
// TODO Auto-generated method stub
return name+"下次再见";
}
}
(3)创建工厂类
package com.entity;
import com.interfaceRoom.Person;
public class PersonFactory {
public Person getPerson(String ethnic){
if(ethnic.equalsIgnoreCase("chin")){
return new Chinese();
}else{
return new American();
}
}
}
(4)创建测试类
package com.test;
import com.entity.PersonFactory;
import com.interfaceRoom.Person;
public class FactoryTest {
public static void main(String[] args) {
PersonFactory pf=new PersonFactory();
Person p = pf.getPerson("chin");
System.out.println(p.sayHello("马向林"));
System.out.println(p.sayGoodBye("马向林"));
p=pf.getPerson("ame");
System.out.println(p.sayHello("马向林"));
System.out.println(p.sayGoodBye("马向林"));
}
}
(5)结构如下:
2.2 单态模式与工厂模式的Spring实现
首先要导入的JAR包有如下:
commons-logging-1.1.1.jar
org.springframework.aop-3.1.0.RELEASE.jar
org.springframework.asm-3.1.0.RELEASE.jar
org.springframework.beans-3.1.0.RELEASE.jar
org.springframework..context-3.1.0.RELEASE.jar
org.springframework.core-3.1.0.RELEASE.jar
org.springframework.expression-3.1.0.RELEASE.jar
然后在src目录下创建bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="peopleTarget" class="com.model.People"></bean>
<bean id="peopleAdvice" class="com.model.PeopleBeforeAdvice"></bean>
<bean id="peopleDelegate" class="org.springframework.aop.support.ControlFlowPointcut">
<bean id="chinese" class="com.mxl.models.Chinese"/>
<bean id="american" class="com.mxl.models.American"/>
<!-- 指定第一个参数为PeopleDelegate -->
<constructor-arg type="java.lang.Class" value="com.model.PeopleDelegate"></constructor-arg>
<constructor-arg type="java.lang.String" value="living"></constructor-arg>
</bean>
<bean id="peopleAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="peopleDelegate"></property>
<property name="advice" ref="peopleAdvice"></property>
</bean>
<bean id="people" class="org.springframework.aop.framework.ProxyFactory">
<property name="interceptorNames" ><idref local="peopleAdvisor"></idref></property>
<property name="target" ref="peopleTarget"></property>
</bean>
</beans>
最后修改FactoryTest测试类,在该测试类中使用工厂模式,主要的程序代码如下:
package com.test;
import com.entity.PersonFactory;
import com.interfaceRoom.Person;
public class FactoryTest {
public static void main(String[] args) {
ApplicationContext ctx=new FileSystemXmlApplicationContext(“src/bean.xml”) Person p = null;
P=(Person)ctx.getBean(“chinese”);
System.out.println(p.sayHello("马向林"));
System.out.println(p.sayGoodBye("马向林"));
p=pf.getPerson("american");
System.out.println(p.sayHello("马向林"));
System.out.println(p.sayGoodBye("马向林"));
}
}
运行结果都是一样的
下面对FactoryTest类简单的修改,修改后的代码如下:
public class SpringTest{
public static void main(String []args){
//实例化Spring容器
ApplicationContext ctx=new FileSystemXmlApplicationContext(“bean.xml”);
Person p1=null;
p1=(Person)ctx.getBean(“chinese”);
Person p2=null;
p1=(Person)ctx.getBean(“chinese”);
System.out.println(p1==p2);
}
}
结果是true,说明默认是采用单例模式
第三章 控制反转(IoC)与依赖注入(DI)
3.1 控制反转(IoC)
IoC是一个很大的概念,可以用不同的方式实现。其主要实现方式有以下几种
(1)依赖查找(Dependency Lookup):容器提供回调接口和上下文环境给组件。EJB和Apache Avalon都使用这种方式
(2)依赖注入(Dependency Injection):组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。
其中,依赖注入是时下最流行的IoC类型,其又有接口注入(Interface Injection),设值注入(Setter Injection)和构造子注入(Constructor Injection)三种方式。
3.2 依赖注入(DI)
依赖注入的基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责
类似于EJB容器组件一样,SpringDI容器负责管理Bean,就目前来看,spring内置了两种基础的DI容器,即BeanFactory和ApplicationContext,他们之间的关系是,ApplicationContext隶属于BeanFactory,下面介绍这两种DI容器
1. 面向Java ME/Java SE的BeanFactory
BeanFactory主要用在内存、CPU资源受限场合,比如Applet,手持设备等。它内置了最基础的DI功能,比如配置框架,基础功能。开发者经常会使用到Spring内置的XmlBeanFactory实现。
2. 面向Java EE的ApplicationContext
在BeanFactory基础上,ApplicationContext提供了大量面向企业计算所需要的特性集合,比如消息资源的国际化处理、简化同SpringAOP的集成、内置事件支持、针对Web应用提供了诸多便利、资源操控等。开发者会经常在各种场合使用到不同的ApplicationContext实现,例如,面向Web应用的XmlWebApplicationContext容器、基于注解存储DI元数据AnnotationConfigApplicationContext容器、适合于各种场景的ClassPathXmlApplicationContext和FileSystemXmlApplicationContext、面向Portal应用的XmlPortletApplicationContext。
除此之外,Spring还内置了一些其他类型的ApplicationContext实现,比如面向JCA资源适配器环境的ResourceAdapterApplicationContext。
第四章 多种依赖注入方式
SpringDI容器支持多种不同的依赖注入类型,比如设置注入,构造注入,属性注入,方法注入等
4.1 设值注入
设值注入,是指通过调用setXxx()方法,从而建立起对象间的依赖关系。这种注入方式简单、直观,因而在Spring依赖注入里最常用。在Spring实例化Bean过程中,Spring首先调用Bean的默认构造方法,来实例化Bean对象,然后通过反射的方式调用set方法来注入属性值,因此设值注入要求一个Bean必须满足以下两点要求:
(1)Bean类必须提供一个默认的构造方法;
(2)Bean类必须为需要注入的属性提供对应的setXxx()方法。
Bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!--定义的Bean的id是chinese,class指定实现类--!>
<bean id="chinese" class="com.mxl.models.Chinese">
<!--property元素是用来指定需要容器注入的属性,axe(接口,有一个chop()砍的方法)属性需要容器注入,此处是设置注入,因此,Chinese类必须拥有setAxe()方法-->
<property name=”axe”>
<ref local=”stoneAxe”><!--将另一个Bean的引用注入给chinese Bean-->
</property>
</bean>
<bean id=”stoneAxe” class=”com.mxl.models.StoneAxe”/><!--定义stoneAxe Bean,stoneAxe是axe这个接口的具体实现类-->
</beans>
4.2 构造注入
所谓构造注入,就是通过构造方法来完成依赖的注入,传入的各个参数都是受管的Bean依赖的对象,这些对象间构成了依赖关系。设值注入是Spring所推荐的但是设值注入的缺点是:无法清晰地表示出哪些属性是必须的,哪些是可选的。而使用构造注入的优势是通过构造方法来强制依赖关系,有了构造方法的约束,不可能实现一个不完全或无法使用的Bean,如下面的Bean代码
public class Animal {
private String name;
private int age;
public Animal(String name,int age){
this.name=name;
this.age=age;
}
//此处省略上面两个属性的setXxx()方法和getXxx()方法
}
然后使用构造注入来配置这个Bean,主要使用constructor-arg子元素来定义构造方法的参数,其子元素value用来设置该参数的值,代码如下:
<bean id=”animal” class=”com.mxl.models.Anilmal”>
<constructor-arg><value>Dog</value> </constructor-arg>
<constructor-arg><value>3</value> </constructor-arg>
</bean>
构造参数是按照构造方法的顺序进行的,如果设置顺序错误,会报异常
对于以上的异常,可以通过重新配置Bean定义来解决,如下,
1.通过类型来解决:
<bean id=”animal” class=”com.mxl.models.Anilmal”>
<constructor-arg type=”java.lang.String”><value>Dog</value> </constructor-arg>
<constructor-arg type=”int”><value>3</value> </constructor-arg>
</bean>
2.按索引匹配入参:
<bean id=”animal” class=”com.mxl.models.Anilmal”>
<constructor-arg index=”0”><value>Dog</value> </constructor-arg>
<constructor-arg index=”1”><value>3</value> </constructor-arg>
</bean>
3.联合使用类型和索引匹配
4.3 属性注入
在没有提供构造方法和设置注入的情况下,借助属性注入,同样可以实现类与类之间的依赖关系,比如,借助@Autowired、@Resouce、@EJB等注解。这里介绍@Autowired的使用,下面给出了使用示例。
首先在com.mxl.models包中创建Office类,并在该类中定义一个名称为office的属性, 代码如下:
public class Office {
private String officeNo="001";
@Override
public String toString() {
return "办公号"+officeNo;
}
public String getOfficeNo(){
return officeNo;
}
public void setOfficeNo(String officeNo){
this.officeNo=officeNo;
}
}
接着创建Boss类,在该类中引用Office类,并使用@Autowired注解,代码如下:
import org.springframework.beans.factory.annotation.Autowired;
public class Boss {
@Autowired //使用注解
private Office office;
@Override
public String toString() {
return "office"+office;
}
public Office getOffice() {
return office;
}
public void setOffice(Office office) {
this.office = office;
}
}
在Boss类中,我们使用@Autowired,autowired是自动装配的意思,对Office进行了注解,为了激活这一注解,需要在DI容器中配置AutowiredAnnotationBeanPostProcessor对象,具体的bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor" />
<bean id="boss" class="Boss"></bean>
<bean id="office" class="Office">
<property name="officeNo" value="002"></property>
</bean>
</beans>
测试类:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AnnoIoCTest {
public static void main(String[] args) {
ApplicationContext cx=new ClassPathXmlApplicationContext("bean.xml");
Boss boss=(Boss)cx.getBean("boss");
System.out.println(boss);
}
}
运行后,结果如下:
2017-2-16 10:55:56 org.springframework.context.support.AbstractApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@5cdf8f5e: startup date [Thu Feb 16 10:55:56 CST 2017]; root of context hierarchy
2017-2-16 10:55:56 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
信息: Loading XML bean definitions from class path resource [bean.xml]
office办公号002
4.3 方法注入
Spring内置了以下两种方法注入策略,即查找(Lookup)方法注入和方法替换(Replacement)注入。
方法注入的使用过于复杂,而且不适用。我们完全可以通过改进应用的架构设计,以避免他们的使用。
第五章 装配Bean
5.1 Bean容器
SpringIoC设计的核心是Bean容器,主要实现是指org.springframework.beans包,设计目标是与JavaBean组件一起使用。这个包通常不是由用户直接使用,而是由服务器将其用作其他多数功能的底层中介。其中一个最高级抽象是BeanFactory接口,他是工厂设计模式的实现,允许通过名称创建和检索对象,BeanFactory也可以管理对象之间的关系。
Bean工厂:BeanFactory是一个类工厂,但和传统类工厂不同,传统的类工厂仅生成一个类的对象,或几个实现某一相同接口类的对象。而BeanFactory是通用的工厂,可以创建和管理各种类的对象
BeanFactory实际上是实例化、配置和管理众多Bean的容器。这些Bean通常会彼此合作,因而他们之间会产生依赖。BeanFactory使用的配置数据可以反映这些依赖关系。一个Bean工厂可以用接口org.springframework.beans.factory.BeanFactory表示,这个接口有多个实现。最常使用的简单BeanFactory实现是org.springframework.beans .factory.xml.
XmlBeanFactory(提示一下,ApplicationContext是BeanFactory的子类,所以大多数用户更喜欢使用ApplicationContext的XML形式)。
BeanFactory提供三种实例化的方法:
1.从文件系统资源实例化BeanFactory
使用这种方式,配置文件bean.xml的存储位置可以不固定,例如,bean.xml可以存储在项目工程的根目录下(ch5/bean.xml)代码如下:
Resource res=new FileSystemResource(“beans.xml”);//由beans.xml生成Resource实例
BeanFactory factory=new XmlBeanFactory(res);//生成BeanFactory实例
如果bean.xml不在根目录下,应该这样写:
Resource res=new FileSystemResource(“src/beans.xml”);
2.从CLASSPATH下的资源实例化BeanFactory
使用这种方式时,配置文件的存储位置相对于上一种方式来说是固定的。例如,bean2.xml存储在项目工程目录的CLASSPATH根目录下(ch5/src/beans2.xml)。代码如下,Spring在项目工程的CLASSPATH根目录下查找beans2.xml
Resource resClasspath=new ClassPathResource(“beans2.xml”);
BeanFactory factory=new XmlBeanFactory(resClasspath);//生成BeanFactory实例
如果beans2.xml没有存储在项目的CLASSPATH根路径下,而储存在CLASSPATH下的mxl目录下,此时可以使用如下方式:
Resource resClasspath=new ClassPathResource(“mxl\\beans2.xml”);
3.使用ApplicationContext从CLASSPATH下的XML文件实例化BeanFactory
该种方式与第二种方式基本相同,这里不再赘述,beans2.xml存储在Web应用的CLASSPATH根目录下,使用示例如下:
ApplicationContext appContext=new ClassPathXmlApplicationContext(“beans2.xml”);
BeanFactory factory=(BeanFactory)appContext;//直接生成类的工厂实例
ApplicationContext接口由BeanFactory派生而来,提供了更多面向实际应用的功能。简而言之,BeanFactory提供了配置框架及基础功能,而ApplicationContext则增加了更多支持企业核心内容的功能。ApplicationContext完全由BeanFactory扩展而来,因而BeanFactory所具备的能力和行为也适用于ApplicationContext,ApplicationContext位于org.springframework.context包中,其基本的功能与BeanFactory很相似,也具有负责读取Bean定义文件、维护Bean之间的依赖关系的功能。
实现ApplicationContext最常用的有两个:
org.springframework.context.support.FileSystemXmlApplicationContext:可指定XML定义文件的相对路径或绝对路径来读取定义文件
org.springframework.context.support.ClassPathXmlApplicationContext:
5.2 Bean实例的创建方式
1.调用构造器创建Bean实例
(1)创建Wather接口,代码:
public interface Wather {
public String taste();
}
(2)创建Person接口,代码如下:
public interface Person {
public void drinkWather();
}
(3)创建Chinese的Person具体实现类
public class Chinese implements Person {
private Wather wather;
public void setWather(Wather wather){
System.out.println("Spring执行依赖注入......");
this.wather=wather;
}
//创建无参的构造器
public Chinese(){
System.out.println("Spring实例化Chinese,Chinese实例...");
}
@Override
public void drinkWather() {
System.out.println(wather.taste());
}
}
(4)创建Cocacola的Wather具体实现类,同样包含了一个无参的构造方法
public class Cocacola implements Wather{
@Override
public String taste() {
return "咖啡";
}
public Cocacola(){
System.out.println("Spring实例化依赖Bean-Cocacola...");
}
}
(5)在src目录下新建bean.xml文件,在文件中对Cocacola类和Chinese类进行配置:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="cocacola" class="Cocacola"></bean>
<bean id="chinese" class="Chinese">
<property name="wather" ref="cocacola"></property>
</bean>
</beans>
(6)测试类:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
Chinese c=(Chinese)ac.getBean("chinese");
c.drinkWather();
}
}
2.调用静态工厂方法创建Bean
(1)创建Being接口
public interface Being {
public void hobby();
}
(2)创建Dog的Being具体实现类
public class Dog implements Being{
private String msg;
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public void hobby() {
System.out.println(msg+":狗爱啃骨头!");
}
}
(3)创建Cat类
public class Cat implements Being{
private String msg;
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public void hobby() {
System.out.println(msg+":猫爱吃老鼠");
}
}
(4)创建静态工厂,包含一个静态方法返回Being实例
public class BeingFactory {
public static Being getBeing(String str){
if(str.equals("dog")){
return new Dog();
}
else{
return new Cat();
}
}
}
(5)bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="dog" class="BeingFactory" factory-method="getBeing">
<constructor-arg value="dog"></constructor-arg>
<property name="msg" value="贵宾犬"></property>
</bean>
<bean id="cat" class="BeingFactory" factory-method="getBeing">
<constructor-arg value="cat"></constructor-arg>
<property name="msg" value="蓝猫"></property>
</bean>
</beans>
(6)测试类
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
Dog dog=(Dog)ac.getBean("dog");
dog.hobby();
Cat cat=(Cat)ac.getBean("cat");
cat.hobby();
}
}
(7)结果
贵宾犬:狗爱啃骨头!
蓝猫:猫爱吃老鼠
3.调用实例工厂创建Bean
实例工厂必须提供工厂实例,因此必须在配置文件中配置工厂实例,而bean元素无需class属性。因为BeanFactory不再直接实例化该Bean。
采用实例工厂方法创建Bean的配置需要如下两个属性:
(1)factory-bean:该属性的值为工厂Bean的id。
(2)Factory-method:该属性的值为负责生成Bean实例的实现
具体步骤如下:
(1)修改BeingFactory类,将静态工厂方法修改为实例工厂方法(就是把方法前面的static去掉而已),代码如下:
public class BeingFactory {
public Being getBeing(String str){
if(str.equals("dog")){
return new Dog();
}
else{
return new Cat();
}
}
}
(2)在配置文件beans.xml文件中使用factory-bean和factory-method属性对工厂类和实例工厂方法进行配置,配置代码如下:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="beingFactory" class="BeingFactory"></bean>
<bean id="dog" factory-bean="beingFactory" factory-method="getBeing">
<constructor-arg value="dog"></constructor-arg>
<property name="msg" value="贵宾犬"></property>
</bean>
<bean id="cat" factory-bean="beingFactory" factory-method="getBeing">
<constructor-arg value="cat"></constructor-arg>
<property name="msg" value="垃圾猫猫"></property>
</bean>
</beans>
再次运行,结果都是一样的
调用实例工厂方法创建Bean,与调用静态工厂方法创建Bean的用法基本相似。区别如下:
(1)调用实例工厂方法创建Bean时,必须将实例工厂配置成Bean实例。而静态工厂方法则无需配置工厂Bean。
(2)调用实例工厂方法创建Bean时,必须使用factory-bean属性来确定工厂Bean。而静态工厂方法则使用class元素来确定静态工厂类。
5.2 自动装配
自动装配可以减少配置文件的数量,但降低了依赖关系的透明性和清晰性。
自动装配的类型可分为:byName,byType,constructor,autodetect,no
byName规则:通过名字注入依赖关系,示例如下:
(1)创建Role类(省略了2个get()和set()方法)
package com.mxl.models;
public class Role {
private int id;
private String name;
(2)创建User类(省略了五个属性的get和set方法)
package com.mxl.models;
public class User {
private int id;
private String username;
private String password;
private int age;
private Role role;
@Override
public String toString() {
return "用户信息--用户名:"+username+",密码:"+password+",年龄:"+age+"," +",角色:"+role.getName();
}
(3)bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="role" class="com.mxl.models.Role">
<property name="id" value="1"></property>
<property name="name" value="管理员"></property>
</bean>
<bean id="user" class="com.mxl.models.User" autowire="byName">
<property name="id" value="1"></property>
<property name="username" value="wuyi"></property>
<property name="password" value="admin"></property>
<property name="age" value="22"></property>
</bean>
</beans>
测试程序:
package com.mxl.models;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ByNameTest {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
User user=(User)ac.getBean("user");
System.out.println(user);
}
}
结果如下:
用户信息--用户名:wuyi,密码:admin,年龄:22,,角色:管理员
byType规则:指根据类型匹配来注入依赖关系。将autowire="byType"程序依旧可以运行,与上面类似
constructor规则:使用这种方法装配,只不过是通过了构造方法而进行自动装配,与前面两种方式没有多大区别,如下所示,为User类定义一个构造方法,使用构造方法来进行自动装配:
public User(String username, String password, int age, Role role) {
this.username = username;
this.password = password;
this.age = age;
this.role = role;
}
改动bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="role" class="com.mxl.models.Role">
<property name="id" value="1"></property>
<property name="name" value="管理员"></property>
</bean>
<bean id="user" class="com.mxl.models.User" autowire="constructor">
<constructor-arg index="0" value="wuyi"></constructor-arg>
<constructor-arg index="1" value="admin"></constructor-arg>
<constructor-arg index="2" value="22"></constructor-arg>
</bean>
</beans>
对于大型的应用,不建议使用自动装配,由于依赖关系的装配依赖于源文件的属性名,导致Bean与Bean之间的耦合降低到代码层次,不利于高层次的解耦。
另外,在某一个Bean中,将其autowire-candidate属性设置为false则会将该bean排除在自动装配之外了
5.2 使用Spring特殊Bean
5.2.1.Bean后处理器:这种Bean并不对外界提供服务,无需id属性,但他负责对容器的其他Bean执行处理,例如为容器中的目标Bean生成代理。这种Bean可称为Bean后处理器,它在Bean创建成功后,对其进行进一步的加强处理。
Bean后处理器必须实现BeanPostProcessor接口,该接口包含两个方法:
Object postProcessBeforeInitialization(Object bean,String beanName):在Bean初始化之前被调用,第一个参数是Bean实例,第二个参数是Bean实例的名字
Object postProcessAfterInitialization(Object bean,String beanName):在Bean初始化之前被调用,第一个参数是Bean实例,第二个参数是Bean实例的名字
(1)创建People接口
package com.mxl.models;
public interface People {
public void useAxe();//使用斧子的方法
}
(2)创建Axe接口
package com.mxl.models;
public interface Axe {
public String chop();
}
(3)创建Axe接口的实现类StoneAxe:
package com.mxl.models;
public class StoneAxe implements Axe {
@Override
public String chop() {
return "石斧子砍柴好慢";
}
}
(4)创建People接口的实现类America类
package com.mxl.models;
import org.springframework.beans.factory.InitializingBean;
public class American implements People ,InitializingBean{
private String name;
private Axe axe;
public American() {
System.out.println("American无参构造方法执行");
}
public void setName(String name) {
this.name = name;
}
public void setAxe(Axe axe) {
this.axe = axe;
}
@Override
public void useAxe() {
System.out.println(name+axe.chop());
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("初始化Bean之后afterPropertiesSet()方法执行");
}
public void init(){
System.out.println("正在执行初始化方法init...");
}
}
(5)创建后处理器类:MyBeanPostProcessor,该类实现了BeanPostProcessor接口及其两个方法,代码如下:
package com.mxl.models;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor{
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("后:系统已经完成对"+beanName+"的初始化");
//如果系统刚完成初始化的Bean是American
if(bean instanceof American){
American a=(American)bean;
a.setName("wuyi");
}
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
System.out.println("前:系统正在准备对"+beanName+"进行初始化");
return bean;
}
}
(6)配置文件beans.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean class="com.mxl.models.MyBeanPostProcessor"></bean>
<bean id="stoneAxe" class="com.mxl.models.StoneAxe"></bean>
<bean id="america" class="com.mxl.models.American" init-method="init" >
<property name="axe" ref="stoneAxe"></property>
</bean>
</beans>
(7)测试类:
package com.mxl.models;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class BeanPostTest {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
System.out.println("程序已经实例化BeanFactory....");
People people=(People)ac.getBean("america");
System.out.println("程序已经完成对American的实例化");
people.useAxe();
}
}
第六章 面向切面的编程
6.1 创建Advice
Spring3.0中的通知类型包括前置通知、后置通知、环绕通知和异常通知4种。
前置通知(Before Advice):
在目标方法执行前被调用,但这个通知不能阻止目标方法前的执行。所对应的接口是org.springframework.aop.BeforeAdvice。例如,在用户登录系统后台前对用户身份进行识别。如下:
(1)UserLogin接口:
package com.mxl.models;
public interface UserLogin {
public void login(String name);//用户登录
}
(2)UserLogin接口的实现类UserLoginImpl的具体实现类:
package com.mxl.models;
public class UserLoginImpl implements UserLogin{
@Override
public void login(String name) {
System.out.println(name+"正在登录系统后台");
}
}
(3)实现MethodBeforeAdvice接口就可以在调用UserLoginImpl类的login()方法之前执行一些动作,即对用户的身份识别:
package com.mxl.models;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class CheckUser implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] arg, Object target)
throws Throwable {
String username=(String)arg[0];
System.out.println("正在对【"+username+"】用户进行身份校验");
}
}
(4)配置文件bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="checkuser" class="com.mxl.models.CheckUser"></bean>
<bean id="target" class="com.mxl.models.UserLoginImpl"></bean>
<!-- 使用Spring代理工厂配置一个代理 -->
<bean id="userlogin" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames" value="checkuser"></property>
<property name="target" ref="target"></property>
</bean>
</beans>
(5)测试类:
package com.mxl.models;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
UserLogin ul =(UserLogin) ac.getBean("userlogin");
ul.login("吴义");
}
}
结果如下:
正在对【吴义】用户进行身份校验
吴义正在登录系统后台
后置通知(After Returning Advice):
后置通知与前置通知正好相反,所对应的接口是org.springframework.aop. AfterReturningAdvice,将CheckUser改一下即可:
package com.mxl.models;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
public class CheckUser implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
String username=(String)arg2[0];
System.out.println("欢迎您【"+username+"】,登录成功");
}
}
环绕通知(After Returning Advice):
是最常用的通知类型。执行前后调用,接口是org.aopalliance.intercept.MethodInterceptor
前置,其他的代码都一样,不一样的地方:
(1)创建实现MethodInterceptor接口的类CheckUser,具体内容如下:
package com.mxl.models;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class CheckUser implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
Object[] objs = invocation.getArguments();
String username=(String) objs[0];
//在目标方法执行前调用
System.out.println("方法前:"+username);
//通过反射调用执行方法
Object obj = invocation.proceed();
System.out.println("方法后,欢迎你:"+username);
return obj;
}
}
(2)bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="culw" class="com.mxl.models.CheckUser"></bean>
<bean id="target" class="com.mxl.models.UserLoginImpl"></bean>
<!-- 使用Spring代理工厂配置一个代理 -->
<bean id="userlogin" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--指定代理接口,如果是多个接口,可以使用list元素指定 -->
<property name="interceptorNames" value="culw"></property>
<property name="target" ref="target"></property>
</bean>
</beans>
测试类都是一样的。测试结果如下:
方法前:吴义
吴义正在登录系统后台
方法后,欢迎你:吴义
环绕通知(Throws Advice):
在目标方法抛出异常后调用。Spring提供强制类型的Throws通知,因此可以编写代码来捕获异常。所对应的接口是org.springframework.aop.ThrowsAdvice。ThrowsAdvice定义在异常发生时该有什么动作,与前面几种通知类型不同,ThrowsAdvice是一个标志接口,他没有定义任何必须实现的方法。但是,实现这个接口的类必须至少有一个如下形式的方法:
void afterThrowing(Throwable throwable)
void afterThrowing(Method method,Object [] args,Object target,Throwable throwable)
(1)修改UserLoginImpl类,使其抛出异常。代码如下:
package com.mxl.models;
public class UserLoginImpl implements UserLogin{
@Override
public void login(String name) {
if(name.equals("吴义")){
System.out.println(name+"正在登录系统");
}
else{
throw new RuntimeException("输入的用户名不正确");
}
}
}
(2)实现异常通知的类(也就是上面的CheckUser类):
package com.mxl.models;
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
public class ExceptionAdvice implements ThrowsAdvice{
public void afterThrowing(Method method,Object [] args,Object target,Exception ex){
System.out.println("Method:"+method.getName()+"抛出异常:"+ex.getMessage());
}
}
(3)bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="exceptionAdvice" class="com.mxl.models.ExceptionAdvice"></bean>
<bean id="target" class="com.mxl.models.UserLoginImpl"></bean>
<!-- 使用Spring代理工厂配置一个代理 -->
<bean id="userlogin" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--指定代理接口,如果是多个接口,可以使用list元素指定 -->
<property name="interceptorNames" value="exceptionAdvice"></property>
<property name="target" ref="target"></property>
</bean>
</beans>
6.1 定义Pointcut
Pointcut即切入点,用于配置切面的切入位置。Spring的Pointcut分为静态Pointcut,动态Ponitcut ,和用户自定义Pointcut三种,其中静态Ponitcut只需考虑类名,方法名。动态Pointcut除此之外,还需要考虑方法的参数,以便在运行时可以动态的确定切入点的位置。
静态Pointcut:
静态切入点只在代理创建时执行一次,而不是在运行期间每次调用方法时都执行,所以性能比动态切入点要好,是首选的切入点方式。静态意味着不变,例如方法和类的名称。在Spring中定义了如下两个静态切入点的实现类:
(1)StaticMethodMatcherPointcut:一个抽象的静态Pointcut,它不能被实例化。开发者可以自己扩展该类来实现自定义的切入点。
(2)NameMatcherMethodPointcutL:只能对方法名进行判别的静态Pointcut实现类
示例如下:
(1)创建People类,有四个方法:
package com.mxl.models;
public class People {
public void speaking(){
System.out.println("嗨!,大家好!");
}
public void running(){
System.out.println("正在跑步");
}
public void eating(){
System.out.println("正在吃东西");
}
public void died(){
System.out.println("忧郁而死");
}
}
(2)创建前置通知类PeopleBeforeAdvice,表示在目标类的方法执行前输入该方法所属的类名以及该方法的名字。
package com.mxl.models;
import java.lang.reflect.Method;
import org.springframework.aop.MethodBeforeAdvice;
public class PeopleBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println(target.getClass().getSimpleName()+"将要执行"+method.getName()+"方法:");
}
}
(3)如果按照之前的使用前置通知,则会对每一个方法都使用前置通知,而现在需要的是对speaking()方法使用前置通知,对于其他三个方法都不使用。这就需要定义一个切面了,来过滤那些不需要使用前置通知的方法,代码如下:
package com.mxl.models;
import java.lang.reflect.Method;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
public class PeopleAdvisor extends StaticMethodMatcherPointcutAdvisor{
//切点方法匹配规则,方法名为speaking
@Override
public boolean matches(Method method, Class<?> arg1) {
return "speaking".equals(method.getName());
}
//切点类匹配规则,为People类或其子类
@Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
@Override
public boolean matches(Class<?> arg0) {
return People.class.isAssignableFrom(arg0);
}
};
}
}
(4)bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="peopleAdvice" class="com.mxl.models.PeopleBeforeAdvice" ></bean>
<bean id="peopeleTarget" class="com.mxl.models.People"></bean>
<bean id="peopleAdvisor" class="com.mxl.models.PeopleAdvisor"><!-- 定义切面 -->
<property name="advice" ref="peopleAdvice"></property><!-- 注入前置通知 -->
</bean>
<!-- 使用Spring代理工厂配置一个代理 -->
<bean id="people" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames" >
<idref local="peopleAdvisor"/>
</property>
<property name="target" ref="peopeleTarget"></property>
</bean>
</beans>
(5)定义测试类:
package com.mxl.models;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
People p=(People)ac.getBean("people");
p.speaking();
p.running();
p.eating();
p.died();
}
}
运行结果如下:
People将要执行speaking方法:
嗨!,大家好!
正在跑步
正在吃东西
忧郁而死
使用正则表达式:以上示例中,仅能通过方法名定义切入点,如果有多个方法需要定义切入点,需要实现类中多次判断,相当繁复,如果多个目标的方法名字有一定的命名规则,此时使用正则表达式来过滤,将减少许多麻烦。
在Spring中,RegexpMethodPointcutAdvisor是正则表达式方法匹配的切面实现类。其类继承结构如下图所示,该类功能比较全。因此,也省去了编写通知切面的麻烦(PeopleAdvisor)
符号 | 描述 | 示例 |
. | 匹配换行符外所有的单个字符 | setFoo.匹配setFooB,但不匹配setFoo或者setFooBar |
+ | 匹配+号前的字符1次或N次 | setFoo.+匹配setFooB和setFooBar但不匹配setFoo |
* | 匹配*号前的字符0次或N次 | setFoo.*匹配setFoo、setFooB和setFooBar |
? | 匹配?号前的字符0次或1次 | e?le?匹配angel中的el和angle中的le |
将bean.xml修改:并且删除PeopleAdvisor类:
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="peopleAdvice" class="com.mxl.models.PeopleBeforeAdvice" ></bean>
<bean id="peopeleTarget" class="com.mxl.models.People"></bean>
<bean id="peopleAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="patterns"> <!-- 使用正则表达式 -->
<list>
<value>.*ing</value> <!-- 表示以ing结尾的方法 -->
</list>
</property>
<property name="advice" ref="peopleAdvice"></property><!-- 注入前置通知 -->
</bean>
<!-- 使用Spring代理工厂配置一个代理 -->
<bean id="people" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--指定代理接口,如果是多个接口,可以使用list元素指定 -->
<property name="interceptorNames" >
<idref local="peopleAdvisor"/> <!-- 切面 -->
</property>
<property name="target" ref="peopeleTarget"></property>
</bean>
</beans>
测试代码不变,结果如下:
People将要执行speaking方法:
嗨!,大家好!
People将要执行running方法:
正在跑步
People将要执行eating方法:
正在吃东西
忧郁而死
动态Pointcut:
由于动态切入点除了要考虑方法的名称等静态信息外,还要考虑方法的参数。由于它是动态的,在执行时既要计算方法的静态信息,还要计算其参数,结果也不能被缓存。因此,动态切入点要消耗更多的系统资源。
Spring中提供了如下几种动态切入点的实现:
(1)ControlFlowPointcut:控制流程切入点
(2)DynamicMethodMatcherPointcut:动态方法匹配器
生成PeopleDelegate类,执行People类的eating()和died()方法
package com.mxl.models;
public class PeopleDelegate {
private People people;
public void setPeople(People people) {
this.people = people;
}
public void living(){
people.eating();
people.died();
}
}
bean.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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="peopleAdvice" class="com.mxl.models.PeopleBeforeAdvice" ></bean>
<bean id="peopeleTarget" class="com.mxl.models.People"></bean>
<bean id="peopleDelegate" class="org.springframework.aop.support.ControlFlowPointcut">
<!-- 指定第一个参数为PeopleDelegate类 -->
<constructor-arg type="java.lang.Class" value="com.mxl.models.PeopleDelegate"></constructor-arg>
<constructor-arg type="java.lang.String" value="living"></constructor-arg>
</bean>
<bean id="peopleAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="pointcut" ref="peopleDelegate"></property><!-- 指定切点 -->
<property name="advice" ref="peopleAdvice"></property><!-- 指定通知 -->
</bean>
<!-- 使用Spring代理工厂配置一个代理 -->
<bean id="people" class="org.springframework.aop.framework.ProxyFactoryBean">
<!--指定代理接口,如果是多个接口,可以使用list元素指定 -->
<property name="interceptorNames" >
<idref local="peopleAdvisor"/> <!-- 切面 -->
</property>
<property name="target" ref="peopeleTarget"></property>
</bean>
</beans>
在上述配置文件中,采用DefaultPointcutAdvisor切面来创建动态切面,通过pointcut属性指定切点为peopleDelegate,通过advice属性指定通知为peopleAdvice
测试代码:
package com.mxl.models;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
People p=(People)ac.getBean("people");
p.speaking();
p.running();
p.eating();
p.died();
PeopleDelegate pd=new PeopleDelegate();
pd.setPeople(p);
pd.living();
}
}
运行结果如下:
嗨!,大家好!
正在跑步
正在吃东西
忧郁而死
People将要执行eating方法:
正在吃东西
People将要执行died方法:
忧郁而死