面向切面编程(Spring AOP)
为了将那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,Spring提供了面向切面的编程方式,也称Spring AOP,这有效的减少了系统间的重复代码,达到了模块间的松耦合目的。
AOP,Aspect Oriented Programing,即面向切面编程。AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码,主要体现在事务处理、日志管理、权限控制、异常处理等方面。
目前流行的AOP框架有两个,分别是Spring AOP和AspectJ。AspectJ是一个基于Java语言的AOP框架。
AOP术语
- Joinpoint(连接点):是指那些被拦截到的点,在Spring中,可以被动态代理拦截目标类的方法。
- Pointcut(切入点):是指要对哪些Joinpoint进行拦截,即被拦截的拦截点。
- Advice(通知):是指拦截到Joinpoint之后要做的事情,即对切入点增强的内容。
- Target(目标):是指代理的目标对象。
- Weaving(织入):是指把增强代码应用到目标上,生成代理对象的过程。
- Proxy(代理):是指生成的代理对象。
- Aspect(切面):是切入点和通知的结合。
AspectJ开发
基于XML的声明式AspectJ
导入AOP的的jar包
- spring-aop-3.2.0.RELEASE.jar:是Spring为AOP提供的实现。
- com.springsource.org.aopalliance-1.0.0.jar:是AOP联盟提供的规范。
导入AsprctJ相关的jar包
- spring-aspects-3.2.0.RELEASE.jar:Spring为AspectJ提供的实现。
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar:AspectJ提供的规范。
建立UserDao.java
package cn.itcast.dao;
public interface UserDao {
void save();
void update();
void delete();
void find();
}
UserDaoImpl.java
package cn.itcast.dao;
public class UserDaoImpl implements UserDao{
@Override
public void save() {
System.out.println("save添加用户");
}
@Override
public void update() {
System.out.println("update修改用户");
}
@Override
public void delete() {
System.out.println("delete删除用户");
}
@Override
public void find() {
System.out.println("find查询用户");
}
}
MyAspect.java
package cn.itcast.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
/*
* 切面类,在此编写通知
* 可以在XML配置文件中确定通知类型
*/
public class MyAspect {
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.println("前置通知,目标:");
System.out.println(joinPoint.getTarget()+",方法名称:");
System.out.println(joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("后置通知,方法名称:"+joinPoint.getSignature().getName());
}
//环绕通知
//ProceedingJoinPoint是JoinPoint子接口,表示可以执行目标方法
//1.必须返回Object类型值
//2.必须接收一个参数,类型为ProceedingJoinPoint
//3.必须throws Throwable
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
//开始
System.out.println("环绕开始");
//执行当前目标方法
Object obj=proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知,出错了"+e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知");
}
}
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/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.xsd">
<!-- 1.目标类 -->
<bean id="userDao" class="cn.itcast.dao.UserDaoImpl"></bean>
<!-- 2.切面 -->
<bean id="myAspect" class="cn.itcast.aspectj.xml.MyAspect"></bean>
<!-- 3.aop编程 -->
<aop:config>
<aop:aspect ref="myAspect">
<!-- 3.1 配置切入点,通知最后增强哪些方法 -->
<aop:pointcut expression="execution(* cn.itcast.dao..*.*(..))" id="myPointCut"/>
<!-- 3.2关联通知Advice和切入点pointCut -->
<!-- #1前置通知 -->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- #2后置通知,在方法返回之后执行,就可以获得返回值
returning属性:用于设置后置通知的第二个参数的名称,类型是Object
-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal"/>
<!-- #3环绕通知 -->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!-- #4抛出通知:用于处理程序发生异常,就可以接受当前方法产生的异常
注意:如果程序没有异常,将不会执行增强
throwing属性:用于设置通知第二个参数的名称,类型Throwable
-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!-- #5最终通知:无论程序发生任何事情,都将执行 -->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
AspectJ表达式
execution() 用于描述方法
语法:execution(修饰符 返回值 包.类.方法名(参数) throws异常)
- 修饰符:一般省略。
- public:公共方法
- *:任意
- 返回值:不能省略
- void:无返回值
- String:返回值为字符串
- *:任意(一般用这个)
- 包:可以省略
- com.eaglezsx.crm:固定包
- com.eaglezsx.crm.*.service:crm包下面的任意子包,但要包含service。比如com.eaglezsx.crm.staff.service
- com.eaglezsx.crm..:crm下面的任意子包(包括孙包)
- com.eaglezsx.crm.*.service.. :crm包下面任意子包,固定目录service,service目录任意包
- 类:可以省略
- UserServiceImplement:指定类
- *Impl:以Impl结尾的类
- User*:以User开头的类
- *:任意类
- 方法名:不能省略
- addUser:固定方法
- add*:以add开头的方法
- *Do:以Do结尾的方法
- *:任意方法
- (参数)
- ():无参
- (int):一个整型参数
- (int,int):两个整型参数
- (..):任意参数
- throws:可省略,一般不写。
* cn.itcast.dao..*.*(..)
意思是,返回值任意,cn.itcast.dao下的任意包,下的任意类,下的任意方法,方法的参数任意
TestXML.java
package cn.itcast.aspectj.xml;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.itcast.dao.UserDao;
public class TestXML {
@Test
public void demo01(){
String xmlPath="applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
//1.从Spring容器获得内容
UserDao userDao=(UserDao)applicationContext.getBean("userDao");
//2.执行方法
userDao.save();
}
}
运行结果
前置通知,目标:
cn.itcast.dao.UserDaoImpl@110fcd9,方法名称:
save
环绕开始
save添加用户
后置通知,方法名称:save
环绕结束
最终通知
基于Annotation的声明式AspectJ
- @AspectJ:用于定义一个切面
- @Before:用于定义前置通知,相当于BeforeAdvice
- @AfterReturning:用于定义后置通知,相当于AfterReturningAdvice
- @Around:用于定义环绕通知,相当于MethodInterceptor
- @AfterThrowing:用于定义抛出通知,相当于ThrowAdvice
- @After:用于定义最终final通知,不管是否异常,该通知都会执行
@Aspect
@Component
public class MyAspect {
/*
用于取代<aop:pointcut expression="execution(* cn.itcast.dao..*.*(..))" id="myPointCut"/>
要求:方法必须是private没有值 名称自定义,没有参数
*/
@Pointcut("execution(* cn.itcast.dao..*.*(..))")
private void myPointCut(){}
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
...
}
@AfterReturning(value="myPointCut()")
public void myAfterReturning(JoinPoint joinPoint){
...
}
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
...
}
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
...
}
@After("myPointCut()")
public void myAfter(){
...
}
}
@Repository("userDao")
public class UserDaoImpl implements UserDao{
...
}
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/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描包:使注解生效 -->
<context:component-scan base-package="cn.itcast"></context:component-scan>
<!-- 使切面开启自动代理 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
TestAnnotation.java
public class TestAnnotation {
@Test
public void demo01(){
String xmlPath="applicationContext.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlPath);
//1.从Spring容器获得内容
UserDao userDao=(UserDao)applicationContext.getBean("userDao");
//2.执行方法
userDao.save();
}
}