Spring框架
一、Spring概述
1.Spring就是为了解决软件设计的"解耦"而出生的,它提出的控制反转IoC和面向切面编程AOP都是优秀的解耦方式。
(1)IoC将原来主调方自己产生对象的过程交给Spring容器,由Spring容器创建后自动"注入"给主调方。
(2)AOP将混杂在主业务逻辑中的交叉业务逻辑,如事务,日志,安全等,封装成"切面",在主业务逻辑运行的时候,动态的"织入"给主业务逻辑。这样切面可以复用,主业务逻辑也比原来清晰。AOP面向切面编程是对面向对象编程的一个补充。
(3)Spring的优良特性
[1]非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
[2]依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现。
[3]面向切面编程:Aspect Oriented Programming——AOP
[4]容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
[5]组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
[6]一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。
二、Spring与IoC
1.IoC:将原来主调方自己产生对象的过程交给Spring容器,由Spring容器创建后自动"注入"给主调方。通俗讲,不用用户自己创建对象了,而将对象的创建交给Spring容器,并且对象之间的依赖关系也交给Spring容器管理。
举例:
传统的做法:
有Person类
String getName(){
Person p=new Person();
return p.getName();
}
//主调方
main(){
sysout(getName());
}
有Spring框架的做法:
在Spring容器中产生Person的对象,比如对象名为p
String getName(Person p){
return p.getName();
}
//主调方
main(){
//从Spring获得对象p
getName(p);
}
2.Ioc的实现方式:主要分为两种
(1)依赖查找:DL,对象使用者通过Spring提供的回调和上下文,然后自己在Spring容器中查找对应的对象。比如JNDI系统查找。
(2)依赖注入:DI,对象使用者本身不会去new对象也不会去查找Spring容器来获得对象,而是在程序运行的时候,如使用者需要另外的对象支持的时候,Spring容器就"注入"给该使用者,使用者类和被调方类均为POJO(普通类),由于Spring的出现,两者完全基于POJO的依赖。
依赖注入是目前最优秀的解耦方式。
3.第一Spring应用:
开发步骤:
(1)在Eclispe中安装Spring Tools,在线安装地址:https://download.springsource.com/release/TOOLS/update/e4.10/
(2)导入Spring的jar包和log4j的jar包,并配置log4j的属性文件
(3)创建JavaBean
(4)使用Spring Tools创建Spring的配置文件,并配置bean对象:
(选择约束对象与版本)
<!-- 定义一个Emp的bean对象,将emp对象交给Spring容器来管理 -->
<!-- id:该bean的id名称,可以唯一的标志出该bean,class:该bean对应的全类名 -->
<bean id="emp" class="com.edu.beans.Emp"/>
<bean id="student" class="com.atguigu.helloworld.bean.Student">
<!-- 使用property子元素为bean的属性赋值 -->
<property name="studentId" value="1001"/>
<property name="stuName" value="Tom2015"/>
<property name="age" value="20"/>
</bean>
(5)从Spring容器中获得bean对象并使用
//获得Spring容器,从类路径(即src目录)下加载applicationContext.xml配置文件来产生Spring容器
ApplicationContext ac=new ClassPathXmlApplicationContext("applicationContext.xml");
//从Spring容器中通过bean的id获得bean对象
Emp emp = (Emp)ac.getBean("emp");
4.Spring的容器ApplicationContext,有两个实现类:
ClassPathXmlApplicationContext//从classpath下(src目录下)找applicationContext.xml/ioc容器配置文件在类路径下
FileSystemXmlApplicationContext//从文件路径中找applicationContext.xml/iocioc容器配置文件在磁盘路径下
当容器ApplicationContext被实例化后,bean对象就已经被容器创建,而不是在bean被使用时才创建。
存在的几个问题:
1) src.源码包开始的路径,称为类路径的开始;所有源码包里面的东西都会被合并放在类路径里面;
java:/bin/
web:/WEB-INF/classes/
2) 导包commons-logging-1.1.3.jar(依赖)
3) 先导包再创建配置文件
4) Spring的容器接管了标志了s的类
几个细节:
1) ApplicationContext(IOC容器的接口)
2) 给容器中注册一个组件;我们也从组件中按照id拿到了这个组件的对象?
组件的创建工作,是容器完成;
Person对象是什么时候创建好了呢?
3) 同一个组件在ioc容器中是单实例的;容器启动完成都已经创建准备好的
4)容器中如果没有这个组件,获取组件?报异常
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No bean named 'person02' is defined
5) ioc容器在创建这个组件对象的时候,(property)会利用setter方法为javaBean的属性进行赋值
6) javaBean的属性名是由什么决定的?getter/setter方法是属性名:set去掉,后面那一串首字母小写
所有getter/setter方法都自动生成!
当容器ApplicationContext被实例化后,bean对象就已经被容器创建,而不是在bean被使用时才创建。
通过Bean的类型从IOC中获取bean的实例
ApplicationContext ioc =
new ClassPathXmlApplicationContext("ioc.xml");
Person person= (Person) ioc.getBean(Person.class);
如果IOC该类型的bean有多个,就会报错
org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.atguigu.bean.Person] is defined: expected single matching bean but found 2: person01,person02
Person person= ioc.getBean("person01",Person.class);//传id时,规定类型
5.<u>BeanFactory,Spring的容器的另种表示方式。</u>
该类的实现类XmlBeanFactory
BeanFactory bf=new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
Emp emp =(Emp)bf.getBean("emp");
当容器BeanFactory被实例化后,bean对象就没有被容器创建,而是在bean被使用时才创建。
6.Spring容器中bean的作用域:创建<bean>的时候,该标签有一个scope属性,表示该bean的作用域:
(1)singleton:单例的,是<u>默认</u>的作用域,即在整个Spring容器中只有一个该bean对象的实例,无论产生多少次。bean对象在容器初始化 的时候就已经创建。
(2)propotype:多例的,每次从容器中获得的该bean的实例都是一个新的实例。bean对象在使用的时候才初始化,即进行的延迟。
(3)request:对于每一个请求都会创建一个新的bean对象,当然,该属性仅对于WEB应用有效。
(4)session:对于每一次会话都会创建一个新的bean对象,当然,该属性仅对于WEB应用有效。
(5)global session:对于每一次全局的会话会创建一个新的bean对象,主要用于集群方面。
7.Bean后处理器:BeanPostProcessor接口,在Spring容器初始化bean对象的时候,接口的方法被调用,它所包含的两个接口方法:
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
在bean被容器初始化之前被调用的方法,其中参数bean表示当前被初始化的bean对象,beanName表示当前被初始化的bean的id或name,方法返回的对象即为该bean对象。
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
在bean被容器初始化之后被调用的方法,其中参数bean表示当前被初始化的bean对象,beanName表示当前被初始化的bean的id或name,方法返回的对象即为该bean对象。
后处理的配置:
<!-- 配置BeanPostProcessor,该bean由容器自动调用,所以无需id -->
<bean class="com.edu.utils.MyBeanPostProcessor"/>
8.定制bean的始末:
public void setUp() {
System.out.println("setup...................");
}
public void tearDown() {
System.out.println("teardown...................");
}
<bean id="emp" class="com.edu.beans.Emp" init-method="setUp" destroy-method="tearDown"/>
9.Spring的执行流程:bean的生命周期
(1)调用无参构造创建bean
(2)如果bean实现了BeanNameAware,则执行setBeanName(String bn)设置bean名称
(3)如果bean实现了BeanFactoryAware,则执行setBeanFactory(BeanFactory bf)设置BeanFactory
(4)如果bean实现了BeanPostProcessor,则执行BeanPostProcessBeforeInitialization()方法
(6)如果实现了InintializtionBean,则执行AfterPropertiesSet(),此时标志该bean即该bean的属性被初始化完成
(7)调用init-method
(8)执行则执行BeanPostProcessAfterInitialization()
(9)执行bean的业务方法
(10)如果实现了DisposableBean接口,调用destory()
(11)调用destroy-method
基于XML配置的注入
class Person{
private int pid;
private String pname;
public Person(){
}
public Person(int pid,String pname){
this.pid=pid;
this.pname=pname;
}
getter()/setter()
}
<bean id="person" class="Person"></bean>
person.setter();
1.通过Spring创建一个bean对象的时候,其实质就是调用bean的默认构造方法产生对象,此时该bean的属性没有初始化,而属性初始化的工作由Spring来完成,此时Spring给属性赋值的操作成为"注入",注入主要分为设值注入和构造注入。
(1)设值注入(本质是通过setter方法注入)
I.注入简单类型属性值
public class Student {
private int sid;
private String sname;
private int sage;
setter/getter
...
}
<bean id="student" class="com.edu.beans.Student">
<!-- 设置注入,通过调用bean的setter方法来注入值 -->
<property name="sid" value="1"/>
<property name="sname" value="丽丽"/>
<property name="sage" value="24"/>
</bean>
II.注入域属性值(属性的类型是自定义的类型)
public class Student {
private int sid;
private String sname;
private int sage;
private School school;//域属性
setter/getter
...
}
<bean id="student" class="com.edu.beans.Student">
<property name="sid" value="1"/>
<property name="sname" value="张三"/>
<!-- 给基本类型属性注入值,使用value属性注入 -->
<property name="sage" value="21"/>
<!-- 给域属性注入值,采用ref,因为此时的school是引用类型,ref的值为上面的school这个bean的id -->
<property name="school" ref="school"/>
</bean>
(2)构造注入(调用带参的构造函数进行属性值的注入)
<bean id="student" class="com.edu.beans.Student">
<!-- 构造注入:给基本类型属性注入值 -->
<constructor-arg name="sid" value="1"/>
<constructor-arg name="sname" value="李四"/>
<constructor-arg name="sage" value="23"/>
<!-- 构造注入:给引用类型属性(域属性)注入值 -->
<constructor-arg name="school" ref="school"/>
</bean>
(3)p和c命名空间注入
p命名空间注入使用的是setter注入方式
c命名空间注入使用的是构造注入方式
<bean id="student" class="com.edu.beans.Student" p:sid="1" p:sname="丽丽" p:sage="22" p:school-ref="school"/>
<bean id="student" class="com.edu.beans.Student" c:sid="1" c:sname="王五" c:sage="23" c:school-ref="sc"/>
(4)注入集合属性值
<bean id="sCollection" class="com.edu.beans.SCollections">
<property name="strs">
<!-- 对数组类型的属性注入值 -->
<array>
<!-- 注入简单数据类型的数据 -->
<value>abc</value>
<value>def</value>
<value>ghi</value>
</array>
</property>
<property name="students">
<!-- 给List类型的属性注入值 -->
<list>
<!-- 注入的值为其他的引用数据类型 -->
<ref bean="student"/>
<ref bean="student2"/>
</list>
</property>
<property name="ints">
<!-- 给Set类型的属性注入值 -->
<set>
<!-- 注入简单数据类型的数据 -->
<value>123</value>
<value>456</value>
</set>
</property>
<property name="myMap">
<!-- 给map属性注入值 -->
<map>
<!-- 给map中的每一个键值对注入值 -->
<entry key="南京" value-ref="school"/>
<entry key="成都" value-ref="school1"/>
</map>
</property>
<property name="props">
<!-- 给Properties属性注入值 -->
<props>
<!-- 给Properties的每一个键值对赋值 -->
<prop key="addr">南京市</prop>
<prop key="tel">13800000000</prop>
</props>
</property>
</bean>
(5)域属性的自动注入,一个bean中包含的域属性(其他bean作为属性)可以不用显式的注入值,通过Spring的自动注入隐式的注入值。
自动注入的方式分为两种:byName:按名字注入,byType:按类型注入
I.byName:按名字注入,要求被注入域属性值的bean中的域属性名和配置文件中配置的域属性对应的bean的id同名,Spring就会隐式自动注入域属性值。
JavaBean:
public class Student {
private int sid;
private String sname;
private int sage;
private School school;//域属性,域属性名需要和配置文件中School这个bean的id同名,则能完成自动注入
...
getter/setter
}
<bean id="school" class="com.edu.beans.School">
<property name="scid" value="1001"/>
<property name="scname" value="南京千峰教育"/>
</bean>
<!-- 通过autowire="byName"来按名称进行自动注入 -->
<bean id="student" class="com.edu.beans.Student" autowire="byName">
<property name="sid" value="1"/>
<property name="sname" value="张三"/>
<property name="sage" value="21"/>
</bean>
II.byType:按类型注入,要求被注入域属性值的bean中的域属性的类型要和配置文件中配置的域属性对应的bean的类型同源(要么是相同类型,要么是它的子类),Spring就会按类型自动注入域属性值,注意:此时配置文件中只能有一个该类型的域属性对应的bean,多个这样的bean则Spring就不知道用哪一个来自动注入。
JavaBean:
public class Student {
private int sid;
private String sname;
private int sage;
private School school;//域属性,域属性名需要和配置文件中School这个bean的id同名,则能完成自动注入
...
getter/setter
}
<bean id="school" class="com.edu.beans.School">
<property name="scid" value="1001"/>
<property name="scname" value="南京千峰教育"/>
</bean>
<!-- 通过autowire="byName"来按名称进行自动注入 -->
<bean id="student" class="com.edu.beans.Student" autowire="byType">
<property name="sid" value="1"/>
<property name="sname" value="张三"/>
<property name="sage" value="21"/>
</bean>
(6)使用SPEL(Spring的EL表达式,符合OGNL表达式的规则)注入
用法:
调用java的API:
<property name="age" value="#{T(java.lang.Math).random()*30}"/>
<property name="school" value="#{school}"/>
<property name="age" value="#{myUtils.getAge()}"/>
(7)使用内部bean注入:如果用户希望某个bean只能被一个bean内部使用,而不是用于给其他bean共享,则可以把该bean定义为内部bean。
<!-- 内部bean -->
<bean id="student3" class="com.edu.beans.Student">
<property name="sid" value="2"/>
<property name="sname" value="李四"/>
<!-- 给基本类型属性注入值,使用value属性注入 -->
<property name="sage" value="25"/>
<property name="school">
<!-- 给域属性school注入内部bean,该bean只能在student内部使用,所有无需id -->
<bean class="com.edu.beans.School">
<property name="scid" value="2001"/>
<property name="scname" value="杭州千峰"/>
</bean>
</property>
</bean>
(8)使用同类抽象bean注入:同一个类产生的不同对象中如果有一些属性具有相同的值,那么可以将这个类的这些属性专门封装到一个抽象的bean
中,然后把该bean注入给该类的对象,以期复用该抽象bean。
<!--
同类的抽象bean,abstract="true"表示该bean是抽象的,不能实例化,
即不能通过getBean()获取对象,该bean的作用就是用于给其他bean继承
-->
<bean id="department" class="com.edu.beans.Employee" abstract="true">
<property name="deptno" value="1002"/>
<property name="dname" value="研发部"/>
</bean>
<!-- parent="department"表示从上面的抽象bean中继承抽象bean的属性及值 -->
<bean id="emp1" class="com.edu.beans.Employee" parent="department">
<property name="empno" value="7788"/>
<property name="ename" value="SCOTT"/>
</bean>
<bean id="emp2" class="com.edu.beans.Employee" parent="department">
<property name="empno" value="7369"/>
<property name="ename" value="SNAKE"/>
</bean>
(9)使用异类抽象bean注入:不同的类有相同的属性,可以将这些属性抽取出来形成一个异类的抽象bean,从而在其他具有这些属性的bean的定义时,继承这个异类抽象bean,以期复用这个异类抽象bean。
<!--
异类的抽象bean,abstract="true"表示该bean是抽象的,不能实例化,
即不能通过getBean()获取对象,该bean的作用就是用于给其他bean继承
-->
<bean id="dept" abstract="true">
<property name="deptno" value="1002"/>
<property name="dname" value="研发部"/>
</bean>
<!-- parent="dept"表示从上面的抽象bean中继承抽象bean的属性及值 -->
<bean id="emp" class="com.edu.beans.Employee" parent="dept">
<property name="empno" value="7788"/>
<property name="ename" value="SCOTT"/>
</bean>
<bean id="manager" class="com.edu.beans.Manager" parent="dept">
<property name="mno" value="7906"/>
<property name="mname" value="ALEN"/>
</bean>
(10)为Spring指定多个配置文件
I.平行关系的配置文件
比如有spring1.xml,spring2.xml,spring3.xml这三个Spring的配置文件时平行关系的,那么在获得bean对象的时候可以这样使用:
String[] configures={"spring1.xml","spring2.xml","spring3.xml"};
ApplicationContext ac=new ClassPathXmlApplicationContext(configures);
getBean(id); //该id为上面三个xml配置中任何一个里面配置的bean
II.包含关系:
比如springctrl.xml包含springservice.xml,springservice.xml包含springdao.xml,那么可以单独定义service和dao的xml,
然后在springctrl.xml总配置文件中通过<import>标签导入service和dao的xml。
<import resource="springservice.xml"/>
<import resource="springdao.xml"/>
ApplicationContext ac=new ClassPathXmlApplicationContext("springctrl.xml");
getBean(id); //该id为上面三个xml配置中任何一个里面配置的bean
三、基于注解的DI
第一步:定义bean,并注入简单属性值
/*
* @Component等效于xml配置中的<bean>,其value属性等效于xml配置<bean>中的id属性,如果只有一个value属性,可以省略value,
* @Component("school")等效 <bean id="school" class="com.edu.beans.School"/>
* 除了使用@Component定义bean之外,还可以使用@Respository,@Service,@Controller来定义bean,这三个注解的功能是一样,都是定义Spring容器中的bean,扩展 这三个注解的目的是为了后期的扩展,比如@Repository,@Service,@Controller分别表示 Dao,Service,Controller层的bean.
*/
@Component("school")
public class School {
/*
* @Value:注入简单类型的值,如果只有一个value属性,可以省略value。
* 该注解注入值的方式采用的是设值注入,但是如果将该注解添加到属性上面的时候,可以省略掉setter方法,如果有setter方法,该注解还可以加在 setter方法上。
*/
@Value("1001")
private int scid;
@Value("千峰教育")
private String scname;
@Override
public String toString() {
return "School [scid=" + scid + ", scname=" + scname + "]";
}
}
第二步:定义bean,并注入简单属性值和域属性值
@Component("student")
public class Student {
@Value("11")
private int sid;
@Value("丽丽")
private String sname;
@Value("21")
private int sage;
/*
* 此时,对于域属性,注解 方式注入值都采用自动注入的方式,分为按名称自动注入和按类型自动注入
* 按名称自动注入:
* @Autowired
* @Qualifier("school")
*
* 按类型自动注入:
* @Autowired
*
* 另外一个注解即可按名称也可按类型
* 按类型:@Resource
* 按名称:@Resource(name="school")
*/
@Autowired
private School school;//域属性
@Override
public String toString() {
return "Student [sid=" + sid + ", sname=" + sname + ", sage=" + sage + ", school=" + school + "]";
}
}
第三步:扫描注解基本包
<!-- 扫描注解的基本包,让Spring的注解生效 -->
<context:component-scan base-package="com.edu.beans"/>
第四步:测试
1.AOP:
面向切面编程,是程序设计的一种思想,是面向对象编程的一种补充。它的大致思想就是,代码主要分为主业务逻辑和交叉业务逻辑,主业务逻辑主要是指具体的业务,比如转账这个业务,交叉业务逻辑是指混杂在主业务逻辑中的一些其他代码,比如转账这个主业务逻辑在执行的时候,需要事务,日志,安全的代码支持,这些代码被称为交叉业务逻辑,这些交叉业务逻辑混杂在主业务逻辑中使代码看起来混杂不清,逻辑不清晰,AOP编程就是把这些交叉业务逻辑代码单独提取出来,通过AOP编程让这些交叉业务逻辑在主业务逻辑代码运行的时候,动态的"织入"进来,那么,这样主业务也显得清晰,交叉业务逻辑也可以得到复用。
2.引入AOP:
(1)通过继承交叉业务逻辑实现:交叉业务逻辑也纠缠在了主业务逻辑代码中,同时主业务逻辑继承了交叉业务逻辑就不能继承其他类了
(2)通过JDK动态代理方式实现
3.AOP的实现:
(1)AOP是对OOP的补充,OOP是从静态方面考虑代码的复用性,而AOP是从动态方面考虑代码的复用性。AOP的实现常用的方式有两种:通过JDK动态代理实现和通过CGLIB动态代理实现。
(2)AOP,面向切面编程,将可以复用的交叉业务逻辑提取出来形成"切面",通过动态代理,在主业务逻辑方法运行的时候,动态的"织入"给主业务逻辑方法。
(3)AOP编程的相关术语:
I.切面:泛指交叉业务逻辑,比如混杂在转账主业务中的譬如事务,日志,安全等交叉业务逻辑代码叫做"切面",如引入AOP中的MyAspect类就叫做切面
II.织入:在主业务逻辑运行的时候,动态的调用交叉业务逻辑方法的过程叫做"织入",入引入AOP中的new InvocationHandler() 匿名内部类中做的事情就叫做"织入"。
III.连接点:泛指可被织入切面的主业务逻辑方法,如引入AOP中的SomeServiceImpl类的doSome(),doOther()都叫做"连接点"。
IV.切入点:指已经被代理增强了的主业务逻辑方法,比如doSome方法被织入了切面,那么doSome()可以成为切入点,如果doOther没有被织入切面,那么doOther只能叫做连接点,不能叫做切入点。"切入点"定义了"切面"被"织入"到主业务逻辑的"位置",即具体"织入"给哪个主业务逻辑方法。
V.目标对象:即主业务逻辑对应的类的对象,如引入AOP中的service对象。
VI.通知:是切面的一种实现,通知可以完成简单的织入功能,如引入AOP中的new InvocationHandler() 可以泛指通知,通知定义了切面的织入时间,是在主业务方法执行之前执行还是之后执行。切入点定义了切面织入的位置,通知定义了切面织入的时间。
VII.顾问:是切面的另一种实现方式,顾问可以以复杂的方式来织入切面。
(4)Spring提供了对AOP的支持,其他很多框架都提供了对AOP的支持,其中AspectJ也实现了AOP,并且AspectJ支持XML配置和注解方式来使用AOP,并且使用方便快捷,所以Spring又将AspectJ吸收到自己的框架中,所以AOP程序员可以在Spring框架中使用AspectJ提供的AOP编程。
(5)AspectJ是一个面向切面编程的框架,它扩展了java语言,有一套自己对AOP支持的语法,通过自己的特有的编译器将AOP代码最终编译成符合java规范的字节码文件。
(6)AspectJ基于XML配置方式的AOP:
开发步骤:
第一步:导入Spring的所有jar,还要包含对AspectJ支持的jar包:
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.4.RELEASE.jar
spring-aop-4.3.9.RELEASE.jar
spring-aspects-4.3.9.RELEASE.jar
第二步:定义目标类及目标对象(主业务逻辑),将对象放在Spring容器中
<!-- 目标对象:待增强的对象,即即将被织入切面的代码 -->
<bean id="someService" class="com.edu.service.impl.SomeServiceImpl"/>
第三步:定义切面及通知
I.AspectJ的通知类型:通知主要是指切面中包含的方法。
A.前置通知:在目标方法(主业务逻辑方法)执行之前执行的方法。
public class MyAspect { //切面(交叉业务逻辑)
//通知方法(交叉业务逻辑方法)
/*
* 前置通知:方法名任意,可以包含一个JoinPoint类型的参数,该类型的参数可以获得目标方法的相关信息
*/
public void before(JoinPoint jp) {
System.out.println("切面中的前置通知");
System.out.println("切面中的前置通知,目标方法的签名:"+jp.getSignature());
System.out.println("切面中的前置通知,目标对象名:"+jp.getTarget().getClass().getName());
}
}
B.后置通知:在目标方法执行之后执行的方法。
/*
* 后置通知:方法名任意,除了可以包含JoinPoint参数外,还可以包含一个Object的参数,参数名和xml中配置的后置通知的returning属性的值相同,
* 表示目标方法的返回值
*/
public void afterReturning(Object res) {
System.out.println("切面中的后置通知:目标方法的返回值为:"+res);
}
C.环绕通知(方法拦截器):在目标方法执行之前和之后执行的方法。
/*
* 环绕通知:方法名任意,除了可以包含JoinPoint参数外,还可以包含ProceedingJoinPoint参数,该参数的proceed()方法的执行相当于执行目标方法
* 所以
*
*/
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("切面中的环绕通知前置增强.....");
Object res = pjp.proceed(); //相当于执行目标方法,返回值代表目标方法的返回值
System.out.println("切面中的环绕通知后置增强.....");
return res;
}
D.异常通知:在目标方法发生异常的时候执行的方法。
/*
* 异常通知:方法名随意,,除了可以包含JoinPoint参数外,还可以包含Throwalbe类型的参数,参数名需要和aop的xml配置中的throwing属性值相同
* 表示当前的异常对象
*/
public void afterThrowing(Throwable e) {
System.out.println("切面中的异常通知,异常信息:"+e.getMessage());
}
E.最终通知:无论有没有发生异常,该最终通知都会执行的方法,类似try...catch的finally。
第四步:定义切入点表达式:切入点表达式的目的就是指明在哪些目标方法中织入通知
切入点表达式的语法:
execution(访问权限 返回值 全类名.方法名(参数列表) 异常类型)
切入点表达式中的通配符:
符号 意义
* 0个或多个任意字符
.. 用在包中表示当前包及其子包路径
用在方法参数中表示任意的参数
+ 用在类上面表示当前类及其子类
用在接口上面表示当前接口及其实现类
举例:
execution(* *.get(..)):匹配任意的get方法
execution(* com.edu.service..*.doSome(..)):匹配com.edu.service包或其子包下面的所有类的doSome方法,注意:当".."用在包中的时候,后面的类名只能用"*"。
execution(* com.edu.service..*.doOther(Object+)):匹配com.edu.service包或其子包下面的所有类的doOther方法,方法参数匹配所有引用 类型。
第五步:通过AOP的XML配置将通知通过切入点表达式织入到目标方法中
<!-- 目标对象:待增强的对象,即即将被织入切面的代码 -->
<bean id="someService" class="com.edu.service.impl.SomeServiceImpl"/>
<!-- 切面对象:被织入到目标方法中的对象 -->
<bean id="myAspect" class="com.edu.utils.MyAspect"/>
<!-- 定义AspectJ的AOP配置,完成切面织入到目标方法 ,打开aop命名空间-->
<aop:config>
<aop:pointcut expression="execution(* com.edu.service..*.doSome(..))" id="aopPC"/>
<aop:pointcut expression="execution(* com.edu.service..*.doOther(..))" id="aopPC1"/>
<aop:pointcut expression="execution(* com.edu.service..*.doThird(..))" id="aopPC2"/>
<!-- 定义切面,ref:指定切面对象,即为前面定义的切面的id -->
<aop:aspect ref="myAspect">
<!-- 切面的前置通知,method:切面中的前置通知方法的名称, pointcut-ref:前面定义的切入点的id,即将该前置通知织入给切入点指明的目标方法-->
<aop:before method="before" pointcut-ref="aopPC"/>
<!-- 切面的后置通知,returning:返回给通知方法的参数名,表示目标方法的返回值 -->
<aop:after-returning method="afterReturning" pointcut-ref="aopPC1" returning="res"/>
<!-- 环绕通知 -->
<aop:around method="around" pointcut-ref="aopPC2"/>
<!-- 异常通知,throwing:异常通知方法参数名,表示当前发生异常 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="aopPC" throwing="e"/>
</aop:config>
第六步:测试
(7)基于注解的Spring的AspectJ的AOP
开发步骤:
第一步:导入Spring的所有jar,还要包含对AspectJ支持的jar包:
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.4.RELEASE.jar
spring-aop-4.3.9.RELEASE.jar
spring-aspects-4.3.9.RELEASE.jar
第二步:定义目标类及目标对象(主业务逻辑),将对象放在Spring容器中
@Component("someService")
public class SomeServiceImpl implements ISomeService { //目标类(主业务逻辑)
...
}
第三步:定义切面及通知
@Component //将该切面类对象放入Spring容器
@Aspect //定义该类为切面
public class MyAspect { //切面(交叉业务逻辑)
//通知方法(交叉业务逻辑方法)
/*
* 前置通知:方法名任意,可以包含一个JoinPoint类型的参数,该类型的参数可以获得目标方法的相关信息
*/
@Before("execution(* com.edu.service..*.doSome(..))")
public void before(JoinPoint jp) {
System.out.println("切面中的前置通知");
System.out.println("切面中的前置通知,目标方法的签名:"+jp.getSignature());
System.out.println("切面中的前置通知,目标对象名:"+jp.getTarget().getClass().getName());
}
/*
* 后置通知:方法名任意,除了可以包含JoinPoint参数外,还可以包含一个Object的参数,参数名和xml中配置的后置通知的returning属性的值相同,
* 表示目标方法的返回值
*/
@AfterReturning(value = "execution(* com.edu.service..*.doOther(..))",returning = "res")
public void afterReturning(Object res) {
System.out.println("切面中的后置通知:目标方法的返回值为:"+res);
}
/*
* 环绕通知:方法名任意,除了可以包含JoinPoint参数外,还可以包含ProceedingJoinPoint参数,该参数的proceed()方法的执行相当于执行目标方法
* 所以
*
*/
@Around("execution(* com.edu.service..*.doThird(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("切面中的环绕通知前置增强.....");
Object res = pjp.proceed(); //相当于执行目标方法,返回值代表目标方法的返回值
System.out.println("切面中的环绕通知后置增强.....");
return res;
}
/*
* 异常通知:方法名随意,,除了可以包含JoinPoint参数外,还可以包含Throwalbe类型的参数,参数名需要和aop的xml配置中的throwing属性值相同
* 表示当前的异常对象
*/
@AfterThrowing(value = "execution(* com.edu.service..*.doSome(..))",throwing = "e")
public void afterThrowing(Throwable e) {
System.out.println("切面中的异常通知,异常信息:"+e.getMessage());
}
}
第四步:配置自动代理及扫描注解基本包
<context:component-scan base-package="com.edu"/>
<!-- 配置AspectJ的自动代理,会生成注解的AOP代理对象,将扫描注解中注解的切面和通知,然后在目标方法执行时,动态的将它们织入给目标方法 -->
<aop:aspectj-autoproxy/>
第五步:测试