Spring AOP
本小记中学习的目标
-
Spring AOP的相关概念
-
动态代理介绍
-
AOP是如何实现的介绍
-
AspectJ的开发
一、Spring AOP的相关概念
AOP(Aspect-Oriented Programming),面向对象编程。它与OOP(面向对象编程)不一样,OOP中,以类作为程序的基本单元,而AOP则是以Aspect(切面)。
在业务处理代码中会有日志记录、性能统计、安全控制、事务处理、异常的处理等操作。这些操作可以使用封装或继承的方式达到代码重用,但仍然会存在同样的代码全散在各个方法当中。因而使用OOP这种封装、继承的操作和无形地增加了开发的工作量,加大了升级维护的困难,为了解决这个问题,AOP采用了横向抽取机制,把分散在各个方法中的重复代码提取出来,然后在程序编译或运行阶段把这些抽取出来的代码应用到需要执行的地方。传统的OOP则实现的是纵向父子关系的重用。注意:AOP并不是用来替代OOP的它只是OOP的补充。
关于AOP相关术语介绍
-
切面:Aspect,指的是横切到系统功能的类
-
连接点:Joinpoint,是指程序在运行中的一些时间点
-
切入点:Pointcut,需要处理的连接点。在Spring AOP中,所有的方法执行都是连接点,而切入点是一个描述信息,通过切入点来确定哪些连接点需要被处理。
-
通知:Advice,由切面添加到特定的连接点(满足切入点规则)的一段代码,也就是在定义好的切入点处要执行的程序代码。可以理解是切面开启后切面的方法,它是切面的具体实现
-
引入:Introduction,可以在自现有的实现类中添加自定义的方法和属性
-
目标对象:Target Object,指的是所有被通知的对象
-
代理:Proxy,通知应用到目标对象之后被动态创建的对象
-
织入:Weaving,把切面代码插入到目标对象上,从而生成代理对象的过程
二、动态代理介绍
Java中有多种动态代理技术:JDK、CGLIB、Javassist、ASM,其中最为常用的是前两种,Spring AOP中也是常用这两种动态代理技术。
JDK动态代理
这种方式的动态代理是java.lang.reflect.*包提供的方式,它必须要借一个接口才能产生代理对象。
关于普通的使用JDK动态代理的实例
1.新增一个Maven的jar工程
2.新增一个接口类:com.xiaoxie.proxy.jdk.UserDao.java
package com.xiaoxie.proxy.jdk;
public
interface UserDao {
void save();
void update();
void delete();
}
3.新增上述接口的实现类 com.xiaoxie.proxy.jdk.impl.UserDaoImpl
package com.xiaoxie.proxy.jdk.impl;
import com.xiaoxie.proxy.jdk.UserDao;
public
class UserDaoImpl
implements UserDao {
@Override
public
void save() {
System.
out.println(
"保存记录!");
}
@Override
public
void update() {
System.
out.println(
"更新记录!");
}
@Override
public
void delete() {
System.
out.println(
"删除记录!");
}
}
4.创建一个切面类 com.xiaoxie.proxy.jdk.JDKDynamicProxy
package com.xiaoxie.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.xiaoxie.aspect.MyAspect;
public
class JDKDynamicProxy
implements InvocationHandler{
// 声明真实对象接口
private UserDao
userDao;
/*
* 创建代理方法,建立代理对象与真实对象的代理关系,返回代理对象
*/
public Object createProxy(UserDao
userDao) {
this.
userDao =
userDao;
//类加载
ClassLoader
classLoader = JDKDynamicProxy.
class.getClassLoader();
//代理对象实现所有接口
Class[]
interfaces =
userDao.getClass().getInterfaces();
//使用代理类进行增强并返回代理对象
return Proxy.
newProxyInstance(
classLoader,
interfaces,
this);
}
/**
* 代理的逻辑方法,所有动态代理类的方法调用都交由这个方法处理
* arg0:代理对象
* arg1:将要执行的方法
* arg2:执行方法时需要的参数
*
reurn
返回代理结果
*/
@Override
public Object invoke(Object
arg0, Method
arg1, Object[]
arg2)
throws Throwable {
//创建一个切面
MyAspect
myAspect =
new MyAspect();
//在执行目标方法之前执行增强方法
myAspect.checkAutho();
myAspect.handleException();
//执行目标方法并传入参数
Object
obj =
arg1.invoke(
userDao,
arg2);
//在执行目标的方法后再执行增强方法
myAspect.log();
myAspect.perfomanceMonitor();
return
obj;
}
注意:在JDK动态代理中,代理类必须要实现java.lang.reflect.InvovationHandler接口,在实现代理的方法invoke中进行动态代理的实现
5.创建测试类 com.xiaoxie.test.Test,并在其中新增测试方法
private
static
void JDKDynamicTest() {
//创建代理对象
JDKDynamicProxy
jdkDynamicProxy =
new JDKDynamicProxy();
//创建目标对象
UserDao
userDao =
new UserDaoImpl();
//从代理对象中获得增强后的代理对象
UserDao
userDaoAdvice = (UserDao)
jdkDynamicProxy.createProxy(
userDao);
//执行方法
System.
out.println(
"------------save-----------");
userDaoAdvice.save();
System.
out.println(
"------------update---------");
userDaoAdvice.update();
System.
out.println(
"------------delete---------");
userDaoAdvice.delete();
}
6.在main函数中调用这个测试方法,控制台打印结果如下:
------------save-----------
检查权限!
处理异常!
保存记录!
记录日志!
性能测试!
------------update---------
检查权限!
处理异常!
更新记录!
记录日志!
性能测试!
------------delete---------
检查权限!
处理异常!
删除记录!
记录日志!
性能测试!
注意:如果使用JDK的动态代理,必须要提供接口才能使用,比如UserDao
CGLIB动态代理
CGLIB(Code Generation Library)是一个高性能开源的代码生成包,采用非常底层的字节码技术,对于指定的目标类生成一个子类,并对子类进行增强,在Spring Core包中已经集成了CGLIB所需要的jar包。
实例:
1.创建一个目标类 com.xiaoxie.proxy.Employee
package com.xiaoxie.proxy.cglib;
public
class Employee {
public
void save() {
System.
out.println(
"执行保存!");
}
public
void update() {
System.
out.println(
"执行更新!");
}
public
void delete() {
System.
out.println(
"执行删除!");
}
}
2.创建代理类 com.xiaoxie.proxy
package com.xiaoxie.test;
import com.xiaoxie.proxy.cglib.CglibDynamicProxy;
import com.xiaoxie.proxy.cglib.Employee;
import com.xiaoxie.proxy.jdk.JDKDynamicProxy;
import com.xiaoxie.proxy.jdk.UserDao;
import com.xiaoxie.proxy.jdk.impl.UserDaoImpl;
public
class Test {
public
static
void main(String[]
args) {
//JDKDynamicTest();
cglibDynamicTest();
}
//JDK代理
private
static
void
JDKDynamicTest() {
//创建代理对象
JDKDynamicProxy
jdkDynamicProxy =
new JDKDynamicProxy();
//创建目标对象
UserDao
userDao =
new UserDaoImpl();
//从代理对象中获得增强后的代理对象
UserDao
userDaoAdvice = (UserDao)
jdkDynamicProxy.createProxy(
userDao);
//执行方法
System.
out.println(
"------------save-----------");
userDaoAdvice.save();
System.
out.println(
"------------update---------");
userDaoAdvice.update();
System.
out.println(
"------------delete---------");
userDaoAdvice.delete();
}
//CGLIB代理
private
static
void cglibDynamicTest() {
//创建代理对象
CglibDynamicProxy
cglibDynamicProxy =
new CglibDynamicProxy();
//创建目标对象
Employee
employee =
new Employee();
//获得增强后的目标对象
Employee
employeeAdvice = (Employee)
cglibDynamicProxy.createProxy(
employee);
//执行方法
System.
out.println(
"------------save-----------");
employeeAdvice.save();
System.
out.println(
"------------update---------");
employeeAdvice.update();
System.
out.println(
"------------delete---------");
employeeAdvice.delete();
}
}
3.在测试类中新增测试方法
//CGLIB代理
private
static
void cglibDynamicTest() {
//创建代理对象
CglibDynamicProxy
cglibDynamicProxy =
new CglibDynamicProxy();
//创建目标对象
Employee
employee =
new Employee();
//获得增强后的目标对象
Employee
employeeAdvice = (Employee)
cglibDynamicProxy.createProxy(
employee);
//执行方法
System.
out.println(
"------------save-----------");
employeeAdvice.save();
System.
out.println(
"------------update---------");
employeeAdvice.update();
System.
out.println(
"------------delete---------");
employeeAdvice.delete();
}
4.在main方法中调用这个测试方法,控制台打印结果如下
------------save-----------
检查权限!
处理异常!
执行保存!
记录日志!
性能测试!
------------update---------
检查权限!
处理异常!
执行更新!
记录日志!
性能测试!
------------delete---------
检查权限!
处理异常!
执行删除!
记录日志!
性能测试!
基于代理类的Spring AOP实现介绍
在Spring当中默认是使用JDK动态代理实现AOP编程,使用org.springframework.aop.framework.ProxyFactoryBean创建代理是Spring AOP实现的最基本方式。
在学习ProxyFactoryBean之前,先对Spring的通知类型进行认识。根据Spring中通知在目标类方法中的连接点位置,通知可以分为如下6种
1.环绕通知:在目标方法之前和之后进行增强
org.aopalliance.intercept.MethodInterceptor
一般来说可以用于日志的记录、事务的处理等
2.前置通知:在目标方法执行前进行增强 org.springframework.aop.MethodBeforeAdvice
一般来说可以用于权限管理等
3.后置返回通知:在目标方法成功执行后进行增强 org.springframework.aop.AfterReturningAdvice
一般来说可以用于关闭流、删除临时文件等
4.后置通知:在目标方法执行后进行增强,这个与后置返回通知不一样的是,它不管是否执行成功都会做通知 org.springframewrok.aop.AfterAdvice
一般来说可以用于释放资源
5.异常通知:在目标方法抛出异常后进行增强 org.springframework.aop.ThrowsAdvice
一般来说可以用于处理异常、记录异常日志等
6.引入通知:在目标类中添加一些新的方法和属性 org.springframework.aop.IntroductionInterceptor
一般来说可以用于修改目标类
了解了上面的通知类型后我们看一下 ProxyFactoryBean
ProxyFactoryBean,它是org.springframework.beans.factory.FactoryBean接口的一个实现类,FactoryBean负责实例化一个bean,而ProxyFactoryBean则负责为其它bean创建代理实例。
ProxyFactoryBean常用属性
target:代理的目标对象
proxyInterfaces:代理需要实现的的接口列表
interceptorNames:需要织入目标的Advice
proxyTargetClass:是否对类进行代理,默认为false(使用JDK代理),true(使用GCLIB代理)
singleton:返回代理实例是否为单例,默认为true
optimize:这个值为true时,强制使用CGLIB代理
实例
1.在pom.xml中除了需要添加spring的核心jar依赖外还需要aopalliance的依赖
<!--
aopalliance
-->
<
dependency
>
<
groupId
>
aopalliance
</
groupId
>
<
artifactId
>
aopalliance
</
artifactId
>
<
version
>1.0
</
version
>
</
dependency
>
2.创建一个切面类 com.xiaoxie.aspect.UserAspect
package com.xiaoxie.aspect;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 这是一个切面类
* 由于它做环绕通知,需要实现org.aopalliance.intercept.MethodInterceptor
*
@author
adven
*
*/
public
class UserAspect
implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation
arg0)
throws Throwable {
//前增强
check();
except();
//目标方法执行
Object
object =
arg0.proceed();
//后增强
log();
destroy();
return
object;
}
//定义增强方法
private
void check() {
System.
out.println(
"检查用户操作权限~~~~");
}
private
void except() {
System.
out.println(
"异常处理~~~~~");
}
private
void log() {
System.
out.println(
"记录日志~~~~");
}
private
void destroy() {
System.
out.println(
"释放资源~~~~");
}
}
注意:它要实现的接口是:org.aopalliance.intercept.MethodInterceptor,因为这里我们做的环绕通知
3.配置Spring的配置文件 beans.xml
<?
xml
version=
"1.0"
encoding=
"UTF-8"
?>
xsi:schemaLocation=
"
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
"
>
<!-- 定义目标对象 -->
<
bean
class=
"com.xiaoxie.proxy.jdk.impl.UserDaoImpl"
id=
"userDao"
/>
<!-- 定义切面 -->
<
bean
class=
"com.xiaoxie.aspect.UserAspect"
id=
"userAspect"
/>
<!-- 使用Spring代理工厂定义一个代理对象 -->
<
bean
class=
"org.springframework.aop.framework.ProxyFactoryBean"
id=
"userDaoProxy"
>
<!-- 指定目标对象 -->
<
property
name=
"
target"
ref=
"userDao"
/>
<!-- 代理要实现的接口 -->
<
property
name=
"
proxyInterfaces"
value=
"com.xiaoxie.proxy.jdk.UserDao"
/>
<!-- 指定切面 -->
<
property
name=
"
interceptorNames"
value=
"userAspect"
/>
<!-- 指定代理方式 ,这里指定为true,表示使用CGLIB代理-->
<
property
name=
"
proxyTargetClass"
value=
"true"
/>
</
bean
>
</
beans
>
注意:切面类也是要配置Bean的配置信息的,这样Spring容器才知道如何去处理
4.在测试类中新增方法
//Spring AOP CGLIB代理
private
static
void aopCglibTest() {
ApplicationContext
context =
new ClassPathXmlApplicationContext(
"beans.xml");
//获取增强后的代理对象
UserDao
userDaoAdvice = (UserDao)
context.getBean(
"userDaoProxy");
//执行方法
System.
out.println(
"------------save-----------");
userDaoAdvice.save();
System.
out.println(
"------------update---------");
userDaoAdvice.update();
System.
out.println(
"------------delete---------");
userDaoAdvice.delete();
}
5.在main方法中调用这个方法,运行程序在控制台打印的结果如下:
------------save-----------
检查用户操作权限~~~~
异常处理~~~~~
保存记录!
记录日志~~~~
释放资源~~~~
------------update---------
检查用户操作权限~~~~
异常处理~~~~~
更新记录!
记录日志~~~~
释放资源~~~~
------------delete---------
检查用户操作权限~~~~
异常处理~~~~~
删除记录!
记录日志~~~~
释放资源~~~~
AspectJ开发
AspectJ是一个基于Java语言的AOP框架,Spring2.0以后就引入了对AspectJ的支持,在开发中使用AspectJ来实现Spring AOP有两种方式,一是基于xml配置文件开发,另一个是基于注解开发。
一、基于xml配置开发
基于xml配置开发AspectJ是指的通过xml文件来定义切面、切入点及通知,所有的配置在到<aop:config>元素内完成
<aop:config>的元素及其子元素的说明
<aop:config>:它是顶层配置元素,在<beans>下可以包含多个这个元素
<aop:aspect>:定义一个切面,它是<aop:config>的子元素,属性ref指定切面的定义
<aop:pointcut>:配置切入点,它是<aop:config>的子元素,属性expression指定通知增强哪些方法
<aop:before>:配置前置通知,它是<aop:config>的子元素,属性method用来指定前置通知方法,属性pointcut-ref指定关联的切入点
<aop:after-returning>:配置后置返回通知,<aop:config>的子元素,属性method用来指定后置返回通知方法,属性pointcut-ref指定关联的切入点
<aop:around>:配置环绕通知,<aop:config>的子元素,属性method用来指定环绕通知方法,属性pointcut-ref指定关联的切入点
<aop:after-throwing>:配置异常通知,<aop:config>的子元素,属性method用来指定异常通知方法,属性pointcut-ref指定关联的切入点,throwing属性用来指定抛出的异常对象,在没有发生异常时不会执行
<aop:after>:配置后置通知,<aop:config>的子元素,属性method用来指定后置通知方法,属性pointcut-ref指定关联的切入点
<aop:declare-parents>:给通知引入新的额外接口,增强功能
实例
1.新增一个maven的jar工程
2.在pom.xml中编写对应的jar依赖
<
dependencies
>
<!-- 添加Spring核心依赖包 Context -->
<
dependency
>
<
groupId
>org.springframework
</
groupId
>
<
artifactId
>spring-context
</
artifactId
>
<
version
>5.0.2.RELEASE
</
version
>
</
dependency
>
<!-- commons-logging-1.2 -->
<
dependency
>
<
groupId
>commons-logging
</
groupId
>
<
artifactId
>commons-logging
</
artifactId
>
<
version
>1.2
</
version
>
</
dependency
>
<!--
aspectjweaver
-->
<
dependency
>
<
groupId
>org.aspectj
</
groupId
>
<
artifactId
>
aspectjweaver
</
artifactId
>
<
version
>1.8.13
</
version
>
</
dependency
>
<!-- spring-aspects -->
<
dependency
>
<
groupId
>org.springframework
</
groupId
>
<
artifactId
>spring-aspects
</
artifactId
>
<
version
>5.0.2.RELEASE
</
version
>
</
dependency
>
</
dependencies
>
3.新增一个切面类,com.xiaoxie.aspect.MyAspect,在切面类中编写各种类型的通知
package com.xiaoxie.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/*
* 切面类,在这个类中编写各种类型的通知
*/
public
class MyAspect {
//前置通知
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.getTarget() +
",被增强处理的方法:" +
jp.getSignature().getName());
}
/*
* 环绕通知
* 这里的参数是ProceedingJoinPoint,它是JoinPoint的子接口
*/
public Object around(ProceedingJoinPoint
pjp)
throws Throwable {
//开始
System.
out.println(
"开始环绕,在执行目标方法之前执行~~~~~");
//执行目标方法
Object
proceed =
pjp.proceed();
//结束
System.
out.println(
"结束环绕,在执行目标方法之后执行~~~~~");
return
proceed;
}
//异常通知
public
void except(Throwable
e) {
System.
out.println(
"异常通知,程序执行异常" +
e.getMessage());
}
//后置通知
public
void after() {
System.
out.println(
"后置通知,一般用来释放资源操作");
}
}
4.编写目标接口及目标类,com.xiaoxie.dao.UserDao、com.xiaoxie.dao.impl.UserDaoImpl
/*com.xiaoxie.dao.UserDao*/
package com.xiaoxie.dao;
public
interface UserDao {
void save();
void update();
void delete();
}
/*com.xiaoxie.dao.impl.UserDaoImpl*/
package com.xiaoxie.dao.impl;
import com.xiaoxie.dao.UserDao;
public
class UserDaoImpl
implements UserDao {
@Override
public
void save() {
System.
out.println(
"保存记录!");
}
@Override
public
void update() {
System.
out.println(
"更新记录!");
}
@Override
public
void delete() {
System.
out.println(
"删除记录!");
}
}
5.编写Spring配置文件beans.xml
<?
xml
version=
"1.0"
encoding=
"UTF-8"
?>
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop=
"http://www.springframework.org/schema/aop"
xsi:schemaLocation=
"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"
>
<!-- 定义目标对象bean -->
<
bean
class=
"com.xiaoxie.dao.impl.UserDaoImpl"
id=
"userDao"
/>
<!-- 定义切面 -->
<
bean
class=
"com.xiaoxie.aspect.MyAspect"
id=
"myAspect"
/>
<!-- aop:config配置 -->
<
aop:config
>
<!-- 配置切面 -->
<
aop:aspect
ref=
"myAspect"
>
<!-- 配置切入点,expression是定义切入点的表达式,其中第一个*代表是返回值为任意值,后面的两个*表示的是任意类任意方法,后面括号内的..表示方法参数任意 -->
<
aop:pointcut
expression=
"execution(* com.xiaoxie.dao.impl.*.*(..))"
id=
"myPointCut"
/>
<!-- 把通知与切入点作关联 -->
<!-- 关联前置通知 -->
<
aop:before
method=
"before"
pointcut-ref=
"myPointCut"
/>
<!-- 关联后置返回通知 -->
<
aop:after-returning
method=
"afterReturning"
pointcut-ref=
"myPointCut"
/>
<!-- 关联环绕通知 -->
<
aop:around
method=
"around"
pointcut-ref=
"myPointCut"
/>
<!-- 关联异常通知,没有异常的情况下不会执行 -->
<
aop:after-throwing
method=
"except"
pointcut-ref=
"myPointCut"
throwing=
"e"
/>
<!-- 关联后置通知,不管目标方法是否成功执行,它都会执行 -->
<
aop:after
method=
"after"
pointcut-ref=
"myPointCut"
/>
</
aop:aspect
>
</
aop:config
>
</
beans
>
注意:在这里例<aop:config>的时候需要把aop这个命名空间勾选上
6.编写测试类com.xiaoxie.test.Test及相应的测试方法
package com.xiaoxie.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.xiaoxie.dao.UserDao;
public
class Test {
public
static
void main(String[]
args) {
aspectJByXml();
}
private
static
void aspectJByXml() {
ApplicationContext
context =
new ClassPathXmlApplicationContext(
"beans.xml");
//从容器中获得缯强目标对象
UserDao
userDaoAdvice = (UserDao)
context.getBean(
"userDao");
System.
out.println(
userDaoAdvice);
System.
out.println(
"---------save--------");
userDaoAdvice.save();
System.
out.println(
"--------delete-------");
userDaoAdvice.delete();
}
}
7.运行main方法,控制台打印结果如下
com.xiaoxie.dao.impl.UserDaoImpl@79dc5318
---------save--------
前置通知~~~~~,目标对象:com.xiaoxie.dao.impl.UserDaoImpl@79dc5318,被增强处理的方法:save
开始环绕,在执行目标方法之前执行~~~~~
保存记录!
后置通知,一般用来释放资源操作
结束环绕,在执行目标方法之后执行~~~~~
后置返回通知~~~~,目标对象:com.xiaoxie.dao.impl.UserDaoImpl@79dc5318,被增强处理的方法:save
--------delete-------
前置通知~~~~~,目标对象:com.xiaoxie.dao.impl.UserDaoImpl@79dc5318,被增强处理的方法:delete
开始环绕,在执行目标方法之前执行~~~~~
删除记录!
后置通知,一般用来释放资源操作
结束环绕,在执行目标方法之后执行~~~~~
后置返回通知~~~~,目标对象:com.xiaoxie.dao.impl.UserDaoImpl@79dc5318,被增强处理的方法:delete
各类通知类型与目标方法的执行过程流程图示

二、基于注解开发AspectJ
基于注解开发相比于基于xml配置文件的开发要便捷许多,在开发过程中常常使用这种基于注解的开发方式
关于AspectJ相关注解
@Aspect 定义一个切面,注解在切面类上
@PointCut 定义切入点表达式,需要定义一个切入点方法,这个方法是一个返回值void且方法体为空的普通方法
@Before 定义前置通知
@AfterReturning 定义后置返回通知
@Around 定义环绕通知
@AfterThrowing 定义异常通知
@After 定义后置通知
实例
1.新增一个切面类 com.xiaoxie.aspect.MyAspectAnnotation
package com.xiaoxie.aspect;
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;
/*
* @Aspect:表示这个是一个切面类
* @Component:这个类在Spring中作为组件使用,相当于在Spring的
xml
配置文件中定义一个bean
*/
@Aspect
@Component
public
class MyAspectAnnotation {
//切入点方法,这里要指定切入点的表达式,并且这里需要有一个方法,这个方法返回值为void,方法体为空
@Pointcut(
"execution(* com.xiaoxie.dao.impl.*.*(..))")
private
void myPointCut() {}
/*前置通知,与切入点进行关联*/
@Before(
"myPointCut()")
public
void before(JoinPoint
jp) {
System.
out.print(
"前置通知~~~~~");
System.
out.println(
",目标对象:" +
jp.getTarget() +
",被增强处理的方法:" +
jp.getSignature().getName());
}
/*返回通知,与切入点进行关联*/
@AfterReturning(
"myPointCut()")
public
void afterReturning(JoinPoint
jp) {
System.
out.print(
"后置返回通知~~~~");
System.
out.println(
",目标对象:" +
jp.getTarget() +
",被增强处理的方法:" +
jp.getSignature().getName());
}
/*环绕通知,与切入点进行关联*/
@Around(
"myPointCut()")
public Object around(ProceedingJoinPoint
pjp)
throws Throwable {
//开始
System.
out.println(
"开始环绕,在执行目标方法之前执行~~~~~");
//执行目标方法
Object
proceed =
pjp.proceed();
//结束
System.
out.println(
"结束环绕,在执行目标方法之后执行~~~~~");
return
proceed;
}
/*异常通知,这里一定要记得throwing属性需要指定,并且指定与参数的名称一样*/
@AfterThrowing(value=
"myPointCut()",throwing=
"e")
public
void except(Throwable
e) {
System.
out.println(
"异常通知,程序执行异常" +
e.getMessage());
}
/*后置通知,与切入点进行关联*/
@After(
"myPointCut()")
public
void after() {
System.
out.println(
"后置通知,一般用来释放资源操作");
}
}
注意:1.这里使用了注解@Ascept,表示它是一个切面类;使用了@Component,表示是一个Spring的组件,相当于在Spring配置文件中定义切面Bean;
2.定义了切入点方法,使用注解@PointCut,这个注解声明在方法上,这个方法要求返回值为void,方法体为空;同时在这个切入点方法中指定了表达式,从这个表达式可以找到对应的目标方法
3.对于切面中的各通知方法进行注解,注解中需要与对应的切入点方法进行关联
2.定义目标接口及目标类 com.xiaoxie.dao.EmployeeDao、com.xiaoxie.dao.impl.EmployeeDaoImpl
目标接口
package com.xiaoxie.dao;
public
interface EmployeeDao {
void save();
void update();
void delete();
}
目标实现类
package com.xiaoxie.dao.impl;
import org.springframework.stereotype.Repository;
import com.xiaoxie.dao.EmployeeDao;
/*这个注解把下面这个类注解为目标对象(employeeDao)*/
@Repository(
"employeeDao")
public
class EmployeeDaoImpl
implements EmployeeDao{
@Override
public
void save() {
System.
out.println(
"保存记录!");
}
@Override
public
void update() {
System.
out.println(
"更新记录!");
}
@Override
public
void delete() {
System.
out.println(
"删除记录!");
}
}
注意:这里使用了注解@Repository,相当于在Spring的配置文件中对这个指定的目标类注入生成对象,同时指定id为employeeDao
3.新增Spring的xml配置文件
<?
xml
version=
"1.0"
encoding=
"UTF-8"
?>
xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance"
xmlns:context=
"http://www.springframework.org/schema/context"
xmlns:aop=
"http://www.springframework.org/schema/aop"
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-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd"
>
<!-- 指定需要扫描的包,使注解生效 -->
<
context:component-scan
base-package=
"com.xiaoxie.aspect"
/>
<
context:component-scan
base-package=
"com.xiaoxie.dao.impl"
/>
<!-- 注解对AspectJ的支持 -->
<
aop:aspectj-autoproxy
/>
</
beans
>
注意:这个配置文件中不需要对切面、bean类注入等进行配置,但是需要做两个操作:第一,指定Spring组件扫描的包;第二,声明注解对AspectJ的支持
这里同时要注意需要勾选上context和aop的命名空间
4.在测试类Test中新增测试方法
private
static
void aspectByAnnotation() {
ApplicationContext
context =
new ClassPathXmlApplicationContext(
"AnnoationBeans.xml");
//从容器中获得缯强目标对象
EmployeeDao
userDaoAdvice = (EmployeeDao)
context.getBean(
"employeeDao");
System.
out.println(
userDaoAdvice);
System.
out.println(
"---------save--------");
userDaoAdvice.save();
System.
out.println(
"--------delete-------");
userDaoAdvice.delete();
}
5.在main方法中调用上面的测试方法并运行后,控制台打印结果如下
com.xiaoxie.dao.impl.EmployeeDaoImpl@63dd899
---------save--------
开始环绕,在执行目标方法之前执行~~~~~
前置通知~~~~~,目标对象:com.xiaoxie.dao.impl.EmployeeDaoImpl@63dd899,被增强处理的方法:save
保存记录!
结束环绕,在执行目标方法之后执行~~~~~
后置通知,一般用来释放资源操作
后置返回通知~~~~,目标对象:com.xiaoxie.dao.impl.EmployeeDaoImpl@63dd899,被增强处理的方法:save
--------delete-------
开始环绕,在执行目标方法之前执行~~~~~
前置通知~~~~~,目标对象:com.xiaoxie.dao.impl.EmployeeDaoImpl@63dd899,被增强处理的方法:delete
删除记录!
结束环绕,在执行目标方法之后执行~~~~~
后置通知,一般用来释放资源操作
后置返回通知~~~~,目标对象:com.xiaoxie.dao.impl.EmployeeDaoImpl@63dd899,被增强处理的方法:delete