文章目录
一、简介
1、概念
AOP(Aspect Oriented Programming,面向切面编程):通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
2、特点
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)。
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类织入增强代码。
3、AOP术语
-
Joinpoint(连接点):
连接点是指那些能被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
-
Pointcut(切入点):
切入点是指真正被拦截的点(Spring中拦截到的方法)。 -
Advice(通知/增强):
通知是指拦截到Joinpoint之后所要做的事情.
通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能) -
Introduction(引介):
引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field. -
Target(目标对象):
代理的目标对象,即被增强的对象。
-
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程,即增强对象的过程。
spring采用动态代理织入,而Aspect/采用编译期织入和类装载期织入。 -
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类。
-
Aspect(切面):
是切入点和通知(引介)的结合。
二、AOP底层实现原理
1、JDK动态代理
JDK动态代理只能对实现接口的类来实现动态代理。
//*****1 接口类
public interface UserDao {
public void find();
public void save();
}
//*****2 接口实现类
public class UserDaoImpl implements UserDao{
public void find() {
System.out.println("查找用户 ...");
}
public void save() {
System.out.println("保存用户 ...");
}
}
//*****3 JDK动态代理类
public class MyJdkProxy implements InvocationHandler {
private UserDao userDao;
public MyJdkProxy(UserDao userDao) {
this.userDao = userDao;
}
public Object createProxy() {
Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
return proxy;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("save".equals(method.getName())) {
System.out.println("权限校验 ....");
return method.invoke(userDao, args);
}
return method.invoke(userDao, args);
}
}
//*****4 测试
public class SpringDemo {
@Test
public void demo1() {
UserDao userDao = new UserDaoImpl();
UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy();
proxy.find();
proxy.save();
}
}
2、使用CGLIB生成代理
对于不使用接口的业务类,无法使用JDK动态代理
CGlib采用非常底层字节码技术,可以为一个类创建子类,解决无接口代理问题
//*****1 切面增强实现类
public class ProductDao {
public void find() {
System.out.println("查找商品 ...");
}
public void save() {
System.out.println("保存商品 ...");
}
}
//*****2 Cglib动态代理类
public class MyCglibProxy implements MethodInterceptor {
private ProductDao productDao;
public MyCglibProxy(ProductDao productDao) {
this.productDao = productDao;
}
public Object createProxy() {
// 1. 创建核心类
Enhancer enhancer = new Enhancer();
// 2. 设置父类
enhancer.setSuperclass(productDao.getClass());
// 3. 设置回调
enhancer.setCallback(this);
// 4. 生成代理
Object proxy = enhancer.create();
return proxy;
}
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
if ("save".equals(method.getName())) {
System.out.println("权限校验 ...");
return methodProxy.invokeSuper(proxy, args);
}
// 原封不动调用原来的代码
return methodProxy.invokeSuper(proxy, args);
}
}
//*****3 测试
public class SpringDemo {
@Test
public void demo() {
ProductDao productDao = new ProductDao();
ProductDao proxy = (ProductDao) new MyCglibProxy(productDao).createProxy();
proxy.find();
proxy.save();
}
}
3、代理总结
-
Spring在运行期,生成动态代理对象,不需要特殊的编译器
-
Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术为目标Bean执行横向织入
- 若目标对象实现了若干接口,spring使用JDK的 java.lang.reflect.Proxy 类代理。
- 若目标对象没有实现任何接口,spring使用 CGLIB 库生成目标对象的子类。
-
程序中应优先对接口创建代理,便于程序解耦维护
-
标记为final的方法,不能被代理,因为无法进行覆盖
- JDK动态代理,是针对接口生成子类,接口中方法不能使用 final 修饰
- CGLib 是针对目标类生产子类,因此类或方法不能使 fina l的
-
Spring只支持方法连接点,不提供属性连接点
三、AOP 增强类型与切面
- AOP联盟为通知 Advice 定义了 org.aopalliance.aop.Interface.Advice
1、5类增强
Spring按照通知Advice在目标类方法的连接点位置,可以分为5类。
- 前置通知
在目标方法执行前实施增强 org.springframework.aop.MethodBeforeAdvice
- 后置通知
在目标方法执行后实施增强 org.springframework.aop.AfterReturningAdvice
-
环绕通知
在目标方法执行前后实施增强 org.aopalliance.intercept.MethodInterceptor
-
异常抛出通知
在方法抛出异常后实施增强 org.springframework.aop.ThrowsAdvice
-
引介通知
在目标类中添加一些新的方法和属性 org.springframework.aop.IntroductionInterceptor
2、切面类型
-
Advisor
代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截.
-
PointcutAdvisor
代表具有切点的切面,可以指定拦截目标类哪些方法.
-
IntroductionAdvisor
代表引介切面,针对引介通知而使用切面.
四、切面编程案例
1、Advisor切面案例(一般切面)
-
ProxyFactoryBean 常用可配置属性
- target:代理的目标对象
- proxyInterfaces:代理要实现的接口
- proxyTargetClass:是否对类代理而不是接口,设置为true时,使用CGLib代理使用
- interceptorNames:需要织入目标的Advice
- singleton:返回代理是否为单实例,默认为单例
- optimize:当设置为true时,强制使用CGLib来产生代理
-
如果多个接口可以使用以下格式赋值
<list> <value></value> ... </list>
//*****1 接口类
public interface StudentDao {
public void find();
public void save();
}
//*****2 接口实现类
public class StudentDaoImpl implements StudentDao {
public void find() {
System.out.println("查找学生 ...");
}
public void save() {
System.out.println("保存学生 ...");
}
}
//*****3 前置增强通知
public class MyBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("AOP前置增强 ... ");
}
}
//*****4 applicationContext配置
<!-- 配置目标类 -->
<bean id="studentDao" class="com.moc.aop_demo3.StudentDaoImpl" />
<!-- 配置前置通知类型 -->
<bean id="myBeforeAdvice" class="com.moc.aop_demo3.MyBeforeAdvice" />
<!-- Spring AOP 产生代理对象 -->
<bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean" >
<!-- 配置目标类 -->
<property name="target" ref="studentDao" />
<!-- 实现的接口 -->
<property name="proxyInterfaces" value="com.moc.aop_demo3.StudentDao" />
<!-- 采用拦截的名称 -->
<property name="interceptorNames" value="myBeforeAdvice" />
<!-- 是否使用CGlib来产生代理 -->
<property name="optimize" value="true"/>
</bean>
//*****5 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo {
@Resource(name = "studentDaoProxy")
private StudentDao studentDao;
@Test
public void demo() {
studentDao.find();
studentDao.save();
}
}
2、PointcutAdvisor 切点切面案例
使用普通Advice作为切面,将对目标类所有方法进行拦截,不够灵活,在实际开发中常采用带有切点的切面。
- 常用PointcutAdvisor 实现类
- DefaultPointcutAdvisor 最常用的切面类型,它可以通过任意Pointcut和Advice 组合定义切面
- JdkRegexpMethodPointcut 构造正则表达式切点
//*****1 切面增强实现类
public class CustomerDao {
public void find() {
System.out.println("查询客户 ...");
}
public void save() {
System.out.println("保存客户 ...");
}
}
//*****2 环绕增强通知
public class MyAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕前增强 ...");
Object obj = methodInvocation.proceed();
System.out.println("环绕后增强 ...");
return obj;
}
}
//*****3 applicationContext配置
<!-- 配置目标类 -->
<bean id="customerDao" class="com.moc.aop_demo.CustomerDao" />
<!-- 配置通知: 环绕通知 -->
<bean id="myAroundAdvice" class="com.moc.aop_demo.MyAroundAdvice" />
<!-- 一般的切面使用通知作为切面,因为要对目标类的某个方法
进行增强就需要配置一个带有切入点的切面 -->
<!-- 正则类型的切面 -->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- pattern中配置正则表达式: .任意字符 * 任意次数-->
<!-- .* 代表所有方法 .*save.* 代表save方法, 具体的话应该是带包名的全路径 -->
<!--<property name="pattern" value=".*save.*" />-->
<!-- 增强多个方法 -->
<property name="patterns" value=".*save.*,.*delete.*"/>
<property name="advice" ref="myAroundAdvice" />
</bean>
<!-- 配置产生代理 -->
<bean id="customerDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean" >
<property name="target" ref="customerDao" />
<!-- 无接口时的配置 -->
<property name="proxyTargetClass" value="true" />
<!-- 拦截器的配置 -->
<property name="interceptorNames" value="myAdvisor" />
</bean>
//*****4 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext4.xml")
public class SpringDemo4 {
@Resource(name="customerDaoProxy") // 注入代理对象
private CustomerDao customerDao;
@Test
public void demo1() {
customerDao.find();
customerDao.save();
}
}
五、自动创建代理
前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大。
- 解决方案:自动创建代理
- BeanNameAutoProxyCreator 根据Bean名称创建代理
- DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理,切面自动代理
- AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ注解进行自动代理
1、Bean名称自动代理
//*****1 切面增强实现类
public class CustomerDao {
public void find() {System.out.println("查询客户 ...");}
public void save() {System.out.println("保存客户 ...");}
}
public interface StudentDao {
public void find();
public void save();
}
public class StudentDaoImpl implements StudentDao {
public void find() {System.out.println("查找学生 ...");}
public void save() {System.out.println("保存学生 ...");}
}
//*****2 前置增强通知
public class MyBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("AOP前置增强 ... ");
}
}
//*****3 applicationContext配置
<!-- 配置目标类 -->
<bean id="studentDao" class="com.moc.aop_demo.StudentDaoImpl" />
<bean id="customerDao" class="com.moc.aop_demo.CustomerDao" />
<!-- 配置增强 -->
<bean id="myBeforeAdvice" class="com.moc.aop_demo.MyBeforeAdvice" />
<!-- 对所有以DAO结尾Bean所有方法使用代理 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao" />
<property name="interceptorNames" value="myBeforeAdvice" />
</bean>
//*****4 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext5.xml")
public class SpringDemo5 {
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(name = "customerDao")
private CustomerDao customerDao;
@Test
public void demo1() {
studentDao.find();
studentDao.save();
customerDao.find();
customerDao.save();
}
}
2、切面自动代理
//*****1 切面增强实现类
public class CustomerDao {
public void find() {System.out.println("查询客户 ...");}
public void save() {System.out.println("保存客户 ...");}
}
public interface StudentDao {
public void find();
public void save();
}
public class StudentDaoImpl implements StudentDao {
public void find() {System.out.println("查找学生 ...");}
public void save() {System.out.println("保存学生 ...");}
}
//*****2 环绕增强通知
public class MyAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕前增强 ...");
Object obj = methodInvocation.proceed();
System.out.println("环绕后增强 ...");
return obj;
}
}
//*****3 applicationContext配置
<!-- 配置目标类 -->
<bean id="studentDao" class="com.moc.aop_demo.StudentDaoImpl" />
<bean id="customerDao" class="com.moc.aop_demo.CustomerDao" />
<!-- 配置增强 -->
<bean id="myAroundAdvice" class="com.moc.aop_demo.MyAroundAdvice" />
<!-- 配置切面 -->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value="com\.moc\.aop_demo\.CustomerDao\.save" />
<property name="advice" ref="myAroundAdvice" />
</bean>
<!--采用切面自动代理方式 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
//*****4 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext6.xml")
public class SpringDemo6 {
@Resource(name="studentDao")
private StudentDao studentDao;
@Resource(name = "customerDao")
private CustomerDao customerDao;
@Test
public void demo1() {
studentDao.find();
studentDao.save();
customerDao.find();
customerDao.save();
}
}