Aspect oritention programming(面向切面编程),AOP是一种思想,高度归纳的话是“横向重复,纵向抽取”,如何理解呢?举个例子:访问页面时须要权限认证,若是每一个页面都去实现方法显然是不合适的,这个时候咱们就能够利用切面编程。java
每一个页面都去实现这个方法就是横向的重复,咱们直接从中切入,封装一个与主业务无关的权限验证的公共方法,这样能够减小系统的重复代码,下降模块之间的耦合度,简单的示意图以下:spring
AOP用来封装横切关注点,具体能够在下面的场景中使用:express
Authentication 权限编程
Caching 缓存缓存
Context passing 内容传递性能优化
Error handling 错误处理app
Lazy loading 懒加载框架
Debugging 调试 lide
ogging, tracing, profiling and monitoring 记录跟踪 优化 校准函数
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
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用的就是动态代理。
例子:在增长员工和删除员工时增长事务处理
//员工类 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); }
结果:
这是静态代理的实现方式,静态代理有明显的缺点:
一、代理对象的一个接口只服务于一种类型的对象,若是要代理的方法不少,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就没法胜任了。
二、若是接口增长一个方法,好比 EmployeeService增长修改 updateEmployee()方法,则除了全部实现类须要实现这个方法外,全部代理类也须要实现此方法。增长了代码维护的复杂度。
动态代理就不要本身手动生成代理类了,咱们去掉 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); /** * 三个参数的含义: * 一、目标类的类加载器 * 二、目标类全部实现的接口 * 三、拦截器 */ EmployeeService employeeService = (EmployeeService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), proxyObject); employeeService.addEmployee(null); employeeService.deleteEmployee(0); }
结果:
spring 有两种方式实现AOP的:一种是采用声明的方式来实现(基于XML),一种是采用注解的方式来实现(基于AspectJ)
<?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)、(..):最后这个星号表示方法名,号表示全部的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。
新建注解类:
@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的知识就总结到这里