一、Spring框架
1.1 Spring框架是什么
- Spring是一种容器框架,用于配置各个组件(bean)并且维护各个组件bean之间的关系
- Spring框架可以管理Web层,业务层,DAO层,持久层。
- Spring提倡面向接口编程,配合DI技术可以实现层与层的解耦(主要是WEB层和业务层)
-
Spring框架图
1.2 一个简单的spring项目
-
目录结构
- 引入spring的开发包(最小配置
spring.jar
以及日志包common-logging.jar
) - 创建spring的一个核心文件
ApplicationContext.xml
, 该文件一般放在src目录下,该文件中引入xsd文件,可以自定义文件名。- hibernate核心文件
hibernate.cfg.xml
- struts核心文件
struts-config.xml
- hibernate核心文件
TestSpring |---src |---com.netease | |---Service | |---HelloService.java | |---ByeService.java | |---Test | |---TestService.java |---ApplicationContext.xml External Libraries |--- spring.jar |--- common-logging.jar |--- ...
- 引入spring的开发包(最小配置
-
代码详情
package com.netease.Service; public class HelloService { private String name; private ByeService byeService; /** 引用了一个ByeService*/ public String getName() { return name;} public void setName(String name) { this.name = name;} public ByeService getByeService() { return byeService;} public void setByeService(ByeService byeService) { this.byeService = byeService;} public void sayHello() { System.out.println("hello "+name); } }
package com.netease.Service; public class ByeService { private String name; public String getName() { return name;} public void setName(String name) {this.name = name;} public void sayBye() { System.out.println("Bye " + name); } }
package com.netease.Test; import com.netease.Service.HelloService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TestService { public static void main(String[] args) { /*通过反射机制,在Spring容器中生成对象*/ /*如果%%.xml放在某个包下,则就变为<包名+文件名>*/ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml"); HelloService helloService = (HelloService) applicationContext.getBean("helloService"); helloService.sayHello(); helloService.getByeService().sayBye(); } }
-
Spring核心容器文件ApplicationContext.xml
- 在容器文件中配置Bean(Bean有可能是以下各种类型service/dao/domain/action/数据源)
- Spring框架加载时,会阅读该容器文件,自动创建一个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" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd"> <!--注意Spring如何配置Bean以及如何维护Bean之间的依赖--> <bean id="helloService" class="com.netease.Service.HelloService"> <!--注入属性值--> <property name="name"> <value>Today</value> </property> <!--维护Bean之间依赖,ref指向下一个Bean--> <property name="byeService" ref="byeService"/> </bean> <bean id="byeService" class="com.netease.Service.ByeService"> <property name="name"> <value>Yesterday</value> </property> </bean> </beans>
1.3 Spring框架运行原理图
- Spring框架什么时候被加载,Spring中配置的bean怎样被创建,Bean与Bean之间的关系如何维护
1.4 IOC与DI是什么
- IOC(Inverse Of Controll ) 控制反转: 所谓控制反转就是把创建对象(bean),和维护对象(bean)的关系的权利从程序中转移到spring的容器(applicationContext.xml),而程序本身不再维护.
- DI(Dependency Injection) 依赖注入: 实际上DI和IOC是同一个概念,Spring设计者认为DI更准确表示Spring核心技术
二、装配Bean
2.1 容纳Bean
-
ApplicationContext方式
ApplicationContext ac = new ClassPathXmlApplicationContext("com/netease/bean.xml")
- 这句话执行时,不仅实例化了该容器,其中配置的所有scope为singleton的bean全部通过反射机制被实例化,scope为prototype的bean不会被实例化
- 其他三种加载方式
- ClassPathXmlApplicationContext:从类路径中加载。
- FileSystemXmlApplicationContext:从文件系统加载,需要全路径
- XmlWebApplicationContext:从web系统中加载。
- 好处:预先加载,速度快;缺点:耗内存
-
Bean的作用域,即scope
-
Bean工厂方式
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("com/netease/bean.xml"))
- 这句话执行时,仅实例化了该容器,容器中的bean不被实例化,只有当你使用
factory.getBean("***")
获取某个bean时,才实例化该bean对应的对象。类似于延迟实例化 - 好处:节约内存;缺点:速度慢
- 一般没有特殊要求,都采用ApplicationContext方式实例化(90%),移动端可采用Bean工厂方式
2.2 Bean生命周期
-
完整生命周期步骤如下(通常只用到加粗的几步):
- 实例化:程序加载ApplicationContext文件,并把bean(scope=singleton)实例化到内存
- 设置属性:调用set方法设置bean中指定的属性
- 如果你实现了BeanNameAware接口, 则可以通过BeanName
- 如果你实现了BeanFactoryAware接口,则可以获取BeanFactory
- 如果你实现了ApplicationContextAware接口,则可以获取ApplicationContext
- 如果bean和一个后置处理器关联,则会自动去调用 postProcessBeforeInitialization()方法
- 如果你实现InitializingBean接口,则会调用afterPropertiesSet()方法
- 如果设置了<bean init-method=”init” />,则可以在bean文件中定义自己的初始化方法init.
- 如果bean和一个后置处理器关联,则会自动去调用 postProcessAfterInitialization()方法
- 使用bean
- 容器关闭
- 可以通过实现DisposableBean接口来调用方法 destory,用来关闭资源等
- 可以在<bean destory-method=”destroy”/> 调用定制的销毁方法destroy
-
ApplicationContext方式
-
Bean工厂方式
2.3 装配Bean
2.3.1 什么是装配Bean
- 告诉容器有哪些Bean以及容器如何使用依赖注入将它们配合在
一起。
2.3.2 使用XML装配
- 在XML文件中配置,spring会自动读取该文件加载配置好的Bean
2.3.3 添加一个Bean
-
基本配置:一个bean ID + 全称类名
<beans> <bean id="foo" class="...Foo"/> <bean id="bar" class="...Bar"/> </beans>
-
scope属性
<beans> <bean id="foo" scope="prototype/singleton" class="...Foo"/> <bean id="bar" class="...Bar"/> </beans>
- 注意尽量使用默认的singleton,以防占用太大内存,影响程序性能
-
init-method 和destroy-method属性
- 用途:在Spring实例化或者销毁bean时做一些处理工作
- 使用方法:
- 声明方式:在XML文件中先声明该Bean拥有的初始化和销毁方法的函数名。在bean.java中实现
init
和destroy
方法,函数名可自定义<bean id="foo" init-method="init" destory-method="destroy" class="...Foo" >
- 标签方式:不需要在xml文件中声明初始化和销毁方法,在bean.java中采用标签方式声明初始化和销毁方法
@PostConstruct public void ini(){…} @PreDestroy public void destroy(){…}
- 声明方式:在XML文件中先声明该Bean拥有的初始化和销毁方法的函数名。在bean.java中实现
- 其他进行初始化和销毁处理的方法:
- Spring还提供了两个接口供bean.java使用,来进行实例化或者销毁时的处理,但不推荐
2.3.4 通过set方法注入属性和依赖
通过<bean>元素的<property>子元素注入属性和依赖
- 注入简单属性(基本类型和String)
<bean id="foo" class="...Foo"> <property name="name"> <value>tom</value> </property> </bean>
- 注入依赖
- 引用其他的Bean
<bean id="foo" class="...Foo"> <property name="name"> <ref bean="bar"> </property> </bean> <bean id="bar" class="...Bar"> </bean>
- 内部bean
<bean id="foo" class="...Foo"> <property name="bar"> <bean class="...Bar">...</bean> </property> </bean>
- 引用其他的Bean
- 注入集合属性(数组,List,Set,Map)
- 设置null
<property name="barlist"> <null/> </property>
- 数组
<!-- 给数组注入值 --> <property name="empName"> <list> <value>小明</value> <value>李雷</value> <value>韩梅梅</value> </list> </property>
- List
<!-- 给list注入值 list 中可以有相等的对象 --> <property name="empList"> <list> <ref bean="emp2" /> <ref bean="emp1"/> <ref bean="emp1"/> </list> </property>
- Set
<!-- 给set注入值 set不能有相同的对象 --> <property name="empsets"> <set> <ref bean="emp1" /> <ref bean="emp2"/> <ref bean="emp2"/> </set> </property>
- Map
<!-- 给map注入值只要key不同即可 --> <property name="empMaps"> <map> <entry key="11" value-ref="emp1" /> <entry key="22" value-ref="emp2"/> <entry key="33" value-ref="emp1"/> </map> </property>
- property
<property name="pp"> <props> <prop key="pp1">abcd</prop> <prop key="pp2">hello</prop> </props> </property>
- 设置null
- Bean的继承
-
public class Student
有name和age两个属性 -
public class Gradate extends Student
有degree属性 - 在ApplicationContext文件中体现配置
- 即student中配置过的属性graduate可以不配置
- 如果配置,则会覆盖父类的属性
<!-- 配置一个学生对象 --> <bean id="student" class="com.hsp.inherit.Student"> <property name="name" value="顺平" /> <property name="age" value="30"/> </bean> <!-- 配置Grdate对象 --> <bean id="grdate" parent="student" class="com.hsp.inherit.Gradate"> <!-- 如果自己配置属性name,age,则会替换从父对象继承的数据 --> <property name="name" value="小明"/> <property name="degree" value="学士"/> </bean>
-
2.3.5 通过构造器注入依赖
通过<bean>元素的<constructor-arg>子元素注入属性
<bean id="test" class ="...Test">
<constructor-arg index="0" type="java.lang.String" value="顺平" />
<constructor-arg index="1" type="int" value="20"/>
<constructor-arg index="2" type="double" value="34.5" />
</bean>
2.3.6 自动装配
不推荐使用,但需要了解
- 通过autowire属性装配
<bean id="foo" class="...Foo" autowire="autowire type">
- 有四种自动装配类型:
- byName寻找和属性名相同的bean,若找不到,则装不上。(属性名和bean id名必须相同)
- byType:寻找和属性类型相同的bean,找不到,装不上,找到多个抛异常。
- constructor:查找和bean的构造参数一致的一个或多个bean,若找不到或找到多个,抛异常。按照参数的类型装配
- autodetect: (3)和(2)之间选一个方式。不确定性的处理与(3)和(2)一致。
- defualt : 这个需要在<beans defualt-autorwire=“指定” />
-
no : 不自动装配,这是autowrite的默认值。
- default说明
- 需要在<beans defualt-autorwire=“指定” />
- 如果在<beans>指定了default-atuowrite后,所有的bean的默认的autowire就是<beans>中指定的装配方法;
- 如果没有在<beans>指定defualt-autorwire,则默认是defualt-autorwire=”no”,所有的bean则默认不自动装配,除非自己配置autowire
- 需要在<beans defualt-autorwire=“指定” />
2.3.7 通过标签自动装配(待跟进)
- spring2.5提供了
<context:annotation-config />
配置,即只需要在ApplicationContext.xml
中写入这句话,可激活在类中探测到的各种注解, - 常用注解:
@Required @Autowire @PostConstrct @PreDestroy @Resource @EJB @PersistenceContext @WebServiceRef
- 非常有用
2.3.8 分散配置
通过在ApplicationContext.xml文件中配置
context:property-placeholder
引入属性文件,有多个需要使用','号间隔.
- 步骤如下:
- 配置属性文件db.property
name=scott drivername=oracle:jdbc:driver:OracleDirver url=jdbc:oracle:thin:@127.0.0.1:1521:hsp pwd=tiger
- 在ApplicationContext.xml文件中引入db.properties文件,两种方式
-
第一种
<context:property-placeholder location = "classpath:com/netease/db.properties,classpath:com/netease/db2.properties,..."/>
-
第二种
<bean id="dbPropertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>xx/yy/db.properties</value> <value>xx/yy/db2.properties</value> </list> </property> </bean>
-
- 在ApplicationContext.xml文件中配置bean,采用占位符$方式
<bean id="dbutil" class="com.hsp.dispatch.DBUtil"> <property name="name" value="${name}" /> <property name="drivername" value="${drivername}" /> <property name="url" value="${url}" /> <property name="pwd" value="${pwd}" /> </bean>
- 配置属性文件db.property
三、AOP原理剖析
Aspect Oriented Programming(AOP),即面向切面编程。对所有对象或一类对象编程。
在不增加代码的基础上,还增加新的功能
3.1 AOP原理
3.1.1 AOP术语介绍
- 切面(aspect):要实现的交叉功能,是系统模块化的一个切面或领域。如日志记录。
- 通知:切面的实际实现,他通知系统新的行为。如在日志通知包含了实现日志功能的代码,如向日志文件写日志。通知在连接点插入到应用系统中。
- 连接点:应用程序执行过程中插入切面的地点,可以是方法调用,异常抛出,或者要修改的字段。
- 切入点:定义了通知应该应用在哪些连接点,通知可以应用到AOP框架支持的任何连接点。
- 引入:为类添加新方法和属性。
- 目标对象:被通知的对象。既可以是你编写的类也可以是第三方类。
- 代理对象:将通知应用到目标对象后创建的对象,应用系统的其他部分不用为了支持代理对象而改变。
- 织入:将切面应用到目标对象从而创建一个新代理对象的过程。织入发生在目标对象生命周期的多个点上:
- 编译期:切面在目标对象编译时织入,这需要一个特殊的编译器。
- 类装载期:切面在目标对象被载入JVM时织入,这需要一个特殊的类载入器。
- 运行期:切面在应用系统运行时织入。
3.1.2 AOP原理图
原理解释
- 代理对象:仅需要配置在XML配置文件中即可,用于将目标对象和通知进行绑定;
- 通知:切面编程的实现,只要在代理对象中配置了目标对象,该通知就可以作用到目标对象上;
- 目标对象:需要使用通知的对象,例如想要通过通知来写日志,做安全检查等
通知类型
3.1.3 举个栗子
本例中,
- 有两个接口
HelloServiceInterface
和ByeServiceInterface
,其中类SayingService
实现了这两个接口中的方法。- 配置文件中,为类
SayingService
的具体对象配置了四种通知(严格说是五种),分别在该对象调用每一个方法时为其执行这几个通知- 注意!!!通知针对的是对象,对象调用几个方法,通知就会执行多少遍,除非配置切入点,使得通知绑定到具体的方法上;
- 其他解释见代码注释
- 目录结构
TestSpring |---src |---com.netease | |---Advice | |---MyBeforeAdvice (前置通知,实现了接口MethodBeforeAdvice) | |---MyAfterReturningAdvice (后置通知,实现了接口AfterReturningAdvice) | |---MyInterceptAdvice (环绕通知,实现了接口MethodInterceptor) | |---MyThrowableAdvice (异常通知,实现了接口ThrowsAdvice) | |---Service | |---ByeServiceInterface.java (包含方法sayBye) | |---HelloServiceInterface.java (包含方法sayHello) | |---SayingService.java (实现了以上两个接口) | |---Test | |---TestService.java |---ApplicationContext.xml External Libraries |--- spring.jar |--- common-logging.jar |--- ...
- 代码描述
public class TestService { public static void main(String[] args) { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("ApplicationContext.xml"); /*注意这里必须获取代理对象!!否则就白配置了*/ /*得到的对象为什么可以是HelloServiceInterface --> 这里可以理解为,代理类实际上实现了这个接口,因此可以通过强转得到*/ HelloServiceInterface helloService = (HelloServiceInterface) applicationContext.getBean("proxyFactoryBean"); helloService.sayHello(); /*同理,由于代理类同样实现了ByeServiceInterface接口*/ ByeServiceInterface byeService = (ByeServiceInterface) helloService; byeService.sayBye(); } }
- 配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns=....省略> <!--配置通知--> <bean id="myBeforeAdvice" class="com.netease.Advice.MyBeforeAdvice"/> <bean id="myAfterReturningAdvice" class="com.netease.Advice.MyAfterReturningAdvice"/> <bean id="myInterceptAdvice" class="com.netease.Advice.MyInterceptAdvice"/> <bean id="myThrowableAdvice" class="com.netease.Advice.MyThrowableAdvice"/> <!--配置切入点(可选)--> <!--这里表示将myBeforeAdvice这个通知绑定到sayHello方法,对其他方法失效--> <bean id="nameMatchMethodPointcutAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor"> <property name="advice" ref="myBeforeAdvice"/> <property name="mappedName" value="sayHello"/> </bean> <!--配置目标对象--> <bean id="helloService" class="com.netease.Service.App"> <property name="name"> <value>zhujie</value> </property> </bean> <!--配置代理对象--> <bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--配置被代理的接口集合,表示该代理对象服务于哪些接口,即需要实现哪几个接口--> <property name="proxyInterfaces"> <list> <value>com.netease.Service.HelloServiceInterface</value> <value>com.netease.Service.ByeServiceInteface</value> </list> </property> <!--配置被代理的通知--> <property name="interceptorNames"> <list> <!--<value>myBeforeAdvice</value>--> <!--注意这里写的是我们配置的切入点id,如果仍然写前置通知的id,则切入点配置就无效了--> <value>nameMatchMethodPointcutAdvisor</value> <value>myBeforeAdvice</value> <value>myAfterReturningAdvice</value> <value>myInterceptAdvice</value> <value>myThrowableAdvice</value> </list> </property> <!--配置被代理的对象--> <!--表示该对象调用方法时会被注入通知--> <property name="target" ref="helloService"/> </bean> </beans>