1.Spring的核心容器由哪些模块组成?
答:Spring的核心容器是其他模块的建立基础,有Spring-core、Spring-beans、Spring-context、Spring-context-support和Spring-expression(Spring表达式语言)等模块组成。
- Spring-core模块:提供了框架的基本组成部分,包括控制反转(Inversion ofControl,IoC)和依赖注入(Dependency Injection,DI)功能。
- Spring-beans模块:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bena。
- Spring-context模块:建立在Core和Beans模块的基础之上,提供一个框架式的对象访问方式,是访问定义和配置的任何对象的媒介。ApplicationContext接口是Context模块的焦点。
- Spring-context-support模块:支持整合第三方库到Spring应用程序上下文,特别是用于高速缓存(EhCache、JCache)和任务调度(CommonJ、Quartz)的支持。
- Spring-expression模块:提供了强大的表达式语言去支持运行时查询和操作对象图。这是对JSP2.1规范中规定的统一表达式语言(Unified EL)的扩展。该语言支持设置和获取属性值、属性分配、方法调用、访问数组、集合和索引的内容、逻辑和算术运算、变量命名以及从Spring的IoC容器中以名称检索对象。它还支持列表投影、选择以及常见的列表聚合。
2.Spring IoC的基本概念?
控制反转(Inversion ofControl,IoC)是一个比较抽象的概念,是Spring框架的核心,用来消减计算机的耦合问题。依赖注入(Dependency Injection,DI)是IoC的另外一种说法,只是从不用的角度描述相同的概念。
3.举例说明IoC容器的实现方式有哪些?
实现控制反转的是SpringIoC容器。Spring IoC容器的设计主要是基于BeanFactory和ApplicationContext两个接口。
3.1 BeanFactory
BeanFactory由org.springframework.beans.factory.BeanFactory接口定义,它提供了完整的IoC服务支持,是一个管理Bean的工厂,主要负责初始化各种Bean。BeanFactory接口有多个实现类,其中比较常见的是org.springframework.beans.factory.xml.XmlBeanFactory,该类会根据XML配置文件中的定义来装配Bean。
实例代码:
public static void main(Strin[] args){
//初始化Spring容器,加载配置文件
BeanFactory beanFac=new XmlBeanFactory(
new FileSystemResource("D:\eclipse-workspace\ch1\src\applicationContext.xml")
);
//通过容器获取test实例
TestDao tt=(TestDao)beanFac.getBean(test);
tt.sayHello();
}
3.2 ApplicationContext
ApplicationContext是BeanFactory的子接口,也称为应用上下文,由org.springframework.context.ApplicationContext接口定义。App;icationContext接口除了包含BeanFactory的所有功能以外,还添加了对国际化、资源访问、事件传播等内容的支持。
创建ApplicationContext接口实例通常有以下三种方法;
3.2.1 通过ClassPathXmlApplicationContext创建
ClassPathXmlApplicationContext将从类路径目录(src根目录)中寻找指定的XML配置文件,代码如下
public static void main(String[] args){
//初始化Spring容器ApplicationContext,加载配置文件
ApplicationContext appCon=new ClassPathXmlApplicationContext("applicationContext.xml");
//通过容器获取test实例
TestDao tt=(TestDao) appCon.getBean("test");
tt.sayHello();
}
3.2.2 通过FileSystemXmlApplicationContext创建
FileSystemXmlApplicationContext将从指定文件的绝对路径中寻找XML配置文件,赵到并装载完成ApplicationContext的实例化工作。
public static void main(String[] args){
//初始化Spring容器ApplicationContext,加载配置文件
Application appCon=new FileSystemXmlApplicationtext("D:\eclipse-work
space\ch1\src\applicationContext.xml");
//通过容器获取test实例
TestDao tt=(TestDao)appCon.getBean("test");
tt.sayHello();
}
采用绝对路径的加载方式将导致程序的灵活性变差,一般不推荐使用。因此,通常在Spring的Java应用中采取通过ClassPathXmlApplicationContext类是实例化ApplicationContext容器的方式,而在Web应用中,ApplicationContext容器的实例化工作交给Web服务器完成。
3.2.3 通过Web服务器实例化ApplicationContext容器
在Web服务器实例化ApplicationContext容器时,一般使用基于org.springframework.web.context.Context.ContextLoaderListener的实现方式(需要将spring-web-5.0.2.RELEASE.jar复制到WEB-INF/lib目录中),此方法只需要在web.xml中添加如下代码:
<context-param>
<!--加载src目录下的applicationContext.xml文件 -->
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:applicationContext.xml
</param-value>
</context-param>
<!--指定以ContextLoaderListener方式启动Spring容器-->
<listener>
<listener-class>
org-springframework-web-context.ContextLoaderListener
</listener-class>
</listener>
4.在Spring框架中,什么是控制反转?什么是依赖注入?使用控制反转与依赖注入有什么优点?
控制反转:
当Spring框架出现后,对象的实例不再由调用者来创建,而是有Spring容器来创建。Spring容器会负责控制程序之间的关系,而不是由调用者的程序代码直接控制。这样,控制权由调用者转移到Spring容器,控制权发生了反转,这就是Spring的控制反转。
依赖注入:
从Spring容器的角度看,Spring容器负责将依赖对象赋值给调用者的成员变量,相当于为调用者注入它所依赖的实例,这就是Spring的依赖注入。
总结:
控制反转是一种通过描述(在Spring中可以是XML或注解)并通过第三方产生或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方式的是依赖注入。
控制反转的优点:
1.获取对象方便,可以通过注解等方式获取对象,打破传统的获取方式。
2.对象不在由程序本身进行创建,而是交给spring创建,所以降低了程序之间的依赖关系,从而达到解耦。
3.控制反转能做到更多的事情,比如说事务控制。
4.后期维护方便
依赖注入的优点:
1.构造方法注入:
优点:
1、“在构造期即创建一个完整、合法的对象”,对于这条Java设计原则,构造无疑是最好的响应者。
2、避免了繁琐的setter方法的编写,所有依赖关系均在构造函数中设定,依赖关系集中呈现,更加易读。
3、由于没有setter方法,依赖关系在构造时由容器一次性设定,因此组件在被创建之后即处相对“不变”的稳定状态,无需担心上层代码在调用过程中执行setter方法对组件依赖关系产生破坏,特别是对于Singleton模式的组件而言,这可能对整个系统产生重大的影响。
4、同样,由于关联关系仅在构造函数中表达,只有组件创建者需要关心组件内部的依赖关系。对调用者而言,组件中的依赖关系处于黑盒之中。对上层屏蔽不必要的信息,也为系统的层次清晰性提供了保证。
5、通过构造子注入,意味着我们可以在构造函数中决定依赖关系的注入顺序,对于一个大量依赖外部服务的组件而言,依赖关系的获得顺序可能非常重要,比如某个依赖关系注入的先决条件是组件的DataSource及相关资源已经被设定
2.setter方法注入:
优点:
1、对于习惯了传统JavaBean开发的程序员而言,通过setter方法设定依赖关系显得更加直观,更加自然。
2、如果依赖关系(或继承关系)较为复杂,那么构造模式的构造函数也会相当庞大(我们需要在构造函数中设定所有依赖关系),此时setter模式往往更为简洁。
3、对于某些第三方类库而言,可能要求我们的组件必须提供一个默认的构造函数(如Struts中的Action),此时构造类型的依赖注入机制就体现出其局限性,难以完成我们期望的功能。
5.Bean的配置
Spring可以看作一个大型工程,用于生产和管理Spring容器中的Bean。如果要使用这个工厂生产和管理Bean,需要开发者将Bean配置在Spring的配置文件中。Spring框架支持XML和Properties两种格式的配置文件,在实际开发中常用XML格式的配置文件。
属性或子元素名称 | 描述 |
---|---|
id | Bean在BeanFactory中的唯一标识,在代码中通过BeanFactory获取Bean实例时需要以此作为索引名称 |
class | Bean的具体实现类,使用类的名(例如dao.TestDIDaoImpl) |
scope | 指定Bean实例的作用域 |
<'constructor-args> | <.bean>元素的子元素,使用构造方法注入,指定构造方法的参数。该元素的index睡醒指定参数的序号,ref属性指定对BeanFactory中其他Bean的引用关系,type属性指定参数类型,value属性指定参数的常量值 |
<property.> | <.bean>元素的子元素,用于设置一个属性。该元素的name属性指定Bean实例中相应的属性名称,value属性指定Bean的属性值,ref属性指定属性对BeanFactory中其他Bean的引用关系 |
<list.> | <property.>元素的子元素,用于封装List或数组类型的依赖注入 |
<map.> | <property.>元素的子元素,用于封装Map类型的依赖注入。 |
<set.> | <property.>元素的子元素,用于封装Set’类型的依赖注入。 |
<entry.> | <map.>元素的子元素,用于设置一个键值对。 |
Bean的配置实例代码如下:
<?xml version="1.0" encoding="UTF-8">
<beans xmlns="https://www.springframework.org/schema/beans"
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.xsd">
<!-- 使用id属性定义myTestDIDao,其对应的实现类为dao.TestDIDaoImpl -->
<bean id="myTestDIDao" class="dao.TestDIDaoImpl"/>
<!-- 使用构造方法注入-->
<bean id="testDIService" class="service.TestDIServiceImpl">
<!-- 给构造方法传引用类型的参数值myTestDIDao -->
<constructor-arg index="0" ref="myTestDIDao"/>
</bean>
</beans>
6.bean的实例化有哪几种常见的方法?
6.1.构造方法实例化
Spring框架中,Spring容器可以调用Bean对应类中的无参数构造来实例化Bean,这种方式称为构造方式实例化。
代码如下:
6.1.1.创建BeamClass类
/*
*创建BeamClass类
*/
package instance;
public class BeanClass{
public String message;
public BeanClass(){
message="构造方法实例化Bean";
}
public BeanClass(){
message =s;
}
}
6.1.2.创建配置文件<applicationContext.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"
xsi:schemaLocation="http:springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--构造方法实例化Bean-->
<bean id="constructorInstance" class="instance.BeanClass"/>
</beans>
6.1.3.创建TestInstance测试类,代码如下:
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import instance.BeanClass;
public class TestInstance{
public static void main(String[] args){
//初始化Spring容器ApplicationContext,加载配置文件
ApplicationContext appCon=new ClassPatXmlApplicationContext("applicationContext.xml");
//测试构造方法实例化Bean
BeanClass b1=(BeanClass) appCon.getBean("constructorInstance");
System.out.println(b1+b1.message);
}
}
6.2.静态工产实例化
在使用静态工厂实例化Bean时要求开发者在工厂类中创建一个静态方法来常见Bean的实例。在配置Bean时,class属性指定静态工厂类,同时还需要使用factory-method属性指定工厂类中的静态方法。
6.2.1.创建工厂类BeanStaticFactory
在instance包中创建工厂类BeanStaticFactory,该类中有一个静态方法来实例化对象,具体代码如下:
package instance;
public class BeanStaticFactory{
private static BeanClass beanInstance=new BeanClass("调用静态工厂方式实例化Bean");
public static BeanClass createInstance(){
return beanInstance;
}
}
6.2.2.编辑配置文件
在配置文件applicationContext.xml中添加如下配置代码:
<!-- 静态工厂方法实例化Bean,createInstance为静态工厂类BeanStaticFactory中的静态方法 -->
<bean id="staticFactoryInstance" class="instance.BeanStaticFactory" factory-method="createInstance"/>
6.3.3.添加测试代码
在测试类TestInstance添加如下代码:
//测试静态工厂方法实例化Bean
BeanClass b2=(BeanClass) appCon.getBean("staticFactoryInstance");
System.out.prinln(b2+b2.message);
6.3.实例工厂实例化
在使用实例化工厂实例化Bean时要求开发者在工厂类中创建一个实例方法来创建Bean的实例。在配置Bean时需要使用factory-bean属性指定配置的实例工厂,同时还需要使用factory-method属性指定实例工厂中的实例方法。
6.3.1.创建工厂类BeanInstanceFactory
在instance包中创建工厂类BeanInstanceFactory,该类中有一个实例方法来实例化对象,具体代码如下:
package instance;
public class BeanInstanceFactory{
public BeanClass createBeanClassInstance(){
return new BeanClass("调用实例工厂方法实例化Bean");
}
}
6.3.2.编辑配置文件
在配置文件applicationContext.xml中添加如下配置代码:
<!--配置工厂-->
<bean id="myFactory" class="instace.BeanInstaceFactory"/>
<!-- 使用factory-bean属性指定配置工厂,使用factory-method属性指定使用工厂中的哪个方法实例化Bean-->
<bean id="instanceFactoryInstance" factory-bean="myFactory" factory-method="createBeanClassInstance"/>
6.3.3 添加测试代码
在测试类TestInstance中添加如下代码:
//测试实例工厂方法实例化Bean
BeanClass b3=(BeanClass) appCon.getBean("instaceFactoryInstance");
System.out.println(b3+b3.message);
7.Bean的作用域
作用域名称 | 描述 |
---|---|
singleton | 默认的作用域,使用singleton定义的Bean在Spring容器中只有一个Bean实例 |
prototype | Spring容器每次获取prototype定义的Bean,容器都将创建一个新的Bean实例 |
request | 再一次HTTP请求中容器将返回一个Bean实例,不同的HTTP请求返回不同的Bean实例。 |
session | 在一个HTTP Session中,容器将返回同一个Bean实例。仅在Web Spring应用程序上下文中使用 |
application | 为每个ServletContext对象创建一个实例,即同一个应用共享一个Bean实例。仅在Web Spring应用程序上下文中使用 |
websocket | 为每个WebSocket对象创建一个Bean实例。仅在Web Spring应用程序上下文中使用 |
7.1.singleton作用域
当将bean的scope设置为singleton时,Spring IoC容器仅生成和管理一个Bean实例。在使用id或name获取Bean实例时,IoC容器将返回共享的Bean实例。
由于singleton是scope的默认方式,因此有两种方式将bean的scope设置为singleton。配置文件实例代码如下:
<bean id="constructorInstance" class="instance.BeanClass"/>
或
<bean id="constructorInstance" class="instance.BeanClass" scope="singleton"/>
7.2 prototype作用域
当将bean的scope设置为prototype时,Spring IoC容器将为每次请求创建一个新的实例。
<bean id="constructorInstance" class="instance.BeanClass" scope="prototype"/>
8.Bean的生命周期
一个对象的生命周期包括创建(实例化与初始化)、使用以及销毁等阶段,在Spring中,Bean对象周期也遵循这一过程,但是Spring提供了许多对外接口,允许开发者对三个过程(实例化、初始化、销毁)的前后做一些操作。在Spring Bean中,实例化是为Bean对象开辟空间,初始化则是对属性的初始化。
Spring 容器可以管理singleton作用域Bean的生命周期,在此作用域下,Spring能够精确地知道Bean何时被创建,何时初始化完成,以及何时被销毁。而对于prototype作用域的Bean,Spring只负责创建,当容器创建了Bean的实例后,Bean实例就交给了客户端的代码管理,Spring容器将不再跟踪其生命周期,并且不会管理哪些被配置成prototype作用域Bean。Spring中Bean的生命周期的执行是一个很复杂的过程,可借鉴Servlet的生命周期"实例化-->初始化(int)-->接受请求(service)-->销毁(destroy)"来理解Bean的生命周期。
8.1.Bean的生命周期的整个过程如下:
- 根据Bean的配置情况实例化一个Bean。
- 根据Spring上下文对实例化的Bean进行依赖注入,即对Bean的属性进行初始化。
- 如果Bean实现了BeanNameAware接口,将调用它实现的setBeanName(String beadId)方法,此处参数传递的是Spring配置文件中Bean的id。
- 如果Bean实现了BeanFactoryAware接口,将调用它实现的setBeanFactory方法,此处参数传递的是当前Spring工厂实例的引用。
- 如果Bean实现了ApplicationContextAware接口,将调用它实现的setApplication Context(ApplicationContext)方法,此处参数传递的是Spring上下文实例的引用。
- 如果Bean关联了BeanPostProcessor接口,将调用初始化方法postProcess BeforeInitialization(Object obj,String s)对Bean进行操作。
- 如果Bean实现了InitializingBean接口,将调用afterPropertiesSet方法。
- 如果Bean在Spring配置文件中配置了init-method属性,将自动调用其配置的初始化方法。
- 如果Bean关联了BeanPostProcessor接口,将调用postProcessAfterInitialization(Object obj,String s)方法,由于是在Bean初始化结束时调用After方法,也可用于内存或缓存技术。
- 当Bean不在需要时将进入销毁阶段,如果Bean实现了DisposableBean接口,则调用其实现的destroy方法将Spring中的Bean销毁。
- 如果在配置文件中通过destroy-method属性指定了Bean的销毁方法,将调用其配置的销毁方法进行销毁。
在Spring中,通过实现特定的接口或通过<Bean.>元素的属性,但不建议过多地使用Bean实现接口,因为这样将使代码和Spring聚合比较紧密。
8.2.Bean的生命周期代码演示
8.2.1.创建Bean的实现类
package life;
public class BeanLife{
public void initMyself(){
System.out.println(this.getClass().getName()+"执行自定义的初始化方法");
}
public void destroyMyself(){
System.out.println(this.getClass().getName()+"执行自定义销毁方法");
}
}
8.2.2.配置Bean
在Spring配置文件中视同实现类BeanLife配置一个id为beanLife的Bean,具体代码如下:
<!-- 配置bean,使用init-method属性指定初始化方法,使用destroy-method属性指定销毁方法-->
<bean id="beanLife" class="life.BeanLife" init-method="initMyself" destroy-method="destroyMyself"/>
8.2.3.测试生命周期
创建测试类TestLife,具体代码如下:
package test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import life.BeanLife;
public class TestLife{
public static void main(String[] args){
//初始化Spring容器,加载配置文件
//为了方便演示销毁方法的执行,这里使用ClassPathXmlApplicationContext
//实现类声明容器
ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
System.out.println("获取对象前");
BeanLife bLife=(BeanLife)ctx.getBean("beanLife");
System.out.println("获得对象后"+blife);
ctx.close();//关闭容器,销毁Bean对象
}
}
9.Bean的装配方式
Bean的转配可以理解为将Bean依赖注入到Spring容器中,Bean的转配方式即Bean依赖注入的方式。Spring容器支持基于XML配置的装配、基于注解的装配以及自动装配等多种装配方式,其中最受青睐的装配方式是基于注解的装配。
9.1.基于XML配置的装配
在使用构造方法注入方式装配Bean时,Bean的实现类需要提供带参数的构造方法,并在配置文件中使用<bean.>元素的子元素<constructor-arg.>来定义构造方法的参数;在使用属性的setter方法注入方式装配Bean时,Bean的实现类需要提供一个默认无参数的构造方法,并为需要注入的属性提供对应的setter方法,另外还需要使用<bean.>元素的子元素<property.>为每个属性注入值。
9.1.1.创建Bean的实现类
package assemble;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class ComplexUser{
private String uname;
private List<String> hobbyList;
private Map<String,String> residenceMap;
private Set<String> aliasSet;
private String[] array;
/*
* 使用构造方法注入,需要提供带参数的构造方法
*/
public ComplexUser(String uname,List<String> hobbyList,Map<String,String> residenceMap,Set<String> alicsSet,String[] array){
super();
this.uname=uname;
this.hobbyList=hobbyList;
this.residenceMap=residenceMap;
this.aliasSet=aliasSet;
this.array=array;
}
/**
*使用属性的setter方法注入,提供默认无参数的构造方法,并为注入的属性提供setter方法
*/
public ComplexUser(){
super();
}
/*****此处省略所有的属性的setter方法******/
@Override
public String toString(){
return "uname"+uname+";hobbyList="+hobbyList+";redisenceMap="+residenceMap+";aliasSet="+aliaSet+";array="+array;
}
}
9.1.2.配置Bean
在Spring配置文件中使用实现类ComplexUser配置Bean的两个实例,具体代码如下:
<!--使用构造方法注入方式装配ComplexUser实例user1-->
<bean id="user1" class="assmble.ComplexUser">
<constructor-arg index="0" value="chenheng1"/>
<constructor-arg index="1">
<list>
<value>唱歌</value>
<value>跳舞</value>
<value>爬山</value>
</list>
</constructor-arg>
<constructor-arg index="2">
<map>
<entry key="dalian" value="大连"/>
<entry key="beijing" value="北京"/>
<entry key="shanghai" value="上海">
</map>
</constructor-arg>
<constructor-arg index="3">
<set>
<value>陈恒100</value>
<value>陈恒101</value>
<value>陈恒102</value>
</set>
</constructor-arg>
<constructor-arg index="4">
<array>
<value>aaaaa</value>
<value>bbbbb</value>
</array>
</constructor-arg>
</bean>
<!--使用属性的setter方法注入装配ComplexUser实例user2-->
<bean id="user2" class="assemble.ComplexUser">
<property name="uname" value="chencheng2"/>
<property name="hobbyList">
<list>
<value>看书</value>
<value>学习Spring</value>
</list>
</property>
<property name="residenceMap">
<map>
<entry key="shenzhen" value="深圳">
<entry key="guangzhou" value="广州">
<entry key="tianjin" value="天津">
</map>
</property>
<property name="aliasSet">
<set>
<value>陈恒103</value>
<value>陈恒104</value>
<value>陈恒105</value>
</set>
</property>
<property name="arrya">
<array>
<value>cccccc</value>
<value>dddddd</value>
</array>
</property>
</bean>
9.1.3.测试基于XML配置的装配方式
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import assemble.ComplexUser;
public class TestAssemble{
public static void main(String[] args){
ApplicationContext appCon=new ClassPathXmlApplicationContext("applicationContext.xml");
//使用构造方法装配测试
ComplexUser u1= (ComplexUser) appCon.getBean("user1");
System.out.println(u1);
//使用setter方法装配测试
ComplexUser u2=(ComplexUser) appCon.getBean("user2");
System.out.println(u2);
}
}
9.2.基于注解的装配
在Spring框架中,尽管使用XML配置文件可以很简单地装配Bean,但如果应用中有大量的Bean需要装配,会导致XML配置文件过于庞大,不方便以后的升级与维护,因此更多的时候推荐开发者使用(annotation)的方式去装配Bean。
-
@Component
该注解是一个泛华的概念,仅仅表示一个组件对象(Bean),可以作用在任何层次上。9.2.1.创建Bean的实现类
package annotation; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @Component() /**相当于@Component("annotationUser")或@Component(value="antotationUser"),annotationUser为Bean的id,默认为首字母小写的类名**/ public class AnnotationUser{ @Value("chenheng")//只注入了简单的值,对于复杂值的注入目前使用该方式还解决不了 private String uname; /**省略setter和getter方法**/ }
9.2.2.配置注解
现在有了Bean的实现类,但还不能进行测试,因为Spring容器并不知道去哪里扫描Bean对象,需要在配置文件中配置注解,方式如下:
<context:component-scan base-package="Bean所在的包路径/">
创建配置文件annotationContext.xml.代码如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="https://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001.XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:chemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.soringframework.org/schema/context/spring-context.xsd"> <!--使用context命名空间,通过Spring扫描指定包annotation及其子包下所有的Bean的实现类,进行注解解析--> <context:component-scan base-package="annotation"/>
9.2.3.测试Bean实例
在test包中创建测试类TestAnnotation,测试上述Bean,具体测试代码如下:
package test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClasPathXmlAoolicationContext; import annotation.AnnotationUser; public class TestAnnotation{ public static void main(String[] args){ ApplicationContext appCon=new ClassPathXmlApplicationContext("annotationContext.xml"); AnnotationUser au=(AnnotationUser) appCon.getBean("annotationUser"); System.out.println(au.getUname()); } }
注:在Spring4.0以上的版本,配置注解指定包中的注解进行扫描前需要事先导入SpringAOP的JAR包spring-aop-5.0.2.RELEASE.jar
-
@Repository
该注解用于将数据访问层(DAO)的类标识为Bean,即注解数据访问层Bean,其功能与@Component -
@Service
该注解用于标注一个业务逻辑组件类(Service层),其功能与@Component相同。 -
@Controller
该注解用于标注一个控制器组件类(SpringMVC的Controller),其功能与@Component相同。 -
Autowired
该注解可以对类成员变量、方法及构造方法进行标注,完成自动装配的工作。通用@Autowired来消除setter和getter方法。默认按照Bean的类型进行装配。 -
@Resource
该注解与@Autowired的功能一样,区别在于该注解默认是按照名称来转配注入的,只有当找不到与名称匹配的Bean时才会按照类型来装配注入;而@Autowired默认按照Bean的类型进行装配,如果想按照名称来装配注入,则需要和@Qualifier注解一起使用。
@Resource注解有两个属性–name和type。name属性指定Bean实例名称,即按照名称来装配注入;type属性指定Bean类型,即按照Bean的类型进行装配。 -
@Qualifier
该注解与@Autowired注解配合使用。当@Autowired注解需要按照名称来装配注入时需要和该注解一起使用,Bean的实例名称由@Qualifier注解的参数指定。
在上面的几个注解中,虽然@Repository、@Service和@Controller等注解的功能与@Component注解相同,但为了使类的标注更加清晰(层次化),在实际开发中推荐使用@Repository标注数据访问层(DAO)、使用@Service标注业务逻辑层(Service层)、使用@Controller标注控制器层(控制层)。
实例:
9.2.3.1.创建DAO层
package annotation.dao;
public interface TestDao{
public void save();
}
package annotation.dao;
import org.springframework.stereotype.Repository;
@Repository("testDao")
/**相当于@Repository,但如果在service层中使用@Resource(name="testDao"),testDao不能省略**/
public class TestDaoImpl implements TestDao{
@Override
public void save(){
System.out.println("testDao save");
}
}
9.2.3.2.创建Service层
package annotation.service;
public interface TestService{
public void save();
}
TestServiceImpl 的代码如下:
package annotation.service;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import annottation.dao.TestDao;
@Service("testService")//相当于@Service
public class TestServiceImpl implements TestService{
@Resource(name="testDao")
/**相当于@Autowired,@Autowired默认按照Bean类型装配**/
private TestDao testDao;
@Override
public void save(){
testDao.save();
System.out.println("testService save");
}
}
9.2.3.3.创建Controller
package annotation.controller;
import org.springframework.beans.facroty.annotation.Autowired;
import org.springframework.stereotype.Controller;
import annotation.service.TestSvice;
@Controller
public class TestController{
@Autowired
private TestService testService;
public void save(){
testService.save();
System.out.println("testController save");
}
}
9.2.3.4.配置注解
由于annotation.dao、annotation.service和annotation.controller包都属于annotation包的子包,因此不需要在配置文件annotationContext.xml中配置注解。
9.2.3.5.创建测试类
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import annotation.controller.TestController;
public class TestMoreAnnotation{
public static void main(String[] args){
ApplicationContext appCon=new ClassPathXmlApplicationContext("annotationContext.xml");
TestController testcon=(TestController)appCon.getBean("testController");
testcon.save();
}
}
10.Spring AOP
Spring AOP是Spring框架体系结构中非常重要的功能模块之一,该模块提供了面向切面编程实现。面向切面编程在事务处理、日志记录、安全控制等操作中被广泛使用。
10.1.1.AOP的概念
AOP(Aspect-Oriented Programming) 即面向切面编程,它与OOP(Object-Oriented Programming,面向对象编程)相辅相成,提供了与OOP不同的抽象软件结构的视角。在OOP中,以类作为程序的基本单元,而AOP中的基本单元是Aspect(切面)。Struts2的拦截器设计就是基于AOP的思想,是个比较经典的应用。
在业务处理代码中通常有日志记录、性能统计、安全控制、事务处理、异常处理等操作。尽管使用OOP可以通过封装或继承的方式达到代码的重用,但仍然有同样的代码分散在各个方法中。因此,采用OOP处理日志记录等操作不仅增加了开发者的工作量,而且提高了升级维护的困难。为了解决此类问题,AOP思想应运而生。AOP采取横向抽取机制,即将分散在各个方法中的重复代码提取出来,然后在程序编译或运行阶段将这些抽取出来的代码应用到需要执行的地方。这种横向抽取机制采用传统的OOP是无法办到的,因为OOP实现的是父子关系的纵向重用。但是AOP不是OOP的替代品,而是OOP的补充,它们相辅相成。
10.1.2.AOP的术语
在SpringAOP框架中涉及以下常用术语。
10.1.2.1.切面
切面(Aspect)是指封装横切到系统功能(例如事务处理)的类。
10.1.2.2.连接点
连接点(Joinpoint)是指程序运行中的一些时间点,例如方法的调用或异常的抛出。
10.1.2.3.切入点
切入点(Poinicut)是指需要处理的连接点。在Spring AOP中,所有的方法执行都是连接点,而切入点是一个描述信息,它修饰的是连接点,通过切入点确定哪些连接点需要被处理。
10.1.2.4.通知
通知(Advice)是由切面添加到特定的连接点(满足切入点规则)的一段代码,即在定义好的切入点处所执行的程序代码,可以将其理解为切面开启后切面的方法,因此通知识切面的具体实现。
10.1.2.5.引入
引入(Introduction)允许在现有的实现类中添加自定义的方法和属性。
10.1.2.6.目标对象
目标对象(Target Object)是指所有被通知的对象。如果AOP框架使用运行时代理的方式(动态的AOP)来实现切面,那么通知对象总是一个代理对象。
10.1.2.7.代理
代理(Proxy)是通过应用到目标对象之后被动态创建的对象。
10.1.2.8. 织入
织入(Weaving)是将切面代码插入到目标对象上,从而生产代理对象的过程。根据不同的实现技术,AOP织入有三种方式:编制期织入,需要有特殊的Java编译器;类装载期织入,需要有特殊的类装载器;动态代理织入,在运行期为目标类添加通知生产子类的方式。SoringAOP框架默认采用动态代理织入,而AspectJ(基于Java语言的AOP框架)采用编译器织入和类装载期织入。
10.2.动态代理
在Java中有多种代理技术,例如JDK、CGLIB、CGLIB、Javassist、ASM,其中最常用的动态代理技术是JDK和CGLIB。目前,在Spring AOP中常用JDK和CGLIB两种动态代理技术。
10.2.1.JDK动态代理
JDK动态代理是java.lang.reflect.*包提供的方式,它必须借助一个接口才能产生代理对象。因此,对于使用业务接口的类,Spring默认使用JDK动态代理实现AOP。下面通过一个实例演示如何使用JDK动态代理实现SpringAOP,具体步骤如下:
10.2.2.1.创建应用
创建一个名为ch4的Web应用,并导入所需的JAR包。
10.2.2.2.创建接口及实现类
在ch4的src目录下创建一个dynamic.jdk包,在该包中创建接口TestDao和接口实现类TestDaoImpl。该实现类作为目标类,在代理类中对其方法进行增加处理。
TestDao的代码如下:
package dynamin.jdk;
public interface TestDao{
public void save();
public void modify();
public void delete();
}
TestDaoImpl的代码如下:
package dynamic.jdk;
public class TestDaoImpl implements TestDao{
@Override
public void save(){
System.out.println("保存");
}
@Override
public void modify(){
System.out.println("修改");
}
@Override
public void delete(){
System.out.println("删除");
}
}
10.2.2.3.创建切面类
在ch4的src目录下创建一个aspect包,在该包中创建切面类MyAspect,注意在该类中可以定义多个通知(增强处理的功能方法)。
MyAspect的代码如下:
package aspect;
/*
*切面类,可以定义多个通知,即增强处理的方法
*/
public class MyAspect{
public void check(){
System.out.println("模拟权限控制");
}
public void except(){
System.out.println("模拟异常处理");
}
public void log(){
System.out.println("模拟日志记录");
}
public void monitor(){
System.out.println("性能检测");
}
}
10.2.2.4.创建代理类
在dynamic.jdk包中创建代理类JDKDynamicProxy。在JDK动态代理中代理类必须实现java.lang.reflect.InvocationHandler接口,并编写代理方法,在代理方法中需要通过Proxy实现动态代理。
JDKDynamicProxy的代码如下:
package dynamic.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import aspect.MyAspect;
public class JDKDynamicProxy implements InvocationHandler{
//声明目标类接口对象(真实对象)
private TestDao testDao;
/**创建代理的方法,建立代理对象和真实对象的代理关系,并返回代理对象**/
public Object createProxy(TestDao testDao){
this.testDao=testDao;
//1.类加载器
ClassLoader cld=JDKDynamicProxy.class.getClassLoader();
//2.被代理对象实现的所有接口
Class [] clazz =testDao.getClass().getInterfaces();
//3.使用代理类进行增强,返回代理后的对象
return Porxy.newProxyInstance(cld,clazz,this);
}
/**
*代理的逻辑方法,所有的动态代理类的方法调用都交给该方法处理
*proxy是被代理对象
*method是将要被执行的方法
*args 是执行方法时需要的参数
*return 指返回代理结果
*/
@Override
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable{
//创建一个切面
MyAspect myAspect=new MyAspect();
//前增强
myAsperct.check();
myAspect.except();
//在目标类上调用方法并传入参数,相当于调用testDao中的方法
Object obj=method.invoke(testDao,args);
//后增强
myAspect.log();
myAspect.monitor();
return obj;
}
}
10.2.2.5.创建测试类
在dynamic.jdk包中创建测试类JDKDynamicTest。在主方法中创建代理对象和目标对象,然后从代理对象中获取对目标对象增强后的对象,最后调用该对象的添加、修改和删除方法。
JDKDynamicTest的代码如下:
package dynamic.jdk;
public class JDKDynamicTest{
public static void main(){
//创建代理对象
JDKDynamicProxy jdkProxy=new JDKDynamicProxy();
//创建目标对象
TestDao testDao=new TestDaoImpl();
/**从代理对象中获取增强后的目标对象,该对象是一个被代理的对象,它会进入代理的逻辑方法ainvoke中**/
TestDao testDaoAdvice=(TestDao) jdkProxy.createProxy(testDao);
//执行方法
testDaoAdvice.save();
System.out.println("=========");
testDaoAdvice.modify();
System.out.println("=========");
testDaoAdvice.delect();
}
}
10.2.2.CGLIB动态代理
JDK 动态代理必须提供接口才能使用,对于没有提供接口的类,只能采取CGLIB动态代理。
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,采用非常底层的字节码技术,对指定的目标;类生成一个子类,并对子类进行增强。在Sprin Core包中已经集成了CGLIB所需要得JAR包,不需要另外导入JAR包。下面通过一个实例演示CGLIB动态代理的实现过程,具体步骤如下:
10.2.2.1.创建目标类
在ch4的src目录下创建一个dynmic.cglib包,在该包中创建目标类TestDao,注意该类不需要实现任何接口。
TestDao的代码如下:
package class TestDao{
public void save(){
System.out.println("保存");
}
public void modify(){
System.out.println("修改");
}
public void delete(){
System.out.println("删除");
}
}
10.2.2.2.创建代理类
在dynmic.cglib包中创建代理类CglibDynamicProxy,该类实现MethodInterceptor接口。
CglibDynamicProxy的代码如下:
package dynamic.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.porxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import aspect.MyAspect;
public class CglibDynamicProxy implements MethodInterceptor{
/**
* 创建代理的方法,生成CGLIB代理对象
* target是目标对象,需要增强的对象
* 返回目标对选哪个的CGLIB代理对象
*/
public Object createProxy(Object target){
//创建一个动态类对象,即增强类对象
Enhancer enhancer=new Enhancer();
//确定需要增强的类,设置其父类
enhancer.setSuperclass(target.getClass());
//确定代理逻辑对象为当前对象,要求当前对象实现MethodInterceptor的方法
enhancer.setCallback(this);
//返回创建的代理对象
return enhancer.create();
}
/**
* intercept 方法会在程序执行目标方法时被调用
* proxy是CGLIB根据指定父类生成的代理对象
* method是拦截方法
* args 是拦截方法的参数数组
* methodProxy是方法的代理对象,用于执行父类的方法
* 返回代理结果
*/
@Override
public Object intercept(Object proxy,Mthod method,Object[] args,MethodProxy methodProxy) throws Throwable{
//创建一个切面
MyAspect myAspect=new MyAspect();
//前增强
myAspect.check();
myAspect.except();
//目标方法执行,返回代理结果
Object obj=nethodProxy.invokeSuper(proxy,args);
//后增强
myAspect.log();
myAspect.monitor();
return obj;
}
}
10.2.2.3.创建测试类
在dynamic.cglib包中创建测试类CglibDynamicTest。在主方法中创建代理对象和目标对象,然后从代理对象中获取对目标对象增强后的对象,最后调用该对象的添加、修改和删除方法。
CglibDynamicTest的代码如下:
package dynamic.cglib;
public class CglibDynamicTest{
public static void main(){
//创建代理对象
CglibDynamicProxy cdp=new CglibDynamicProxy();
//创建目标对象
TestDao testDao=new TestDao();
//获取增强后的目标对象
TestDao testDaoAdvice=(TestDao)cdp.createProxy(testDao);
//执行方法
testDaoAdvice.save();
System.out.println("=========");
testDaoAdvice.modify();
System.out.println("=========");
testDaoAdvice.delete();
}
}
10.3.基于代理类的AOP实现
在Spring中默认使用JDK动态代理实现AOP编程。使用org.springframework.aop.framework.ProxyFactoryBean创建代理是Spring AOP实现的最基本方式。
10.3.1.通知类型
在讲解ProxyFactorBean之前先了解一下Spring的通知类型。根据Spring中通知在目标类方法中的连接点位置,通知可以分为6种类型。
1).环绕通知
环绕通知(org.aopalliance.intercept.MethodInterceptor)是在目标方法执行前和执行后实施增强,可应用于日志记录、事务处理等功能。
2)前置通知
前置通知(org.aopalliance.aop.MethodBeforeAdvice)是在目标方法执行前实施增强,可应用权限管理等功能。
3)后置返回通知
后置返回通知(org.springframework.aop.AfterReturningAdvice)是在目标方法成功执行后实施增强,可应用于关闭流、删除临时文件等功能。
4)后置(最终)通知
后置通知(org.springframework.aop.AfterAdvice)是在目标方法执行后实施增强,与后置返回通知不同的是,不管是否发生异常都要执行该类通知,该类通知可应用于释放资源。
5)异常通知
异常通知(org.springframework.aop.ThrowsAdvice)是在方法抛出异常后实施增强,可应用于处理异常、记录日志等功能。
6)引入通知
引入通知(org.springframework.aop.IntroductionInterceptor)是在目标类中添加一些新的方法和属性,可应用于修改目标类(增强类)
10.3.2.ProxyFactoryBean
ProxyFactoryBean是org.springframework.beans.factory.FactoryBean接口的实现类,FactoryBean负责实例化一个Bean实例,ProxyDactoryBean负责为其他Bean实例创建代理实例。ProxyFactoryBean类的常用属性如表所示
属性 | 描述 |
---|---|
target | 代理的目标对象 |
proxyInterfaces | 代理需要实现的接口列表,如果是多个接口,可以使用一下格式赋值:<list.><value.></value.>…<list.> |
interceptorNames | 需要织入目标的Advice |
proxyTargetClass | 是否对类代理而不是接口,默认为false,使用JDK动态代理:当为true时,使用CGLIB动态代理 |
singleton | 返回的代理实例是否为单例,默认为true |
optimize | 当设置为true时强制使用CGLIB动态代理 |
环绕通知实例演示Spring使用ProxyFactoryBean创建AOP代理的过程。
1)导入相关jar包
在核心JAR包的基础上需要向ch4应用的/WEB-INF/lib目录下导入JAR包spring-aop-5.0.2.RELEASE.jar和aopalliance-1.0.jar
aopalliance-1.0.jar是AOP联盟提供的规范包,可以通过地址"http://mvnrepository.com/artifact/aopalliance/aopalliance/1.0"下载。
2)创建切面类
由于该实例实现环绕通知,所以切面类需要实现org.aopalliance.intercept.MethodInterceptor接口。在src目录下创建一个soring.proxyfactorybean包,并在该包中创建切面类MyAspect。
MyAspect的代码如下:
package spring.proxyfactorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 切面类
*/
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation arg0)throw Throwable{
//增强方法
check();
except();
//执行目标方法
Object obj=arg0.proceed();
//增强方法
log();
monitor();
return obj;
}
public void check(){
System.out.println("模拟权限控制");
}
public void except(){
System.out.println("模拟异常处理");
}
public void log(){
System.out.println("模拟日志记录");
}
public void monitor(){
System.out.println("性能监测");
}
}
3)配置切面并指定代理
切面类需要配置为Bean实例,这样Spring 容器才能识别为切面对象。在spring.proxyfactorybean包中创建配置文件applicationContext.xml,并在文件中配置切面和指定代理对象。
applicationContext.xml的代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/benas"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://wwwspringframework.org/schema/bean http://www.springfamework.org/schema/beans/spring-beans.xsd">
<!-- 定义目标对象 -->
<bean id="testDao" class="dynamic.jdk.TestDaoImpl"/>
<!--创建一个切面-->
<bean id="myAspect" class="spring.proxyfactorybean.MyAspect"/>
<!--使用Spring代理工厂定义一个名为testDaoProxy的代理对象-->
<bean id="testDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定代理实现的接口 -->
<property name="proxyInterfaces" value="dynamic.jdk.TestDao"/>
<!--指定目标对象-->
<properxy name="target" ref="testDao"/>
<!--指定切面,织入环绕通知-->
<property name="interceptorNames" value="myAspect"/>
<!--指定代理方式,true指定CGLIB动态代理;默认为false,指定JDK动态代理-->
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
在上述配置文件中首先通过<bean>元素定义了目标对象和切面,然后使用ProxyFactoryBean类定义了代理对象
4)创建测试类
在spring.proxyfactorybean包中创建测试类ProxyFactoryBeanTest,在主方法中使用Spring容器获取代理对象,并执行目标方法。
ProxyFactoryBeanTest的代码如下:
package spring.proxyfactorybean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import dynamic.jdk.TestDao;
public class ProxyFactoryBeanTest{
public static void main(String[] args){
ApplicationContext appCon=new ClassPathXmlApplicationContext("/spring/proxyfactorybean/applicationContext.xml");
//从容器中获取增强后的目标对象
TestDao testDaoAdvice=(TestDao) appCon.getBean("testDaoProxy");
//执行方法
testDaoAdvice.save();
Ststem.out.println("======");
testDaoAdvice.modify();
System.out.println("======");
testDaoAdvice.delete();
}
}
10.4.基于XML配置开发AspectJ
AspectJ是一个基于Java语言的AOP框架。从Spring2.0以后引入了AspectJ的支持。对于目前的Spring框架,建议开发者使用AspectJ实现Spring AOP。使用AspectJ实现Spring AOP的方式有两种,一是基于XML配置开发AspectJ,二是基于注解开发AspectJ。
基于XML配置开发AspectJ是指XML配置文件定义切面、切入点及通知,所有这些定义都必须在<aop:config>元素内。<aop:config>元素及其子元素如表所示:
元素名称 | 用途 |
---|---|
<aop:config>> | 开发AspectJ的顶层配置元素,在配置文件的<benas>下可以包含多个该元素 |
<aop:aspect>> | 配置(定义)一个切面,<aop:config>元素的子元素,属性ref指定切面的定义 |
<aop:ppintcut> | 配置切入点,<aop:aspect>元素的子元素,属性expression指定通知增强哪些方法 |
<aop:before> | 配置前置通知,<aop:aspect>元素的子元素,属性method指定前置通知方法,属性printcut-ref指定关联的切入点 |
<aop:after-returning> | 配置后置返回通知,<aop:aspect>元素的子元素,属性method指定后置返回通知方法,属性pointcut-ref指定关联的切入点 |
<aop:around> | 配置环绕通知,<aop:aspect>元素的子元素,属性method指定环绕通知方法,属性pointcut-ref指定关联的切入点 |
<aop:after-throwing> | 配置异常通知,<aop:aspect>元素的子元素,属性method指定异常通知方法,属性pointcut-ref指定关联的切入点,没有异常发生时将不会执行 |
<aop:after> | 配置后置(最终)通知,<aop:aspect>元素的子元素,属性method指定后置(最终)通知方法,属性pointcut-ref指定关联的切入点 |
<aop:declare-paretns> | 给通知引入新的额外接口,增强功能,不要求掌握该类型的通知 |
下面通过一个实例演示基于XML配置开发AspectJ的过程
10.4.1.导入AspectJ框架相关的JAR包
需要向ch4应用的/WEB-INF/lib目录下导入JAR包spring-aspects-5.0.2.RELEASE.jar和aspectjweaver-1.8.13.jar。
spring-aspects-5.0.2.RELEASE.jar是Spring为AspectJ提供的实现,在Spring的包中已提供。
aspectjweaver-1.8.13.jar是AspectJ 框架所提供的规范包,可以通过地址"http://mvnrepository.com/artifact/org.aspectj/aspectjweaver/1.8.13"下载。
10.4.2.创建切面类
在ch4应用的src目录下创建asoectj.xml包,在该包中创建切面类MyAspect,并在该类中编写各种类型的通知。
MyAspect的代码如下:
package aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspect.lang.ProceedingJoinPoint;
/**
* 切面类,在此类中编写各种类型的通知
*/
public class MyAspect{
/**
* 前置通知,使用JoinPoint接口作为参数获得目标对象信息
*/
public void before(JoinPoint jp){
System.out.print("前置通知:模拟权限控制");
System.out.println(",目标类对象:"+jp.getTarget()+",被增强处理的方法:"+jp.getSignature().getName());
}
/**
* 后置返回通知
*/
public void afterReturning(JoinPoint jp){
System.out.print("后置返回通知:"+"模拟删除临时文件");;
System.out.println(",被增强处理的方法:"+jp.getSignature().getName());
}
/**
* 环绕通知
* ProceedingJoinPoint是JoinPoint的子接口,代表可以执行的目标方法
* 返回值的类型必须是Object
* 必须返回一个参数是ProceedingJoinPoint类型
* 必须throws Throwable
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法前,模拟开启事物");
//执行当前目标方法
Object obj=pjp.proceed();
//结束
System.out.println("环绕结束:执行目标方法后,模拟关闭事务");
return obj;
}
/**
* 异常通知
*/
public void except(Throwable e){
System.out.println("异常通知:"+"程序执行异常"+e.getMessage());
}
/**
* 后置(最终)通知
*/
public void after(){
System.out.println("最终通知:模拟释放资源");
}
}
10.4.3.创建配置文件,并编写相关配置
在aspectj.xml包中创建配置文件applicationContext.xml,并为<aop:config>元素及其子元素编写的相关配置。
applicationContext.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:aop="http://www.springframework.org/scheam/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/scheam/aop
http://www.springframework.org/schema/aop/sprong-aop.xsd">
<!--定义目标对象-->
<bean id="testDao" class="dynamic.jdk.TestDaoImpl"/>
<!--定义切面-->
<bean id="myAspect" class="aspectj.xml.MyAspect"/>
<!--AOP配置-->
<aop:config>
<!--配置切面-->
<aop:aspect ref="myAspect">
<!--配置切入点,通知增加哪些方法-->
<aop>
</aop:aspect>
</aop:config>
</beans>
在上述配置文件中,expression="execution(* dunamic.jdk.*.*(..))"是定义切入点表达式,该切入点表达式的意思是匹配dynamic.jdk包中任意类的任意方法的执行。其中,execution(*dynamic.jdk.*.*(..))是表达式的主体,第一个*表示的是返回类型,使用*代表所有类型;dynamic.jdk表示的是需要匹配的包名,后面第二个*表示的是类名,使用*代表匹配包中所有的类;第三个*表示的是方法名,使用*表示所有方法;后面的(..)表示方法的参数,其中的".."表示任意参数。另外,注意第一个*和包名之间有一个空格。
10.4.4.创建测试类
在aspectj.xml包中创建测试类XMLAspectJTest,在主方法中使用Spring容器获取代理对象,并执行目标方法。
XMLAspectJTest的代码如下:
package aspectj.xml;
import org.springframework.context.ApplicationContext;
import org.springframe.context.support.ClassPathXmlApplicationContext;
import dynamic.jdk.TestDao;
public class XMLAspectJTest{
public static void main(String[] args){
ApplicationContext appCon=new ClassPathXmlApplicationContext("/aspectj/xml/applicationContext.xml");
//从容器中获取增强后的目标对象
TestDao testDaoAdvice=(TestDao)appCon.getBean(""testDao);
//执行方法
testDaoAdvice.save();
}
}
10.5.基于注解开发AspectJ
基于注解开发AspectJ要比基于XML配置开发AspectJ便捷许多。
注解名称 | 描述 |
---|---|
@Aspect | 用于定义一个切面,注解在切面类上 |
@Pointcut | 用于定义切入点表达式。在使用时需要定义一个切入点方法,该方法是一个返回值void切方法体为空的普通方法。 |
@Before | 用于定义前置通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式 |
@AfterReturning | 用于定义后置返回通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式 |
@Around | 用于定义环绕通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式 |
@AfterThrowing | 用于定义异常通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以直接定义切入点表达式。另外,还有一个throwing属性用于访问目标方法抛出的异常,该属性值与异常通知方法中同名的形参一致。 |
@After | 用于定义后置(最终)通知。在使用时通常为其指定value属性值,该值可以是已有的切入点,也可以是直接定义切入点表达式 |
10.5.1.创建切面类,并进行注解
在ch4应用的src目录下创建aspectj.annotation包,在该包中创建切面类MyAspect。在该类中首先使用@Aspect注解定义一个切面类,由于该类在Spring中是作为组件使用的,所有还需要使用@Component注解;然后使用@Pointcut注解切入点表达式,并通过定义方法来表示切入点名称;最后在每个通知方法上添加相应的注解,并将切入点名称作为参数传递给需要执行增强的通知方法。
MyAspect的代码如下:
package aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 切面类,在此类中编写各种类型的通知
*/
@Asect //对应<aop:aspect ref="myAspect">
@Component //对应<bean id="myAspect" class="aspectj.xml.MyAspect"/>
public class MyAspect{
/**
* 定义切入点
*/
@Pointcut("execution(* dynamic.jdk.*.*(..))")
porivate void myPointCut(){
//对应<aop:pointcut expression="execution(* dynamic.jdk.*.*(..))"
id="myPointCut"/>
/**
* 前置通知,使用Joinpoint接口作为参数获得目标对象信息
*/
@Before("myPointCut()")//对应<aop:before method="before" pointcut-ref="myPointCut"/>
public void before(JoinPoint jp){
System.out.println("前置通知:模拟权限控制");
System.out.println(",目标类对象:"+jp.getTarget()+",被增强处理的方法:"+jp.getSignature().getName());
}
/**
* 后置返回通知
*/
@AfterReturning("myPointCut()")
public void afterReturning(JoinPoint jp){
System.out.print("后置返回通知:"+"模拟删除临时文件");
System.out.println(",被增强处理的方法: "+jp.getSignature().getName());
}
/**
* 环绕通知
* ProceedingJoinPoint是JoinPoint的子接口,代表可以执行的目标方法
* 返回值的类型必须是Object
* 必须一个参数是ProceedingJoinPoint类型
* 必须throws Throwable
*/
@Around("myPointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
//开始
System.out.println("环绕开始:执行目标方法钱,模拟开启事务");
//执行当前目标方法
Object obj=pjp.proceed();
//结束
System.out.println("环绕结束:执行目标方法后,模拟关闭事务");
return obj;
}
/**
* 异常通知
*/
@AfterThrowing(value="myPointCut()",throwing="e")
public void except(Throwable e){
System.out.println("异常通知:"+"程序执行异常"+e.getMessage());
}
/**
* 后置(最终)通知
*/
@After("myPointCut")
public void after(){
System.out.println("最红通知:模拟释放资源");
}
}
}
10.5.2.注解目标类
使用注解@Repository将目标类dynamic.jdk.TestDaoImpl注解为目标对象,注解代码如下:
@Repository("testDao")
10.5.3.创建配置文件
在aspectj.annotation包中创建配置文件applicationContext.xml,并在配置文件中指定需要扫描包,使注解生效,同时需要启动基于注解的AspectJ支持。
applicationContext.xml的代码如下:
<?xml version="1.0" encodong="UTF-8"?>
<benas xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/scheam/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/scheam/beans
http://www.springframework.org/scheam/beans/spring-beans.xsd
http://www.springframework.org/scheam/aop
http://www.springframework.org/schema/aop/spring/aop.xsd
http://www.springframework.org/scheam/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--指定需要扫描的包,使注解生效-->
<context:component-scan base-package="aspectj.annotation"/>
<context:component-scan base-package="dynamic.jdk"/>
<!--启动基于注解的AspectJ支持-->
<aop:aspectj-autoproxy/>
</beans>
11.Spring的事务管理
11.1.Spring的数据库编程
11.1.1.Spring JDBC的配置
<!--配置数据源-->
<bean id="dataSource" calass="org.springframework.jdbc.datasource.DeiverManager-DataSource">
<!--MySQL数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--连接数据库的URL-->
<property name="url" value="jdbc:mysql://localhost:3306/springtest
?characterEncoding=utf8"/>
<!--连接数据库的用户名-->
<property name="username" value="root"/>
<!--连接数据库的密码-->
<property name="password" value="root"/>
</bean>
<!--配置JDBC模板-->
<bena id="jdbcTemplate" class="org,springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
在上述实例代码中,配置JDBC模板时需要将dataSource注入到jdbcTemplate,而在数据库访问层(Dao类)需要使用jdbcTemplate时也将需要将jdbcTemplate注入到对应的Bean中。实例代码如下:
@Repository("testDao")
public class TestDaoImpl implements TestDao{
@Autowired
//使用配置文件的JDBC模板
private JdbcTemplate jdbcTemplate;
}
11.1.2.Spring JdbcTemplate的常用方法
public int update(String sql,Object args[]);该方法可以对数据表进行增加、修改、删除等操作。使用args[]设置SQL语句中的参数,并返回更新的行数。实例代码如下:
String insertSql="insert into user values(null,?,?)";
Onject param1[]={"chenheng1","男"};
jdbcTemplate.update(sql,param1);
public Listquery(String sql,RowMapperrowMapper,Object args[]):该方法可以对数据表进行查询操作。rowMapper将结果集映射到用户自定义的类中(前提是自定义类中的属性要与数据表的字段对应。)示例代码如下:
String selectSql="select * from user";
RowMapper<MyUser> rowMapper=new BeanPropertyRowMapper<MyUser>(MyUser.class);
List<MyUser> list=jdbcTemplate.query(sql,rowMapper,null);
11.1.2.1.创建应用并导入JAR包
创建一个名为ch5的web应用,将Spring框架的5个基础JAR包、MySQL数据库的驱动JAR包、Spring JDBC的JAR包以及Spring事务处理的JAR包复制到应用的WEB-INF/lib目录下。ch5应用所添加的JAR包如下所示:
commons-logging-1.2.jar
mysql-connector-java-5.1.45-bin.jar
spring-aop-5.0.2.RELEASE.jar
spring-beans-5.0.2.RELEASE.jar
spring-context-5.0.2.RELEASE.jar
spring-core-5.0.2.RELEASE.jar
spring.expression-5.0.2.RELEASE.jar
spring.jdbc.5.0.2.RELEASE.jar
spring.tx-5.0.2.RELEAER.jar
11.1.2.2.创建并编辑配置文件
在src目录下创建配置文件applicationContext.xml,在该文件中配置数据源和JDBC模板,具体代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/benas"
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:component-scan base-package="com.ch5"/>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DeiverManagerDataSource">
<!--MySQL数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--连接数据库的URL-->
<property name="url" value="jdbc:mysql://localhost:3306/springtest?characterEncoding=utf8"/>
<!--连接数据库的用户名-->
<property name="username" value="root"/>
<!--连接数据库的密码-->
<property name="password" value="root"/>
</bean>
<!--配置JDBC模板-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.code.Jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
11.1.2.3.创建实例类
64