一、AOP概述
1、概述
- AOP,Aspect Oriented Programming的缩写,意为:面向切面编程
- AOP是一种编程范式,隶属于软工范畴,指导开发者如何组织程序结构AOP最早由AOP联盟的组织提出的,制定了一套规范
- Spring将AOP思想引入到框架中,必须遵守AOP联盟的规范
- 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
- AOP是OOP(面向对象编程)的延续,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型
- 利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率
- AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码(性能监视、事务管理、安全检查、缓存)
2、作用
可以在不修改源代码的前提下,对程序进行增强
二、AOP相关术语
- Joinpoint(连接点)
- 指那些可以被拦截到的点,就是可以被增强的方法
- 在spring中,这些连接点指的是方法,因为spring只支持方法类型的连接点
- Pointcut(切入点)
- 所谓切入点是指我们要对哪些Joinpoint(方法)进行拦截的定义
- 真正被增强的方法
- Advice(通知/增强)
- 通知是指拦截到Joinpoint之后所要做的事情就是通知,通知分为前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)
- 指的是切面类中的方法
- Introduction(引介)
- 引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或Field
- Target(目标对象)
- 代理的目标对象
- Weaving(织入)
- 是指把增强应用到目标对象来创建新的代理对象的过程
- 把切面类中的方法(Advice),配置到目标类(Target)中
- Proxy(代理)
- 一个类被AOP织入增强后,就产生一个结果代理类
- Aspect(切面)
- 是切入点和通知的结合,以后咱们自己来编写和配置的
- 就是自己写的切面类
三、AOP底层实现原理
Srping框架的AOP技术底层也是采用的代理技术,代理的方式提供了两种
- jdk动态代理
- cglib字节码增强
1、手动方式
1.1 jdk动态代理
必须有接口,运行期间生成代理对象
public interface UserDao {
public abstract void save();
public abstract void update();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("保存用户");
}
@Override
public void update() {
System.out.println("修改用户");
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* @ClassName: ProxyFactory
* @Description:使用jdk方式生成代理对象
* @author jsz
* @date 2018年8月14日
*/
public class ProxyFactory {
public static Object getProxy(final Class clazz) {
// 得到当前类的类加载器
// 得到当前类所实现的所有接口
// 表示如何调用目标对象中的方法(使用的是策略设计模式)
Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), new InvocationHandler() {
@Override
// 代理对象调用的任何方法,都处发此方法执行
// 参数1:当前的代理对象
// 参数2:当前的代理对象所调用的方法,反射中用
// 参数3:方当前的代理对象所调用的方法参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("save".equals(method.getName())) {
System.out.println("记录日志");
// 开启事务
}
// 提交事务
// 参数1:真实对象
// 参数2:方法的参数
return method.invoke(clazz.newInstance(), args);
}
});
// 返回代理对象
return proxy;
}
}
import org.junit.Test;
public class TestDemo {
@Test
public void test01() throws Exception {
UserDao proxy = (UserDao) ProxyFactory.getProxy(UserDaoImpl.class);
proxy.save();
proxy.update();
}
}
1.2 cglib动态代理
没有接口,采用生成类的子类,在类加载或者预编译的时候创建代理对象
/**
* @ClassName: BookDaoImpl
* @Description:实现类
* @author jsz
* @date 2018年8月14日
*/
public class BookDaoImpl {
public void save(){
System.out.println("保存图书...");
}
public void update(){
System.out.println("修改图书...");
}
}
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
* @ClassName: CgLibFactory
* @Description:使用cglib方式生成代理对象
* @author jsz
* @date 2018年8月14日
*/
public class CgLibFactory {
public static Object getProxy(final Class clazz) {
// 1、创建一个工具类
Enhancer enhancer = new Enhancer();
// 2、设置父类
enhancer.setSuperclass(clazz);
// 3、设置回调函数(方法增强)
enhancer.setCallback(new MethodInterceptor() {
@Override
// 代理对象的方法执行,回调函数执行
// 参数1:当前的代理对象
// 参数2:父类中的方法
// 参数3:方法中的参数
// 参数4:子类中的方法
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy methodProxy) throws Throwable {
try {
System.out.println("开启事务");
method.invoke(clazz.newInstance(), arg);// 调用真实对象中的方法
System.out.println("提交事务");
} catch (Exception e) {
e.printStackTrace();
System.out.println("回滚事务");
}
return null;
}
});
// 4、创建代理对象
Object proxy = enhancer.create();
return proxy;
}
}
public class TestBook {
/**
* @MethodName:test
* @Description:
* @throws Exception
*/
@Test
public void test() throws Exception {
Object proxy = CgLibFactory.getProxy(BookDaoImpl.class);
System.out.println(proxy);
}
}
2、半自动方式
package com.itheima.b_factory_bean;
/**
* @ClassName: UserServiceImpl
* @Description:目标类
* @author jsz
* @date 2019年3月16日
*/
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("b_factory_bean addUser");
}
@Override
public void updateUser() {
System.out.println("b_factory_bean updateUser");
}
@Override
public void deleteUser() {
System.out.println("b_factory_bean deleteUser");
}
}
package com.itheima.b_factory_bean;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* @ClassName: MyAspect
* @Description:切面类:确定通知,需要实现不同接口,
* 接口就是规范,从而确定方法名称
* 这里使用环绕通知MethodInterceptor
* @author jsz
* @date 2019年3月16日
*/
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("before3...");
// 手动执行目标方法
Object object = mi.proceed();
System.out.println("after3...");
return object;
}
}
package com.itheima.b_factory_bean;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @ClassName: TestFactoryBean
* @Description:半自动方式实现AOP测试类
* @author jsz
* @date 2019年3月16日
*/
public class TestFactoryBean {
@Test
public void test() {
String path = "com/itheima/b_factory_bean/applicationContext.xml";
ApplicationContext app = new ClassPathXmlApplicationContext(path);
UserService userService = (UserService) app.getBean("proxyServiceId");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
配置文件
<?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">
<!--1.创建目标类 -->
<bean id="userServiceId"
class="com.itheima.b_factory_bean.UserServiceImpl">
</bean>
<!-- 2.创建切面类 -->
<bean id="myAspectId" class="com.itheima.b_factory_bean.MyAspect"></bean>
<!-- 3.创建代理类
使用工厂bean FactoryBean,底层调用getObject(),返回特殊bean
ProxyFactoryBean 用于创建一个代理的工厂bean,生成特殊代理对象
interfaces:确定接口们
用<array>可以设置多个值,只有一个值时,value=""
target:确定目标类
interceptorNames:通知切面类的名称,类型String[]
用<array>可以设置多个值,只有一个值时,value=""
optimize:强制使用cglib
底层机制:
如果目标类有接口,采用jdk动态代理
如果没有接口,采用cglib字节码增强
如果optimize=true,无论是否有接口,都采用cglib
-->
<bean id="proxyServiceId"
class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 确定接口 -->
<property name="interfaces"
value="com.itheima.b_factory_bean.UserService">
</property>
<!-- 目标类 -->
<property name="target" ref="userServiceId"></property>
<!-- 通知切面类的名称 -->
<property name="interceptorNames" value="myAspectId"></property>
<property name="optimize" value="true"></property>
</bean>
</beans>
3、全自动方式
package com.itheima.c_spring_aop;
/**
* @ClassName: UserServiceImpl
* @Description:目标类
* @author jsz
* @date 2019年3月16日
*/
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("c_spring_aop addUser");
}
@Override
public void updateUser() {
System.out.println("c_spring_aop updateUser");
}
@Override
public void deleteUser() {
System.out.println("c_spring_aop deleteUser");
}
}
package com.itheima.c_spring_aop;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* @ClassName: MyAspect
* @Description:切面类:确定通知,需要实现不同接口
* 接口就是规范,从而确定方法名称
* 这里使用环绕通知MethodInterceptor
* @author jsz
* @date 2019年3月16日
*/
public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
System.out.println("before4...");
// 手动执行目标方法
Object object = mi.proceed();
System.out.println("after4...");
return object;
}
}
package com.itheima.c_spring_aop;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @ClassName: TestFactoryBean
* @Description:测试spring的AOP
* @author jsz
* @date 2019年3月16日
*/
public class TestSpringAop {
@Test
public void test() {
String path = "com/itheima/c_spring_aop/applicationContext.xml";
ApplicationContext app = new ClassPathXmlApplicationContext(path);
UserService userService = (UserService) app.getBean("userServiceId");
userService.addUser();
userService.updateUser();
userService.deleteUser();
}
}
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<!--1.创建目标类 -->
<bean id="userServiceId"
class="com.itheima.c_spring_aop.UserServiceImpl">
</bean>
<!-- 2.创建切面类 -->
<bean id="myAspectId" class="com.itheima.c_spring_aop.MyAspect"></bean>
<!-- 3.aop编程
3.1导入命名空间:xmlns:aop
3.2使用aop:config进行配置
proxy-target-class="true"
声明使用cglib代理
aop:pointcut:切入点,从目标类中获得具体方法
<aop:pointcut expression="切入点表达式" id="切入点的ID"/>
aop:advisor:特殊的切面,只有一个通知和一个切入点
<aop:advisor advice-ref="通知引用" pointcut-ref="切入点引用"/>
3.3切入点表达式
execution(* com.itheima.c_spring_aop.*.*(..))
选择方法 返回值任意 包 类名任意 方法名任意 参数任意
-->
<aop:config proxy-target-class="true">
<aop:pointcut expression="execution(* com.itheima.c_spring_aop.*.*(..))" id="myPointcut"/>
<aop:advisor advice-ref="myAspectId" pointcut-ref="myPointcut"/>
</aop:config>
</beans>
四、XML方式配置AOP
1、常用标签
1.1 <aop:config>
- 作用:用于声明开始aop的配置
1.2 <aop:aspect>
- 作用
- 用于配置切面
- 属性
- id:给切面提供一个唯一标识
- ref:引用配置好的切面类bean的id
1.3 <aop:pointcut>
- 作用
- 用于配置切入点表达式
- 属性
- expression:用于定义切入点表达式
- id:用于给切入点表达式提供一个唯一标识
1.4 <aop:before>
- 作用
- 用于配置前置通知
- 属性
- method:指定通知中方法的名称
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
1.5 <aop:after-returning>
- 作用
- 用于配置后置通知
- 属性
- method:指定通知中方法的名称
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
1.6 <aop:around>
- 作用
- 用于配置环绕通知
- 属性
- method:指定通知中方法的名称
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
1.7 <aop:after-throwing>
- 作用
- 用于配置异常通知
- 属性
- method:指定通知中方法的名称
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
1.8 <aop:after>
- 作用
- 用于配置最终通知
- 属性
- method:指定通知中方法的名称
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
1.9 <aop:around>
- 作用
- 用于配置环绕通知
- 属性
- method:指定通知中方法的名称
- pointct:定义切入点表达式
- pointcut-ref:指定切入点表达式的引用
2、切入点表达式
2.1 格式
- execution([修饰符] 返回值类型 包名.类名.方法名(参数))
2.2 种类
- public void com.itheima.spring.service.CustomerService.save(..)
- * com.itheima.spring.service.*Service.save(..)
- * com.itheima.spring.service.*.*(..)
- * com.itheima.spring.service.CustomerService+.*(..) 当前类和所有子类
- * com..*.*(..)
- * *..*.*(..)
3、入门案例
3.1 创建JavaWEB项目,引入具体的开发的jar包
- Spring框架开发的基本开发包
- Spring框架的AOP的开发包
- spring的aop开发包
- spring-aop-4.2.4.RELEASE.jar
- spring-aspects-4.2.4.RELEASE.jar
- 引入aop联盟的包
- com.springsource.org.aopalliance-1.0.0.jar
- aspectj的开发包
- com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
- spring的aop开发包
3.2 创建Spring的配置文件,引入具体的AOP的schema约束
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
3.3 编写接口和实现类
public class CustomerServiceImpl implements CustomerService {
@Override
public void addCustomer() {
System.out.println("调用了保存客户dao方法");
}
@Override
public void updateCustomer() {
System.out.println("调用了修改客户dao方法");
}
}
3.4 在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"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="service" class="com.itheima.service.CustomerServiceImpl"></bean>
</beans>
3.5 编写切面类
import org.aspectj.lang.ProceedingJoinPoint;
/**
* @ClassName: MyAspectXml
* @Description:aspect,切面类
* @author jsz
* @date 2018年8月15日
*/
public class MyAspectXml {
// advice:通知/增强
public void privilegeUser() {
System.out.println("校验用户权限执行了。。。");
}
// 后置增强
public void afterReturning() {
System.out.println("后置增强执行了。。。");
}
// 环绕通知
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("目标方法执行前。。。" + System.nanoTime());
proceedingJoinPoint.proceed();// 执行目标方法,注释了,不会执行目标方法
System.out.println("目标方法执行后。。。" + System.nanoTime());
}
// 异常通知
public void afterThrowing() {
System.out.println("异常增强执行了。。。");
}
// 最终通知
public void after() {
System.out.println("最终增强执行了。。。");
}
}
3.6 配置切面类
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean id="service" class="com.itheima.service.CustomerServiceImpl"></bean>
<!-- 配置切面类 -->
<bean id="myAspectXml" class="com.itheima.service.MyAspectXml"></bean>
</beans>
3.7 配置AOP
<?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: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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置目标类 -->
<bean id="service" class="com.itheima.service.CustomerServiceImpl"></bean>
<!-- 配置切面类 -->
<bean id="myAspectXml" class="com.itheima.service.MyAspectXml"></bean>
<!-- 配置AOP -->
<aop:config>
<!-- 配置连接点 -->
<!-- 切入点表达式: execution([方法的访问修饰符] 方法的返回值 包名.类名.方法名(参数)) -->
<aop:pointcut
expression="execution(* com.itheima.service.*.add*(..))"
id="pointcut1" />
<aop:pointcut
expression="execution(* com.itheima.service.*.update*(..))"
id="pointcut2" />
<!-- 织入:把连接点变成切入点 -->
<aop:aspect ref="myAspectXml">
<!-- 配置前置通知,save方法执行之前,增强的方法会执行 -->
<!-- method:增强方法 -->
<aop:before method="privilegeUser" pointcut-ref="pointcut1" />
</aop:aspect>
</aop:config>
</beans>
3.8 完成测试
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestAop {
@Resource(name="service")
private CustomerService service;
@Test
public void test() throws Exception {
service.addCustomer();
}
}
五、AOP的注解配置方式
1、常用注解
@Aspect ---定义切面类
@Before---前置通知
@AfterReturning---后置通知
@Around----环绕通知
@AfterThrowing----异常抛出通知
@After-----最终通知
@Pointcut----定义切入点
2、案例
2.1 加入约束
<?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: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.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 开启包扫描 -->
<context:component-scan base-package="com.itheima" />
<!-- 开户aop注解扫描 -->
<aop:aspectj-autoproxy />
</beans>
2.2 编写接口和实现类
import org.springframework.stereotype.Service;
/**
* @ClassName: CustomerServiceImpl
* @Description:target目标类
* @author jsz
* @date 2018年8月15日
*/
@Service("service")
public class CustomerServiceImpl implements CustomerService {
@Override
public void addCustomer() {
System.out.println("调用了保存客户dao方法");
}
@Override
public void updateCustomer() {
System.out.println("调用了修改客户dao方法");
}
}
2.3 编写切面类
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;
/**
* @ClassName: MyAspectXml
* @Description:aspect,切面类
* @author jsz
* @date 2018年8月15日
*/
@Component("myAspectXml")
@Aspect // 配置AOP
public class MyAspectXml {
@Before(value = "MyAspectXml.pointcut1()") // 前置增强
public void privilegeUser() {
System.out.println("校验用户权限执行了。。。");
}
@AfterReturning("MyAspectXml.pointcut2()") // 后置增强
public void afterReturning() {
System.out.println("后置增强执行了。。。");
}
@Around("MyAspectXml.pointcut2()") // 环绕增强
public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("目标方法执行前。。。" + System.nanoTime());
proceedingJoinPoint.proceed();// 执行目标方法,注释了,不会执行目标方法
System.out.println("目标方法执行后。。。" + System.nanoTime());
}
@AfterThrowing("MyAspectXml.pointcut2()") // 异常增强
public void afterThrowing() {
System.out.println("异常增强执行了。。。");
}
@After("MyAspectXml.pointcut1()") // 最终通知
public void after() {
System.out.println("最终增强执行了。。。");
}
@Pointcut("execution(* com.itheima.service.*.add*(..))")
public void pointcut1() {// id=pointcut1
}
@Pointcut("execution(* com.itheima.service.*.update*(..))")
public void pointcut2() {
}
}