一、什么是AOP?
Aspect oritention programming(面向切面编程),AOP是一种思想,高度概括的话是“横向重复,纵向抽取”,如何理解呢?举个例子:访问页面时需要权限认证,如果每个页面都去实现方法显然是不合适的,这个时候我们就可以利用切面编程。
每个页面都去实现这个方法就是横向的重复,我们直接从中切入,封装一个与主业务无关的权限验证的公共方法,这样可以减少系统的重复代码,降低模块之间的耦合度,简单的示意图如下:
二、应用场景
AOP用来封装横切关注点,具体可以在下面的场景中使用:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试 l
ogging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步 T
ransactions 事务
三、相关概念
1.连接点(Joinpoint) 所谓连接点是指那些可能被拦截到的方法。例如:所有可以增加的方法
2.切点(Pointcut) 已经被增强的连接点
3.增强(Advice) 增强的代码
4.目标对象(Target) 目标类,需要被代理的类
5.织入(Weaving) 是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程
6.代理(Proxy) 一个类被AOP织入增强后,就产生出了一个结果类,它是融合了原类和增强逻辑的代理类。
7.切面(Aspect)切入点+通知
通知类型:Spring按照通知Advice在目标类方法的连接点位置,可以分为5类
-
前置通知 (在目标方法执行前实施增强)
-
后置通知(在目标方法执行后实施增强)
-
环绕通知(在目标方法执行前后实施增加)
-
异常抛出通知(在方法跑出异常时通知)
-
引介通知(在目标类中添加一些新的方法和属性)
四、实现原理
AOP的实现关键在于AOP框架自动创建的AOP代理。AOP代理主要分为两大类:
静态代理:使用AOP框架提供的命令进行编译,从而在编译阶段就可以生成AOP代理类,因此也称为编译时增强;静态代理一Aspectj为代表。
动态代理:在运行时借助于JDK动态代理,CGLIB等在内存中临时生成AOP动态代理类,因此也被称为运行时增强,Spring AOP用的就是动态代理。
4.1 静态代理
例子:在增加员工和删除员工时增加事务处理
- //员工类
- public class Employee {
- private Integer uid;
- public void setUid(Integer uid) {
- this.uid = uid;
- }
- public Integer getUid() {
- return uid;
- }
- private Integer age;
- private String name;
- public Integer getAge() {
- return age;
- }
- public String getName() {
- return name;
- }
- public void setAge(Integer age) {
- this.age = age;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
员工接口:
- //员工接口
- public interface EmployeeService {
- //新增方法
- void addEmployee(Employee employee);
- //删除方法
- void deleteEmployee(Integer uid);
- }
员工实现:
- //员工方法实现
- public class EmployeeServiceImpl implements EmployeeService {
- @Override
- public void addEmployee(Employee employee) {
- System.out.println(\"新增员工\");
- }
- @Override
- public void deleteEmployee(Integer uid) {
- System.out.println(\"删除员工\");
- }
- }
事务类:
- //事务类
- public class MyTransaction {
- //开启事务
- public void before(){
- System.out.println(\"开启事务\");
- }
- //提交事务
- public void after(){
- System.out.println(\"提交事务\");
- }
- }
代理类:
- //代理类
- public class ProxyEmployee implements EmployeeService {
- //
- private EmployeeService employeeService;
- private MyTransaction myTransaction;
- public ProxyEmployee(EmployeeService employeeService,MyTransaction myTransaction)
- {
- this.employeeService=employeeService;
- this.myTransaction=myTransaction;
- }
- @Override
- public void addEmployee(Employee employee) {
- myTransaction.before();
- employeeService.addEmployee(employee);
- myTransaction.after();
- }
- @Override
- public void deleteEmployee(Integer uid) {
- myTransaction.before();
- employeeService.deleteEmployee(uid);
- myTransaction.after();
- }
- }
测试:
- @Test
- public void fun1(){
- MyTransaction transaction = new MyTransaction();
- EmployeeService EmployeeService = new EmployeeServiceImpl();
- //产生静态代理对象
- ProxyEmployee proxy = new ProxyEmployee(EmployeeService, transaction);
- proxy.addEmployee(null);
- proxy.deleteEmployee(0);
- }
结果:
这是静态代理的实现方式,静态代理有明显的缺点:
1、代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2、如果接口增加一个方法,比如 EmployeeService增加修改 updateEmployee()方法,则除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
4.2 动态代理
动态代理就不要自己手动生成代理类了,我们去掉 ProxyEmployee.java 类,增加一个 ObjectInterceptor.java 类
- public class ObjectInterceptor implements InvocationHandler {
- //目标类
- private Object target;
- //切面类(这里指事务类)
- private MyTransaction transaction;
- //通过构造器赋值
- public ObjectInterceptor(Object target,MyTransaction transaction){
- this.target = target;
- this.transaction = transaction;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
- this.transaction.before();
- method.invoke(target,args);
- this.transaction.after();
- return null;
- }
- }
测试:
- @Test
- public void fun2(){
- //目标类
- Object target = new EmployeeServiceImpl ();
- //事务类
- MyTransaction transaction = new MyTransaction();
- ObjectInterceptor proxyObject = new ObjectInterceptor(target, transaction);
- /**
- * 三个参数的含义:
- * 1、目标类的类加载器
- * 2、目标类所有实现的接口
- * 3、拦截器
- */
- EmployeeService employeeService = (EmployeeService) Proxy.newProxyInstance(target.getClass().getClassLoader(),
- target.getClass().getInterfaces(), proxyObject);
- employeeService.addEmployee(null);
- employeeService.deleteEmployee(0);
- }
结果:
五、spring的处理AOP的方式
spring 有两种方式实现AOP的:一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ)
5.1 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:context=\"http://www.springframework.org/schema/context\"
- 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/context
- http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd\">
- <!--目标类-->
- <bean name=\"employeeService\" class=\"com.yuanqinnan.aop.EmployeeServiceImpl\"></bean>
- <bean name=\"transaction\" class=\"com.yuanqinnan.aop.MyTransaction\"></bean>
- <aop:config>
- <aop:aspect ref=\"transaction\">
- <aop:pointcut id=\"pointcut\" expression=\"execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))\"/>
- <!-- 配置前置通知,注意 method 的值要和 对应切面的类方法名称相同 -->
- <aop:before method=\"before\" pointcut-ref=\"pointcut\"></aop:before>
- <!--配置后置通知,注意 method 的值要和 对应切面的类方法名称相同-->
- <aop:after-returning method=\"after\" pointcut-ref=\"pointcut\"/>
- </aop:aspect>
- </aop:config>
- </beans>
测试:
- @Test
- public void fun3(){
- ApplicationContext context = new ClassPathXmlApplicationContext(\"META-INF/applicationContext.xml\");
- EmployeeService employeeService = (EmployeeService) context.getBean(\"employeeService\");
- employeeService.addEmployee(null);
- }
结果:
补充:
1.aop:pointcut如果位于aop:aspect元素中,则命名切点只能被当前aop:aspect内定义的元素访问到,为了能被整个aop:config元素中定义的所有增强访问,则必须在aop:config下定义切点。
2.如果在aop:config元素下直接定义aop:pointcut,必须保证aop:pointcut在aop:aspect之前定义。aop:config下还可以定义aop:advisor,三者在aop:config中的配置有先后顺序的要求:首先必须是aop:pointcut,然后是aop:advisor,最后是aop:aspect。而在aop:aspect中定义的aop:pointcut则没有先后顺序的要求,可以在任何位置定义。
aop:pointcut:用来定义切入点,该切入点可以重用;
aop:advisor:用来定义只有一个通知和一个切入点的切面;
aop:aspect:用来定义切面,该切面可以包含多个切入点和通知,而且标签内部的通知和切入点定义是无序的;和advisor的区别就在此,advisor只包含一个通知和一个切入点。
3.在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut\”切入点\” 例如定义切入点表达式 execution(* com.sample.service.impl...(..)) execution()是最常用的切点函数,其语法如下所示:
整个表达式可以分为五个部分: (1)、execution(): 表达式主体。
(2)、第一个号:表示返回类型,号表示所有的类型。
(3)、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包,com.sample.service.impl包、子孙包下所有类的方法。
(4)、第二个号:表示类名,号表示所有的类。
(5)、(..):最后这个星号表示方法名,号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
5.2 注解配置
新建注解类:
- @Component
- @Aspect
- public class AopAspectJ {
- /**
- * 必须为final String类型的,注解里要使用的变量只能是静态常量类型的
- */
- public static final String EDP=\"execution(* com.yuanqinnan.aop.EmployeeServiceImpl..*(..))\";
- /**
- * 切面的前置方法 即方法执行前拦截到的方法
- * 在目标方法执行之前的通知
- * @param jp
- */
- @Before(EDP)
- public void doBefore(JoinPoint jp){
- System.out.println(\"=========执行前置通知==========\");
- }
- /**
- * 在方法正常执行通过之后执行的通知叫做返回通知
- * 可以返回到方法的返回值 在注解后加入returning
- * @param jp
- * @param result
- */
- @AfterReturning(value=EDP,returning=\"result\")
- public void doAfterReturning(JoinPoint jp,String result){
- System.out.println(\"===========执行后置通知============\");
- }
- /**
- * 最终通知:目标方法调用之后执行的通知(无论目标方法是否出现异常均执行)
- * @param jp
- */
- @After(value=EDP)
- public void doAfter(JoinPoint jp){
- System.out.println(\"===========执行最终通知============\");
- }
- /**
- * 环绕通知:目标方法调用前后执行的通知,可以在方法调用前后完成自定义的行为。
- * @param pjp
- * @return
- * @throws Throwable
- */
- @Around(EDP)
- public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println(\"======执行环绕通知开始=========\");
- // 调用方法的参数
- Object[] args = pjp.getArgs();
- // 调用的方法名
- String method = pjp.getSignature().getName();
- // 获取目标对象
- Object target = pjp.getTarget();
- // 执行完方法的返回值
- // 调用proceed()方法,就会触发切入点方法执行
- Object result=pjp.proceed();
- System.out.println(\"输出,方法名:\" + method + \";目标对象:\" + target + \";返回值:\" + result);
- System.out.println(\"======执行环绕通知结束=========\");
- return result;
- }
- /**
- * 在目标方法非正常执行完成, 抛出异常的时候会走此方法
- * @param jp
- * @param ex
- */
- @AfterThrowing(value=EDP,throwing=\"ex\")
- public void doAfterThrowing(JoinPoint jp,Exception ex) {
- System.out.println(\"===========执行异常通知============\");
- }
- }
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:context=\"http://www.springframework.org/schema/context\"
- 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/context
- http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd\">
- <context:component-scan base-package=\"com.yuanqinnan.aop\" ></context:component-scan>
- <!-- 声明spring对@AspectJ的支持 -->
- <aop:aspectj-autoproxy/>
- <!--目标类-->
- <bean name=\"employeeService\" class=\"com.yuanqinnan.aop.EmployeeServiceImpl\"></bean>
- </beans>
测试:
- @Test
- public void fun4(){
- ApplicationContext act = new ClassPathXmlApplicationContext(\"META-INF/applicationContext.xml\");
- EmployeeService employeeService = (EmployeeService) act.getBean(\"employeeService\");
- employeeService.addEmployee(null);
- }
结果:
pringAOP的知识就总结到这里