写在前面:aop编程主要用到的是设计模式是代理模式,关于代理模式,请看前一篇文章《浅析代理模式》https://blog.youkuaiyun.com/ah3629/article/details/82352206
1、概述
AOP(aspect object programming) 面向切面编程,功能是让关注点代码与业务代码分离。
关注点:重复的代码就叫关注点。
切面:关注点代码形成的类,就叫切面(类)。
面向切面编程,指的是对很多功能都有的重复代码进行抽取,再在运行的时候向业务方法上动态植入“切面类代码”。
切入点:执行的目标对象方法。以动态植入切面代码。可以通过切入点表达式,指定拦截哪些类的哪些方法,给指定的类在运行的时候植入切面类代码。
2、手动实现AOP编程
实现aop编程主要是就要将关注点代码和业务代码分离,在运行业务代码时,动态的植入关注点代码。这里以保存(save)方法为例,将save方法执行过程中的开启事务处理异常,提交事务这一部分代码抽取出来,在运行save时再将这部分代码动态植入。
分离关注点代码与业务代码的方式有三种: 过程式/对象式/代理模式分离,这里只说代理模式。
分离关注点代码与业务代码的好处:
1)关注点代码写一次即可;
2)开发者只需关注核心业务;
3)运行时期,执行核心业务代码的时候动态植入关注点代码。
代码实现为:接口类
public interface IUserDao {
void save();
}
接口实现类:
@Component
public class UserDao implements IUserDao{
@Override
public void save() {
System.out.println("核心业务:保存成功");
}
}
切面类:
@Component
public class Aop {
public static void begin(){
System.out.println("开启事务/异常");
}
public static void commite(){
System.out.println("提交事务/关闭");
}
}
代理工厂类:
public class ProxyFactory {
//维护一个目标对象
private static Object target;
private static Aop aop;
public static Object getProxyInstance(Object target_,Aop aop_){
target = target_;
aop = aop_;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),//目标对象的类加载器
target.getClass().getInterfaces(), //目标对象类实现的接口
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
aop.begin();//执行重复代码
Object returnValue = method.invoke(target, args);
aop.commite(); //执行重复代码
return returnValue;
}
});
}
}
spring核心配置文件:
<?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:p="http://www.springframework.org/schema/p"
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" >
<!-- 使用注解步骤:
1)引入context名称空间
2)开启注解扫描
3) 使用注解,通过注解方式,把对象加入ioc容器-->
<context:component-scan base-package="d_myAop1"></context:component-scan>
<!-- 工厂中创建的是userDao的代理类的实例,而不是ProxyFactory的实例 -->
<bean id="userDaoProxy" class="d_myAop1.ProxyFactory" factory-method="getProxyInstance">
<constructor-arg name="target_" ref="userDao"></constructor-arg>
<constructor-arg name="aop_" ref="aop"></constructor-arg>
</bean>
</beans>
测试类:
public class App {
private ApplicationContext ac = new ClassPathXmlApplicationContext("/d_myAop1/bean.xml");
@Test
public void testApp(){
IUserDao userDao = (IUserDao) ac.getBean("userDaoProxy");
System.out.println(userDao.getClass());
userDao.save();
}
}
运行结果:
根据运行结果可以看出,已成功实现aop编程。
3、注解方式实现aop编程
1)实现步骤
先引入aop相关jar文件:
spring-aop-3.2.5.RELEASE.jar
aopalliance.jar
aspectjweaver.jar
aspectjrt.jar
2)spring核心配置文件bean.xml中引入aop的名称空间
3)开启aop注解
4)使用注解
@Aspect 指定一个类为切面类
@Pointcut("execution(* e_aop_anno.*.*(..))") 指定切入点表达式
@Before("pointCut_()") 前置通知:在目标方法之前执行
@After("pointCut_()") 后置通知:在目标方法之后执行(始终执行)
代码实现
还是以常用的保存(save)方法为例
接口:IUserDao代码同上
实现类:UserDao代码同上
aop:此处pointCut方法只是为了写公共的切入点表达式,简化书写,并无实际意义
@Component
@Aspect //指定当前类为切面类
public class Aop {
//指定切入掉表达式,拦截哪些方法,即为哪些类生成 代理对象
@Pointcut("execution(* e_aop_anno.*.*(..))")
public void pointCut_(){
}
//前置通知:在执行目标方法之前执行
@Before("pointCut_()")
public static void begin(){
System.out.println("注解方式:开启事务");
}
//后置通知:在执行目标方法之后执行,无论是否有异常,始终执行
@After("pointCut_()")
public static void commite(){
System.out.println("注解方式:提交事务");
}
}
新增一个OrderDao(没有实现接口,用于演示动态代理和Cglib代理)
@Repository
public class OrderDao {
public void save() {
System.out.println("OrderDao:保存成功");
}
}
spring核心配置文件bean.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
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="e_aop_anno"></context:component-scan>
<!-- 开启aop注解方式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
测试类:
public class App {
private ApplicationContext ac = new ClassPathXmlApplicationContext("/e_aop_anno/bean.xml");
//目标对象实现接口,spring使用动态(JDK)代理
@Test
public void testApp(){
IUserDao userDao = (IUserDao) ac.getBean("userDao");
System.out.println(userDao.getClass());
userDao.save();
}
//目标对象没有实现接口,spring使用cglib代理
@Test
public void testCglib(){
OrderDao orderDao = (OrderDao) ac.getBean("orderDao");
System.out.println(orderDao.getClass());
orderDao.save();
}
}
执行testApp()方法,运行结果
根据运行结果可以看出,UserDao实现了接口,springaop采用的是动态代理
执行testCglib()方法,运行结果
根据运行结果可以看出,OrderDao没有实现接口,springaop采用的是cglib代理
除了上述代码中使用的前置通知,后置通知,还有另外还有返回后通知,异常通知,环绕通知
@AfterReturning("pointCut_()") 返回后通知:执行目标方法结束前执行(与后置通知的区别是,当目标方法出现异常不执行,而后置通知@After则始终执行)
@AfterThrowing("pointCut_()") 异常通知:执行目标方法出现异常时执行
@Around("pointCut_()") 环绕通知:环绕目标方法执行(功能与前置后置放在一起使用相同)
代码实现:
@Component
@Aspect //指定当前类为切面类
public class Aop {
//指定切入掉表达式,拦截哪些方法,即为哪些类生成 代理对象
@Pointcut("execution(* e_aop_anno.*.*(..))")
public void pointCut_(){
}
//前置通知:在执行目标方法之前执行
@Before("pointCut_()")
public static void begin(){
System.out.println("注解方式:开启事务");
}
//后置通知:在执行目标方法之后执行,无论是否有异常,始终执行
@After("pointCut_()")
public static void commite(){
System.out.println("注解方式:提交事务");
}
//返回后通知:调用目标方法结束后执行,出现异常不执行
@AfterReturning("pointCut_()")
public static void afterReturning(){
System.out.println("注解方式:返回后通知");
}
//异常通知:当目标方法出现异常时,执行此关注点代码
@AfterThrowing("pointCut_()")
public static void afterThrowing(){
System.out.println("注解方式:异常通知");
}
//环绕通知:环绕目标方法执行
@Around("pointCut_()")
public static void arround(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("环绕前……");
pjp.proceed(); //执行目标方法
System.out.println("环绕后……");
}
}
运行结果:
关于xml配置文件方式实现aop编程以及切入点表达式的解析由于篇幅问题,将放在下一篇文章。:)
源码下载:https://download.youkuaiyun.com/download/ah3629/10650172