一,什么是AOP
AOP全称是Aspect-Oriented Programming,即面向切面编程。AOP采用横向抽取的机制,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,在将这些提取出来的代码应用到需要执行的地方。
二,AOP术语
- Aspect(切面):通常指封装着用于横向插入系统功能的类
- JointPoint(连接点):在程序执行过程中的某个阶段点。在Spring AOP中,连接点是指方法的调用
- Pointcut(切入点):是指切面与程序流程的交叉点,即那些需要处理的连接点
- Advice(通知/增强处理):AOP框架在特定的切入点执行的增强处理,可以理解为切面类中的方法
- Target Object(目标对象):是指所有被通知的对象
- Proxy(代理):将通知应用到目标对象之后,被动态创建的对象
- Weaving(织入):将切面代码插入到目标对象上,从而生成代理对象的过程
三,基于代理类的AOP实现
其实现是通过ProxyFactoryBean将切面类织入目标对象。
- 怎么定义切面类
切面类需要实现一些特定的接口,并在其中定义好通知。
可以实现的接口有如下:
- org.aopalliance.intercept.MethodInterceptor(环绕通知)
在目标方法执行前后实施增强,可以应用于日志、事务管理等功能 - org.springframework.aop.MethodBeforeAdvice(前置通知)
在目标方法执行前实施增强,可以应用于权限管理等功能 - org.springframework.aop.AfterReturningAdvice(后置通知)
在目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件等功能 - org.springframework.aop.ThrowsAdvice(异常通知)
在方法抛出异常后实施增强,可以应用于处理异常记录日志等功能 - org.springframework.aop.IntroductionInterceptor(引介通知)
在目标类中添加新的方法和属性,可以应用于修改老版本
下面是编写名为MyAspect的环绕通知的切面类的实现代码
- org.aopalliance.intercept.MethodInterceptor(环绕通知)
package com.itheima.factorybean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAspect implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
check_Permissions();
Object obj = mi.proceed();
log();
return obj;
}
public void check_Permissions() {
System.out.println("模拟检查权限。。。");
}
public void log() {
System.out.println("模拟记录日志。。。");
}
}
- ProxyFactoryBean实现织入的过程
织入是在xml文件中编写相关代码实现的。
下面的xml文件中定义了UserDaoImpl的Bean,MyAspect切面类的Bean和一个ProxyFactoryBean的Bean。ProxyFactoryBean需要设置proxyInterfaces属性来指定目标对象实现的接口,如果有多个接口,则用<list><value></value>…</list>的格式进行赋值。target属性用来指定目标对象,interceptorNames指定需要织入的切面类,属性proxyTargetClass属性指定是否对类代理而不是接口,设置为true时,使用CGLIB代理。
<?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://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl"/>
<bean id="myAspect" class="com.itheima.factorybean.MyAspect"/>
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.itheima.jdk.UserDao"/>
<property name="target" ref="userDao"/>
<property name="interceptorNames" value="myAspect"/>
<property name="proxyTargetClass" value="true"/>
</bean>
</beans>
以下是其他相关代码
package com.itheima.jdk;
public interface UserDao {
public void addUser();
public void deleteUser();
}
package com.itheima.jdk;
import java.io.IOException;
public class UserDaoImpl implements UserDao{
public void addUser(){
System.out.println("添加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
}
package com.itheima.jdk;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class ProxyFactoryBeanTest {
public static void main(String[] args) {
String xmlPath = "com/itheima/factorybean/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
userDao.addUser();
}
}
四,AspectJ实现AOP
基于XML的声明式AspectJ
在beans标签下包含的aop:config标签中进行配置,aop:config标签可以包含子元素或者属性,其子元素包含aop:pointcut,aop:advisor,aop:aspect。aop:aspect标签下可以配置切入点和通知。
- 配置切面
aop:aspect扁鹊用来配置切面,其id属性用于定义该切面的唯一标识,ref用于引用普通的Spring Bean。 - 配置切入点
配置切入点使用aop:pointcut标签。其id属性用来指定切入面的唯一标识。expression属性用来指定切入点关联的切入点表达式。
表达式的基本格式如下
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
上述格式说明:
- modifiers-pattern:表示定义的目标方法的访问修饰符,如public、private等
- ret-type-pattern:表示定义的目标方法的返回值类型,如void、String等
- declaring-type-pattern:表示定义的目标方法的类路径,如com.itheima.jdk.UserDaoImpl
- name-pattern:表示具体需要被代理的目标方法,如add()方法
- param-pattern:表示需要被代理的目标方法包含的参数
- throws-pattern:表示需要被代理的目标方法抛出的异常类型
其中带有?的是表示可配置项,其他为不可配置项。
- 配置通知
配置通知的常见标签有前置通知aop:before,后置通知aop:after-returning,环绕通知aop:around,异常通知aop:after-throwing,最终通知aop:after。
这些标签不支持使用子元素,在使用是可以指定一些属性。
- pointcut:只当一个切入点表达式
- pointcut-ref:指定一个已经存在的切入点名称
- method:该属性指定一个方法名,指定将切面Bean中的该方法转换为增强处理
- throwing:该属性只对after-throwing元素有效,用于指定一个形参名,异常通知方法可以通过该形参访问目标方法所抛出的异常
- returning:该属性支队after-returning元素有效,用于指定一个形参名,后置通知方法可以通过该形参访问目标方法的返回值
演示代码如下
package com.itheima.jdk;
public interface UserDao {
public void addUser();
public void deleteUser();
}
package com.itheima.jdk;
public class UserDaoImpl implements UserDao{
public void addUser(){
System.out.println("添加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
}
package com.itheima.aspectj.xml;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
public class MysAspect {
//前置通知
public void myBefore(JoinPoint joinpoint){
System.out.print("前置通知:模拟执行权限检查。。。,");
System.out.print("目标类是:" + joinpoint.getTarget());
System.out.println(",被植入增强的处理的目标方法为:" +
joinpoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志。。。,");
System.out.println("被植入增强处理的目标方法为:" +
joinPoint.getSignature().getName());
}
//环绕通知
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("最终通知:模拟方法结束后的释放资源。。。");
}
}
<?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">
<bean id="userDao" class="com.itheima.jdk.UserDaoImpl"/>
<bean id="mysAspect" class="com.itheima.aspectj.xml.MysAspect"/>
<aop:config>
<aop:aspect ref="mysAspect">
<aop:pointcut id="myPointCut" expression="execution(* com.itheima.jdk.*.*(..))"/>
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut"/>
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
package com.itheima.aspectj.xml;
import com.itheima.jdk.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestXmlAspectj {
public static void main(String[] args) {
String xmlPath = "com/itheima/aspectj/xml/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.addUser();
}
}
基于注解的声明式AspectJ
用注解来代替xml配置
* @Aspect:用于定义一个切面
* @Pointcut:用于定义切入点表达式。在使用时还需要定义一个包含名字和任意参数的方法签名来表示切入点名称。实际上,这个方法签名就是一个返回值为void,且方法体为空的普通的方法。
* @Before:用于定义前置通知,相当于BeforeAdivice。在使用时,需要指定一个value值,指定一个切入点表达式
* @AfterReturning:用于定义后置通知,相当于AfterReturningAdivice。使用时指定pointcut或value和returning属性,pointcut或value这两个属性用于指定切入表达式,returning属性用于表示Advice方法中可定义与此同名的形参,该形参用于访问目标方法的返回值
* @Around:用于定义环绕通知,相当于MethodInterceptor,使用时设置value属性,指定切入点
* AfterThrowing:用于定义异常通知来处理,相当于ThrowAdvice。使用时可指定ponitcut或value和throwing属性,pointcut或value用于指定切入点表达式,throwing用于指定一个形参名来表示Advice方法中可定义与此同名的形参
* @After:用于定义最终通知,不管是否异常,该通知都会执行。使用时可指定value值,用于指定切入点
* DeclareParents:用于定义引介通知,相当于IntroductionInterceptor
下面时演示代码
package com.itheima.jdk;
public interface UserDao {
public void addUser();
public void deleteUser();
}
package com.itheima.jdk;
import org.springframework.stereotype.Repository;
@Repository("userDao")
public class UserDaoImpl implements UserDao{
public void addUser(){
System.out.println("添加用户");
}
public void deleteUser() {
System.out.println("删除用户");
}
}
package com.itheima.aspectj.annotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MysAspect {
@Pointcut("execution(* com.itheima.jdk.*.*(..))")
private void myPointCut(){}
//前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinpoint){
System.out.print("前置通知:模拟执行权限检查。。。,");
System.out.print("目标类是:" + joinpoint.getTarget());
System.out.println(",被植入增强的处理的目标方法为:" +
joinpoint.getSignature().getName());
}
//后置通知
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知:模拟记录日志。。。,");
System.out.println("被植入增强处理的目标方法为:" +
joinPoint.getSignature().getName());
}
//环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//开始
System.out.println("环绕开始:执行目标方法之前,模拟开启事务。。。");
//执行当前目标方法
Object obj = proceedingJoinPoint.proceed();
//结束
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务。。。");
return obj;
}
//异常通知
@AfterThrowing(value = "myPointCut()", throwing = "e")
public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
System.out.println("异常通知:" + "出错了" + e.getMessage());
}
//最终通知
@After("myPointCut()")
public void myAfter() {
System.out.println("最终通知:模拟方法结束后的释放资源。。。");
}
}
<?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: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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.itheima.jdk"/>
<context:component-scan base-package="com.itheima.aspectj.annotation"/>
<aop:aspectj-autoproxy/>
</beans>
package com.itheima.aspectj.annotation;
import com.itheima.jdk.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestAnnotation {
public static void main(String[] args) {
String xmlPath = "com/itheima/aspectj/annotation/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(xmlPath);
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.addUser();
}
}