文章内容部分来自于
https://gitee.com/zhongfucheng/Java3y#spring%E5%AE%B6%E6%97%8F
http://c.biancheng.net/view/4241.html
本人在学习Spring玩转全家桶时,发现自己对AOP以及切片编程等基础Spring知识有些模糊不清,故写翻看网上各种文章,网上文章有的抽象有的例子鲜明但是喧宾夺主,故还是我自己写个文章梳理一下我自己学到的内容
面向切面编程(AOP)和面向对象编程(OOP)类似,也是一种编程模式,全称是“Aspect Oriented Programming”,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率。
目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ
举例说明
在包下 com.mengma.dao 创建CustomerDao 、CustomerDaoImpl的两个类文件
package com.mengma.dao;
public interface CustomerDao {
public void add(); // 添加
public void update(); // 修改
public void delete(); // 删除
public void find(); // 查询
}
package com.mengma.dao;
public class CustomerDaoImpl implements CustomerDao {
@Override
public void add() {
System.out.println("添加客户...");
}
@Override
public void update() {
System.out.println("修改客户...");
}
@Override
public void delete() {
System.out.println("删除客户...");
}
@Override
public void find() {
System.out.println("修改客户...");
}
}
在包 com.mengma.jdk 下,分别创建MyAspect、MyBeanFactory 、JDKProxyTest三个类
package com.mengma.jdk;
public class MyAspect {
public void myBefore() {
System.out.println("方法执行之前");
}
public void myAfter() {
System.out.println("方法执行之后");
}
}
package com.mengma.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.mengma.dao.CustomerDao;
import com.mengma.dao.CustomerDaoImpl;
public class MyBeanFactory {
public static CustomerDao getBean() {
// 准备目标类
final CustomerDao customerDao = new CustomerDaoImpl();
// 创建切面类实例
final MyAspect myAspect = new MyAspect();
// 使用代理类,进行增强
return (CustomerDao) Proxy.newProxyInstance(
MyBeanFactory.class.getClassLoader(),
new Class[] { CustomerDao.class }, new InvocationHandler() {
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
myAspect.myBefore(); // 前增强
Object obj = method.invoke(customerDao, args);
myAspect.myAfter(); // 后增强
return obj;
}
});
}
}
package com.mengma.jdk;
import org.junit.Test;
import com.mengma.dao.CustomerDao;
public class JDKProxyTest {
@Test
public void test() {
// 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
CustomerDao customerDao = MyBeanFactory.getBean();
// 执行方法
customerDao.add();
customerDao.update();
customerDao.delete();
customerDao.find();
}
}
程序要想运行同样需要上篇文章列出的5个spring包
结果如下
其中 Proxy 的 newProxyInstance() 方法的第一个参数是当前类的类加载器,第二参数是所创建实例的实现类的接口,第三个参数就是需要增强的方法
上述例子是,JDK 动态代理,是通过 JDK 中的 java.lang.reflect.Proxy 类实现的
Spring AOP主要做的事情就是:「把重复的代码抽取,在运行的时候往业务方法上动态植入“切面类代码”」
通过动态代理,我们可以把对象「增强」,将非业务代码写在要「增强」的逻辑上
完了以后,我们就可以通过「增强后的对象」去调用方法,最终屏蔽掉「重复代码」
AOP编程可以简单理解成:在执行某些代码前,执行另外的代码
其实Spring AOP的底层原理就是动态代理!Spring AOP使用纯Java实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标类织入增强代码。在Spring中可以无缝地将Spring AOP、IoC和AspectJ整合在一起。
在Java中动态代理有两种方式:
JDK动态代理
上述例子就是jdk的动态代理,下面再举例子说明CGLib动态代理
在 com.mengma.dao 包下创建类 GoodsDao
package com.mengma.dao;
public class GoodsDao {
public void add() {
System.out.println("添加商品...");
}
public void update() {
System.out.println("修改商品...");
}
public void delete() {
System.out.println("删除商品...");
}
public void find() {
System.out.println("修改商品...");
}
}
在 com.mengma.cglib 包下创建类 MyBeanFactory和CGLIBProxyTest
package com.mengma.cglib;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import com.mengma.dao.GoodsDao;
import com.mengma.jdk.MyAspect;
public class MyBeanFactory {
public static GoodsDao getBean() {
// 准备目标类
final GoodsDao goodsDao = new GoodsDao();
// 创建切面类实例
final MyAspect myAspect = new MyAspect();
// 生成代理类,CGLIB在运行时,生成指定对象的子类,增强
Enhancer enhancer = new Enhancer();
// 确定需要增强的类
enhancer.setSuperclass(goodsDao.getClass());
// 添加回调函数
enhancer.setCallback(new MethodInterceptor() {
// intercept 相当于 jdk invoke,前三个参数与 jdk invoke—致
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
myAspect.myBefore(); // 前增强
Object obj = method.invoke(goodsDao, args); // 目标方法执行
myAspect.myAfter(); // 后增强
return obj;
}
});
// 创建代理类
GoodsDao goodsDaoProxy = (GoodsDao) enhancer.create();
return goodsDaoProxy;
}
}
package com.mengma.cglib;
import org.junit.Test;
import com.mengma.dao.GoodsDao;
public class CGLIBProxyTest {
@Test
public void test() {
// 从工厂获得指定的内容(相当于spring获得,但此内容时代理对象)
GoodsDao goodsDao = MyBeanFactory.getBean();
// 执行方法
goodsDao.add();
goodsDao.update();
goodsDao.delete();
goodsDao.find();
}
}
上述代码中,应用了 CGLIB 的核心类 Enhancer
intercept() 方法相当于 JDK 动态代理方式中的 invoke() 方法,该方法会在目标方法执行的前后,对切面类中的方法进行增强;之后调用 Enhancer 类的 create() 方法创建代理类,最后将代理类返回
动态代理也有个约束:目标对象一定是要有接口的,没有接口就不能实现动态代理……----->因此出现了cglib代理
cglib代理也叫子类代理,从内存中构建出一个子类来扩展目标对象的功能
Spring AOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGLib代理
如果是单例的我们最好使用CGLib代理,如果是多例的我们最好使用JDK代理
JDK在创建代理对象时的性能要高于CGLib代理,而生成代理对象的运行性能却比CGLib的低。
如果是单例的代理,推荐使用CGLib
看到这里我们就应该知道什么是Spring AOP(面向切面编程)了:将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能
现在应用更多更流行的是另一种方式@AspectJ注解驱动的切面
连接点(Join point):
能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~
切点(Poincut):
具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。
增强/通知(Advice):
表示添加到切点的一段逻辑代码,并定位连接点的方位信息。
简单来说就定义了是干什么的,具体是在哪干
Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!
org.springframework.aop.MethodBeforeAdvice(前置通知) 在方法之前自动执行的通知称为前置通知,可以应用于权限管理等功能。
org.springframework.aop.AfterReturningAdvice(后置通知) 在方法之后自动执行的通知称为后置通知,可以应用于关闭流、上传文件、删除临时文件等功能。
org.aopalliance.intercept.MethodInterceptor(环绕通知) 在方法前后自动执行的通知称为环绕通知,可以应用于日志、事务管理等功能。
org.springframework.aop.ThrowsAdvice(异常通知) 在方法抛出异常时自动执行的通知称为异常通知,可以应用于处理异常记录日志等功能。
org.springframework.aop.IntroductionInterceptor(引介通知) 在目标类中添加一些新的方法和属性,可以应用于修改旧版本程序(增强类)。
织入(Weaving):
将增强/通知添加到目标类的具体连接点上的过程。
引入/引介(Introduction):
引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!
切面(Aspect):
切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义
使用 AspectJ 开发 AOP 通常有两种方式:
基于 XML 的声明式
基于 Annotation 的声明式
xml由于随着bean增多,会特别臃肿,所以主流更多采用注解
所以下面是注解的样例
package com.mengma.dao;
public interface CustomerDao {
public void add(); // 添加
public void update(); // 修改
public void delete(); // 删除
public void find(); // 查询
}
package com.mengma.dao;
import org.springframework.stereotype.Repository;
@Repository("customerDao")
public class CustomerDaoImpl implements CustomerDao {
@Override
public void add() {
System.out.println("添加客户...");
}
@Override
public void update() {
System.out.println("修改客户...");
}
@Override
public void delete() {
System.out.println("删除客户...");
}
@Override
public void find() {
System.out.println("修改客户...");
}
}
package com.aspectJ.annotation;
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;
import org.springframework.stereotype.Component;
//切面类
@Aspect
@Component
public class MyAspect {
// 用于取代:
// expression="execution(*com.mengma.dao..*.*(..))" id="myPointCut"/>
// 要求:方法必须是private,没有值,名称自定义,没有参数
@Pointcut("execution(* com.mengma.dao..*.*(..))")
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(value = "myPointCut()")
public void myAfterReturning(JoinPoint joinPoint) {
System.out.print("后置通知,方法名称:" + 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("最终通知");
}
}
package com.aspectJ.annotation;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.mengma.dao.CustomerDao;
public class AnnotationTest {
@Test
public void test() {
String xmlPath = "com/aspectJ/annotation/applicationContext.xml";
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(
xmlPath);
System.out.println(applicationContext);
// 从spring容器获取实例
CustomerDao customerDao = (CustomerDao) applicationContext
.getBean("customerDao");
// 执行方法
customerDao.add();
}
}
<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-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.mengma.dao"/>
<context:component-scan base-package="com.aspectJ.annotation"/>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
上述代码中, @Aspect 注解用于声明这是一个切面类,该类作为组件使用,所以要添加 @Component 注解才能生效。 @Poincut 注解用于配置切入点,取代 XML 文件中配置切入点的代码。
在每个通知相应的方法上都添加了注解声明,并且将切入点方法名“myPointCut”作为参数传递给要执行的方法,如需其他参数(如异常通知的异常参数),可以根据代码提示传递相应的属性值。
导入了 AOP 命名空间及其配套的约束,使切面类中的 @Aspect注解能够正常工作;
添加了扫描包,使注解生效;切面开启自动代理。