1.什么是AOP
百度解释:
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,
通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间得耦合度降低,提高程序的可重用性。
AOP Aspect Oriented Programing面向切面编程。
AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
AOP是通过一个接口下,同层级的代理来实现横向抽取功能。
Spring AOP使用纯Java实现,不需要专门的编译过程和类加载器,
在运行期间通过代理方式向目标类织入增强代码。
2.AOP相关术语
Joinpoint(连接点):所谓连接点是指那些被拦截到点。
在Spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)。
Introduction(引介):引介是一种特殊的通知,在不修改代码的前提下,
Introduction可以在运行期为类动态地添加一些方法或Field。
Target(目标对象):代理地目标对象
Weaving(织入):是指把增强应用到目标对象来创建新地代理对象地过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
Aspect(切面):是切入点和通知(引介)的结合。
3.AOP的底层实现
1)JDK动态代理
在Java中,即使不引用spring框架也可以实现代理。
(1)实现接口类
public interface UserDao { public void save(); public void update(); public void delete(); public void find(); }
(2)创建实例类,并创建增删改查四个方法
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("查询用户"); } }
(3)实现代理类
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; } //这个到底如何实现了?对save方法进行增强 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 SpringDemo1 { @Test public void demo1(){ UserDao userDao = new UserDaoImpl(); //生成代理对象 UserDao proxy = (UserDao) new MyJdkProxy(userDao).createProxy(); proxy.save(); proxy.update(); proxy.delete(); proxy.find(); } }
2)使用CGLIB生成代理
JDK代理只能对实现了接口类实现代理,如果没有实现接口,那么JDK的动态代理就会无效。
CGlib采用非常底层字节码技术,可以为一个类创建子类,然后继承这个类,解决无接口代理问题。
(1)创建实例类
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("查找商品"); } }
(2)创建代理类
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); //intercept // 4.生成代理 Object proxy = enhancer.create(); return proxy; } public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if ("save".equals(method.getName())){ System.out.println("权限校验"); return methodProxy.invokeSuper(proxy,objects); } return methodProxy.invokeSuper(proxy,objects); //invokeSuper调用父类,原封不动调用父类的方法 } }
(3)调用代理
public void demo1(){ ProductDao productDao = new ProductDao(); ProductDao proxy = (ProductDao)new MyCglibProxy(productDao).createProxy(); proxy.save(); proxy.delete(); }
Spring在运行期,生成动态代理对象,不需要特殊的编译器。
Spring AOP的底层就是通过JDK动态代理或CGLib动态代理技术,为目标Bean执行横向织入。
a.若目标对象实现了若干接口,spring使用JDK的Java.lang.reflect.Proxy类代理。
b.若目标对象没有实现任何接口,spring使用CGLIB库生成目标对象的子类。
程序中应优先对接口创建代理,便于程序解耦维护。
标记为final的方法,不能被代理,因为无法进行覆盖。
JDK动态代理,是针对接口生成子类,接口中方法不能使用final修饰。
CGLib是针对目标类生产子类,因此类或方法不能使final的
Spring只支持方法连接点,不提供属性连接点。
4.切面常用配置
AOP联盟为通知Advice定义了org.aopalliance.aop.Inerface.Advice规范。
Spring是实现这个规范最好的框架。
1)Spring通知类型
Spring按照通知Advice在目标类方法的连接点位置,可以分为5类:
前置通知:org.springframework.aop.MethodBeforeAdvice,在目标方法执行前实施增强;
后置通知:org.springframework.aop.AfterReturningAdvice,在目标方法执行后实施增强
环绕通知:org.aopalliance.intercept.MethodInterceptor,在目标方法执行前后实施增强
异常抛出通知:org.springframework.aop.ThrowsAdvice,在方法抛出异常后实施增强
引介通知:org.springframework.aop.IntroductionInterceptor,在目标类中添加一些新的方法和属性
2)Spring AOP切面类型
Advisor:代表一般切面,Advice(通知)本身就是一个切面,对目标类所有方法进行拦截。
PointcutAdvisor:代表具有切点的切面,可以指定拦截目标类哪些方法。
IntroductionAdvisor:代表引介切面,针对引介通知而使用切面(不要求掌握)
3)案例展示
(1)创建接口类
public interface StudentDao { public void find(); public void save(); public void update(); public void delete(); }
(2)创建实例类
public class StudentDaoImpl implements StudentDao{ public void find() { System.out.println("find"); } public void save() { System.out.println("save"); } public void update() { System.out.println("update"); } public void delete() { System.out.println("delete"); } }
(3)通知
public class MyBeforeAdvice implements MethodBeforeAdvice { public void before(Method method, Object[] objects, Object o) throws Throwable { System.out.println("前置通知"); } }
(4)配置拦截
<!--配置目标类--> <bean id="studentDao" class="com.ikidana.aop.demo3.StudentDaoImpl"/> <!--前置通知类型--> <bean id="myBeforeAdvice" class="com.ikidana.aop.demo3.MyBeforeAdvice"/> <!--Spring的AOP产生代理对象--> <bean id="studentDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!--配置目标类--> <property name="target" ref="studentDao"/> <!--实现的接口--> <property name="proxyInterfaces" value="com.ikidana.aop.demo3.StudentDao"/> <!--拦截的名称--> <property name="interceptorNames" value="myBeforeAdvice" /> </bean>
(5)Advisor全部拦截
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo3 {
@Resource(name="studentDaoProxy")
private StudentDao studentDao;
@Test
public void demo1(){
studentDao.find();
studentDao.delete();
studentDao.save();
studentDao.update();
}
}
5.PointcutAdvisor切点切面
target:目标类
proxyInterfaces:实现的接口
interceptorNames:拦截名称
proxyTargetClass:是否对类代理而不是接口,设置为true时,使用CGLib代理
interceptorNames:需要织入目标的Advice
singleton:返回代理是否为单实例,默认为单例
optimize:当设置为true时,强制使用CGLib,默认为JDK动态代理
6.Spring的传统AOP的动态代理
使用普通Advice作为切面,将对目标类所有方法进行拦截,不够灵活,在实际开发中常采用带有切点的切面。
常用PointcutAdvisor实现类
DefaultPointcutAdvisor最常用的切面类型,它可以通过任意Pointcut和Advice组合定义切面。
JdkRegexpMethodPointcut构造正则表达式切点。
上实现了所有方法的增强,下面是带有切入点切面的示例:
(1)创建实例类,并配置方法
public class CustomerDao { public void find() { System.out.println("查找"); } public void delete() { System.out.println("删除"); } public void update() { 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)配置增强规则
<!--配置目标类--> <bean id="customerDao" class="com.ikidana.aop.demo4.CustomerDao"/> <!--配置环绕通知--> <bean id="myAroundAdvice" class="com.ikidana.aop.demo4.MyAroundAdvice"/> <!--配置目标规则的切面--> <!--一般的切面是使用通知作为切面的,因为要对目标类的某个方法进行增强就需要配置一个带有切入点的切面--> <bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor" > <!--pattern中配置正则表达式:.任意字符 *任意次数--> <!--.*表示增强所有方法 .*save.*增强save方法--> <!--如果想增强多个方法,可以使用".*save.*,.*delete.*",还可以指定某个包下面的某个方法--> <property name="pattern" value=".*" /> <!--使用环绕通知类型--> <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:applicationContext2.xml") //配置文件注入 public class SpringDemo4 { // @Resource(name="customerDao") //对象注入 @Resource(name="customerDaoProxy") //注入代理对象 private CustomerDao customerDao; @Test public void demo1(){ //这里会使用CGLIB来实现代理对象,因为没有实现任何接口 customerDao.find(); } }
6.Spring的传统AOP动态代理
上面的proxy单个配置虽然不难,但是如果需要增强的方法非常多,那么配置文件会非常冗长。
前面的案例中,每个代理都是通过ProxyFactoryBean织入切面代理,在实际开发中,
非常多的Bean每个都配置ProxyFactoryBean开发维护量巨大。
解决方案:自动创建代理
BeanNameAutoProxyCreator 根据Bean名称创建代理
DefaultAdvisorAutoProxyCreator 根据Advisor本身包含信息创建代理
AnnotationAwareAspectJAutoProxyCreator 基于Bean中的AspectJ注解进行自动代理
(1)BeanNameAutoProxyCreator
对所有以DAO结尾Bean所有方法使用代理。
<!--配置目标类--> <bean id="studentDao" class="com.ikidana.aop.demo5.StudentDaoImpl"/> <bean id="customerDao" class="com.ikidana.aop.demo5.CustomerDao"/> <!--配置增强--> <bean id="myBeforeAdvice" class="com.ikidana.aop.demo5.MyBeforeAdvice"/> <bean id="myAroundAdvice" class="com.ikidana.aop.demo5.MyAroundAdvice"/> <!--自动生成代理--> <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <!--目标类--> <property name="beanNames" value="*Dao"/> <!--使用何种增强类型--> <property name="interceptorNames" value="myBeforeAdvice"/> </bean> @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext3.xml") public class SpringDemo5 { @Resource(name="studentDao") private StudentDao studentDao; @Resource(name="customerDao") private CustomerDao customerDao; @Test public void demo1(){ studentDao.find(); studentDao.delete(); studentDao.save(); studentDao.update(); customerDao.find(); customerDao.delete(); customerDao.save(); customerDao.update(); } }
(2)DefaultAdvisorAutoProxyCreator 切面自动代理
<!--配置目标类--> <bean id="studentDao" class="com.imooc.aop.demo6.StudentDaoImpl"/> <bean id="customerDao" class="com.imooc.aop.demo6.CustomerDao"/> <!-- 配置增强--> <bean id="myBeforeAdvice" class="com.imooc.aop.demo6.MyBeforeAdvice"/> <bean id="myAroundAdvice" class="com.imooc.aop.demo6.MyAroundAdvice"/> <!--配置切面--> <bean id="myAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <property name="pattern" value="com.imooc.aop.demo6.CustomerDao.save"/> <property name="advice" ref="myAroundAdvice"/> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>