- AOP的概述
- AOP的术语
- AOP的底层实现
- 只有使用接口的业务类,才可以使用
- 可以看到test2中的权限校验处理已经被执行了,这就是JDK动态代理的代码实现。其实在Spring中,以上这些JDK动态代理代码,是不需要手写的,只需要配置一下就可以了
- 可以看到test2中的权限校验处理已经被执行了,这就是CGLIB动态代理的代码实现。其实在Spring中,以上这些CGLIB动态代理代码,也是不需要手写的,只需要配置一下就可以了
- 一般切面编程案例
- AOP并不是Spring的发明
#AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice - 这就是一般切面,可以看到StudentDaoImpl类里面的所有方法,都被增强了
- 传统AOP的自动代理
- 增强类中的某个方法
AOP的概述
- AOP Aspect Oriented Programing 面向切面编程
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视,事务挂历,安全检查,缓存)
- Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,在运行期通过代理方式向目标类植入增强代码
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。



AOP的术语
JoinPoint(连接点):
所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义
Advice(通知/增强):
所谓通知是指拦截到Joinpoint之后所要做的事情
通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)
#比如在保存之前,做权限校验,就是前置通知
#比如在删除之后,做日志记录,就是后置通知
#前置通知 + 后置通知 = 环绕通知
Introduction(引介):
引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field
#spring不推荐使用
Target(目标对象):
代理的目标对象
Weaving(织入):
是指把增强应用到目标对象来创建新的代理对象的过程
spring采用动态代理织入,而AspectJ采用编译器织入和类装载期织入
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面):
是切入点和通知(引介)的结合

AOP的底层实现
JDK动态代理
只有使用接口的业务类,才可以使用
src/main/java/com/song/aop/demo1/UserDao
package com.song.aop.demo1;public interface UserDao {public void save();public void update();public void delete();public void find();}
src/main/java/com/song/aop/demo1/UserDaoImpl
package com.song.aop.demo1;public class UserDaoImpl implements UserDao{public void save() {System.out.println("保存用户");}public void update() {System.out.println("修改用户");}public void delete() {System.out.println("删除用户");}public void find() {System.out.println("查询用户");}}
src/main/java/com/song/aop/demo1/MyJdkProxy
package com.song.aop.demo1;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MyJkdProxy implements InvocationHandler {
private UserDao userDao;
public MyJkdProxy(UserDao userDao){
this.userDao = userDao;
}
public Object createProxy(){
Object proxy = Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), this);
return proxy;
}
@Override
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);
}
}
src/test/java/com/song/aop/demo1/Demo1Testor
package com.song.sop.demo1;
import com.song.aop.demo1.MyJkdProxy;
import com.song.aop.demo1.UserDao;
import com.song.aop.demo1.UserDaoImpl;
import org.junit.Test;
public class Demo1Testor {
@Test
public void test1(){
UserDao userDao = new UserDaoImpl();
userDao.save();
userDao.update();
userDao.delete();
userDao.find();
}
@Test
public void test2(){
UserDao userDao = new UserDaoImpl();
UserDao proxy = (UserDao)new MyJkdProxy(userDao).createProxy();
proxy.save();
proxy.update();
proxy.delete();
proxy.find();
}
}
test1的运行结果:
保存用户
修改用户
删除用户
查询用户
test2的运行结果:
权限校验
保存用户
修改用户
删除用户
查询用户
可以看到test2中的权限校验处理已经被执行了,这就是JDK动态代理的代码实现。其实在Spring中,以上这些JDK动态代理代码,是不需要手写的,只需要配置一下就可以了
CGLIB的动态代理
- 对于不是用接口的业务类,无法使用JDK动态代理
- CGlib采用非常底层的字节码技术,可以为一个类创建子类,解决无接口代理问题
pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.2.4.RELEASE</version>
</dependency
src/main/java/com/song/aop/demo2/ProductDao
package com.song.aop.demo2;
public class ProductDao {
public void save(){
System.out.println("保存商品");
}
public void update(){
System.out.println("修改商品");
}
public void delete(){
System.out.println("删除商品");
}
public void find(){
System.out.println("查询商品");
}
}
src/main/java/com/song/aop/demo2/MyCglibProxy
package com.song.aop.demo2;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
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;
}
@Override
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);
}
}
src/test/java/com/song/aop/demo2/Demo2Testor
package com.song.sop.demo2;
import com.song.aop.demo2.MyCglibProxy;
import com.song.aop.demo2.ProductDao;
import org.junit.Test;
public class Demo2Testor {
@Test
public void test1(){
ProductDao productDao = new ProductDao();
productDao.save();
productDao.update();
productDao.delete();
productDao.find();
}
@Test
public void test2(){
ProductDao productDao = new ProductDao();
ProductDao proxy = (ProductDao)new MyCglibProxy(productDao).createProxy();
proxy.save();
proxy.update();
proxy.delete();
proxy.find();
}
}
test1的运行结果:
保存用户
修改用户
删除用户
查询用户
test2的运行结果:
权限校验
保存用户
修改用户
删除用户
查询用户
可以看到test2中的权限校验处理已经被执行了,这就是CGLIB动态代理的代码实现。其实在Spring中,以上这些CGLIB动态代理代码,也是不需要手写的,只需要配置一下就可以了
AOP知识总结
- Spring在运行期,生成动态代理对象,不需要特殊的编译器
- Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术,为目标Bean执行横向织入
若目标对象实现了若干接口,spring使用JDK的java.lang.reflect.Proxy类代理
若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类
- 程序中应优先对接口创建代理,便于程序解耦维护
- 标记为final的方法,不能被代理,会因为无法进行覆盖
JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰
CGLib是针对目标类生产子类,因此类或方法,不能使用final
- Spring只支持方法连接点,不提供属性连接点
一般切面编程案例
通知类型的介绍
AOP并不是Spring的发明
#AOP联盟为通知Advice定义了org.aopalliance.aop.Interface.Advice
Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
- 前置通知 org.springframework.aop.MethodBeforeAdvice
在目标方法执行前实施增强
- 后置通知 org.springframework.aop.AfterReturningAdvice
在目标方法执行后实施增强
- 环绕通知 org.aopalliance.intercept.MethodInterceptor
在目标方法执行前后实施增强
- 异常抛出通知 org.springframework.aop.ThrowsAdvice
在方法抛出异常后实施增强
- 引介通知 org.springframework.aop.IntroductionInterceptor(不建议使用)
在目标类中添加一些新的方法和属性
切面类型的介绍
- Advisor:代表一般切面,Advice本身就是一个切面,对目标类所有方法进行拦截
- PointcutAdvisor:代表具有切点的切面,可以指定拦截目标类哪些方法
- IntroductionAdvisor:代表引介切面,针对引介通知而使用切面(不建议使用)
一般的切面
- proxyTargetClass:是否对类代理而不是接口,设置为true时,使用CGLib代理
- interceptorNames:需要织入目标的Advice
- singleton:返回代理是否为单实例,默认为单例
- optimize:当设置为ture时,强制使用CGLib
pom.xml
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
</dependency>
src/main/resources/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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置目标类-->
<bean id="studentDao" class="com.song.aop.demo3.StudentDaoImpl"/>
<!-- 前置通知类型-->
<bean id="myBeforeAdvice" class="com.song.aop.demo3.MyBeforeAdvice"/>
<!-- Spring的AOP产生代理对象-->
<bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 配置目标类-->
<property name="target" ref="studentDao"/>
<!-- 实现的接口-->
<property name="proxyInterfaces" value="com.song.aop.demo3.StudentDao"/>
<!-- 采用拦截的名称-->
<property name="interceptorNames" value="myBeforeAdvice"/>
</bean>
</beans>
src/main/java/com/song/aop/demo3/StudentDao.java
package com.song.aop.demo3;
public interface StudentDao {
public void find();
public void save();
public void update();
public void delete();
}
src/main/java/com/song/aop/demo3/StudentDaoImpl.java
package com.song.aop.demo3;
public class StudentDaoImpl implements StudentDao{
@Override
public void find() {
System.out.println("学生查询");
}
@Override
public void save() {
System.out.println("学生保存");
}
@Override
public void update() {
System.out.println("学生修改");
}
@Override
public void delete() {
System.out.println("学生删除");
}
}
src/main/java/com/song/aop/demo3/MyBeforeAdvice.java
package com.song.aop.demo3;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("===== 前置增强 =====");
}
}
src/test/java/com/song/aop/demo3/Demo3Testor.java
package com.song.sop.demo3;
import com.song.aop.demo3.StudentDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo3Testor {
@Resource(name = "studentDaoProxy")
private StudentDao studentDao;
@Test
public void test1(){
studentDao.find();
studentDao.save();
studentDao.update();
studentDao.delete();
}
}
运行结果:
===== 前置增强 =====
学生查询
===== 前置增强 =====
学生保存
===== 前置增强 =====
学生修改
===== 前置增强 =====
学生删除
这就是一般切面,可以看到StudentDaoImpl类里面的所有方法,都被增强了
带有切入点的切面
- 使用普通Advice作为切面,将对目标类所有方法进行拦截,不够灵活,在实际开发中常采用带有切点的切面
- 常用PointcutAdvisor实现类
DefaultPointcutAdvisor:最常用的切面类型,它可以通过任意Pointcut和Advice组合定义切面
JdkRegexpMethodPointcut:构造正则表达式切点
src/main/resources/applicationContext.xml
<!-- 配置目标类-->
<bean id="customerDao" class="com.song.aop.demo4.CustomerDao"/>
<!-- 配置通知-->
<bean id="myAroundAdvice" class="com.song.aop.demo4.MyAroundAdvice"/>
<!-- 一般的切面是使用通知作为切面的,因为要对目标类的某个方法进行增强就需要配置一个带有切入点的切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- pattern中配置正则表达式-->
<!-- <property name="pattern" value=".*save"/> -->
<property name="patterns" value=".*save,.*update"/>
<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>
src/main/java/com/song/aop/demo4/CustomerDao.java
package com.song.aop.demo4;
public class CustomerDao {
public void find(){
System.out.println("查询客户");
}
public void save(){
System.out.println("保存客户");
}
public void update(){
System.out.println("修改客户");
}
public void delete(){
System.out.println("删除客户");
}
}
src/main/java/com/song/aop/demo4/MyAroundAdvice.java
package com.song.aop.demo4;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("===== 环绕前增强 ===== ");
Object proceed = methodInvocation.proceed();
System.out.println("===== 环绕后增强 ===== ");
return proceed;
}
}
src/test/java/com/song/aop/demo4/Demo4Testor.java
package com.song.sop.demo4;
import com.song.aop.demo4.CustomerDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo4Testor {
@Resource(name = "customerDaoProxy")
private CustomerDao customerDao;
@Test
public void test1(){
customerDao.find();
customerDao.save();
customerDao.delete();
customerDao.update();
}
}
运行结果:
查询客户
===== 环绕前增强 =====
保存客户
===== 环绕后增强 =====
删除客户
===== 环绕前增强 =====
修改客户
===== 环绕后增强 =====
传统AOP的自动代理
- 前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大
- 解决方案:自动创建代理
BeanNameAutoProxyCreator:根据Bean名称创建代理
DefaultAdvisorAutoProxyCreator:根据Advisor本身包含信息创建代理
AnnotationAwareAspectJAutoProxyCreator:基于Bean中的AspectJ注解进行自动代理
BeanNameAutoProxyCreator
案例:对所有以DAO结尾的Bean所有方法使用代理
src/main/resources/applicationContext.xml
<bean id="studentDao" class="com.song.aop.demo5.StudentDaoImpl"/>
<bean id="customerDao" class="com.song.aop.demo5.CustomerDao"/>
<!-- 配置增强-->
<bean id="myBeforeAdvice" class="com.song.aop.demo5.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="com.song.aop.demo5.MyAroundAdvice"/>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Dao"/>
<property name="interceptorNames" value="myBeforeAdvice"/>
</bean>
src/main/java/com/song/aop/demo5/CustomerDao.java
package com.song.aop.demo5;
public class CustomerDao {
public void find(){
System.out.println("查询客户");
}
public void save(){
System.out.println("保存客户");
}
public void update(){
System.out.println("修改客户");
}
public void delete(){
System.out.println("删除客户");
}
}
src/main/java/com/song/aop/demo5/Student.java
package com.song.aop.demo5;
public interface StudentDao {
public void find();
public void save();
public void update();
public void delete();
}
src/main/java/com/song/aop/demo5/StudentImpl.java
package com.song.aop.demo5;
public class StudentDaoImpl implements StudentDao {
@Override
public void find() {
System.out.println("学生查询");
}
@Override
public void save() {
System.out.println("学生保存");
}
@Override
public void update() {
System.out.println("学生修改");
}
@Override
public void delete() {
System.out.println("学生删除");
}
}
src/main/java/com/song/aop/demo5/MyBeforeAdvice.java
package com.song.aop.demo5;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println("===== 前置增强 =====");
}
}
src/main/java/com/song/aop/demo5/MyAroundAdvice.java
package com.song.aop.demo5;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("===== 环绕前增强 ===== ");
Object proceed = methodInvocation.proceed();
System.out.println("===== 环绕后增强 ===== ");
return proceed;
}
}
src/test/java/com/song/aop/demo5/Demo5Testor.java
package com.song.sop.demo5;
import com.song.aop.demo5.CustomerDao;
import com.song.aop.demo5.StudentDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo5Testor {
@Resource(name = "studentDao")
private StudentDao studentDao;
@Resource(name = "customerDao")
private CustomerDao customerDao;
@Test
public void demo1(){
studentDao.find();
studentDao.save();
studentDao.update();
studentDao.delete();
customerDao.find();
customerDao.save();
customerDao.update();
customerDao.delete();
}
}
运行结果:
===== 前置增强 =====
学生查询
===== 前置增强 =====
学生保存
===== 前置增强 =====
学生修改
===== 前置增强 =====
学生删除
===== 前置增强 =====
查询客户
===== 前置增强 =====
保存客户
===== 前置增强 =====
修改客户
===== 前置增强 =====
删除客户
DefaultAdvisorAutoProxyCreator
增强类中的某个方法
src/main/resources/applicationContext.xml
<bean id="studentDao" class="com.song.aop.demo6.StudentDaoImpl"/>
<bean id="customerDao" class="com.song.aop.demo6.CustomerDao"/>
<!-- 配置增强-->
<bean id="myBeforeAdvice" class="com.song.aop.demo6.MyBeforeAdvice"/>
<bean id="myAroundAdvice" class="com.song.aop.demo6.MyAroundAdvice"/>
<!-- 配置切面-->
<bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="pattern" value="com.song.aop.demo6.CustomerDao.save"/>
<property name="advice" ref="myAroundAdvice"/>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
src/test/java/com/song/aop/demo6/Demo6Testor.java
package com.song.sop.demo6;
import com.song.aop.demo6.CustomerDao;
import com.song.aop.demo6.StudentDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo6Testor {
@Resource(name = "studentDao")
private StudentDao studentDao;
@Resource(name = "customerDao")
private CustomerDao customerDao;
@Test
public void test1(){
studentDao.find();
studentDao.save();
studentDao.update();
studentDao.delete();
customerDao.find();
customerDao.save();
customerDao.update();
customerDao.delete();
}
}
运行结果:
学生查询
学生保存
学生修改
学生删除
查询客户
===== 环绕前增强 =====
保存客户
===== 环绕后增强 =====
修改客户
删除客户
