先实例对比说说什么面向切面,看下面代码:
1 2 3 4 5 6 7 8 @Override public void savePerson () { System.out.println("开始保存到数据库....." ); save(person); System.out.println("...保存成功" ); }
上面打印的语句,其实就相当于日志,监控我有没有保存成功,这里我保存的是person对象,如果我还有student,teacher,dog等等很多对象都需要做增删改查操作,是不是在每个增删改查的语句前后都加上这两句话呢?这样不是很繁琐。那么有没有办法让每有执行save操作时就自动前后打印日志呢?这里就应运而生了面向切面AOP
下面再看看面向切面的例子吧!
maven工程加jar包依赖:
1 2 3 4 5 6 7 <dependency > <groupId > org.aspectj</groupId > <artifactId > aspectjweaver</artifactId > <version > 1.6.8</version > </dependency > <dependency >
applicationContext.xml配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <?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:tx ="http://www.springframework.org/schema/tx" 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-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd" > <context:component-scan base-package ="redisCache.service" /> <bean id ="transaction" class ="aop.Transaction" /> <aop:config > <aop:pointcut expression ="execution(* redisCache.service.impl.PersonDaoImpl.*(..))" id ="perform" /> <aop:aspect ref ="transaction" > <aop:before method ="beginTransaction" pointcut-ref ="perform" /> </aop:aspect > </aop:config > </beans >
切面工具类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package aop; import java.util.ArrayList;import java.util.List; import org.aspectj.lang.JoinPoint;import org.aspectj.lang.ProceedingJoinPoint; import redisCache.entity.Person; public class Transaction { public void beginTransaction () { System.out.println("begin Transaction" ); } public void commit (JoinPoint joinPoint, Object val) { String methodName = joinPoint.getSignature().getName(); System.out.println(methodName); System.out.println(joinPoint.getTarget().getClass().getName()); System.out.println("commit" ); List<Person> personList = (ArrayList<Person>) val; for (Person person : personList) { System.out.println(person.getPname()); } } public void finalMethod () { System.out.println("最终通知" ); } public void aroundMethod (ProceedingJoinPoint joinPoint) { try { System.out.println("around method" ); joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } } public void throwingMethod (Throwable except) { System.out.println(except.getMessage()); } }
person实体类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package redisCache.entity; public class Person { private Long pid; private String pname; public Long getPid () { return pid; } public void setPid (Long pid) { this .pid = pid; } public String getPname () { return pname; } public void setPname (String pname) { this .pname = pname; } }
personDao接口类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package redisCache.service; import java.util.List; import redisCache.entity.Person; public interface PersonDao { void deletePerson () ; List<Person> getPerson () throws Exception ; void savePerson () ; void updatePerson () ; }
personDaoImpl接口实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package redisCache.service.impl; import java.util.ArrayList;import java.util.List; import org.springframework.stereotype.Service; import redisCache.entity.Person;import redisCache.service.PersonDao;@Service ("personDao" )public class PersonDaoImpl implements PersonDao { @Override public void deletePerson () { System.out.println("delete perosn" ); } @Override public List<Person> getPerson () throws Exception { List<Person> personList = new ArrayList<Person>(); Person person1 = new Person(); person1.setPid(1L ); person1.setPname("person1" ); System.out.println("get person" ); personList.add(person1); Person person2 = new Person(); person2.setPid(2L ); person2.setPname("person2" ); personList.add(person2); return personList; } @Override public void savePerson () { System.out.println("delete perosn" ); } @Override public void updatePerson () { System.out.println("delete perosn" ); } }
TestAop测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package redisCache; import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext; import redisCache.service.PersonDao; public class TestAop { public static void main (String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml" ); PersonDao personDao=(PersonDao) context.getBean("personDao" ); try { personDao.getPerson(); } catch (Exception e) { e.printStackTrace(); } } }
允许测试类的结果:
1 2 3 4 5 6 7 log4j:WARN No appenders could be found for logger (org.springframework.core.env.StandardEnvironment). log4j:WARN Please initialize the log4j system properly. log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info. begin Transaction get person begin Transaction delete perosn
从打印结果中可以看出personDaoImpl实现类的所有方法只要执行就会先打印“begin Transcation” .
上面我们在执行方法前打印的方式称为前置通知,当然在面向切面的术语中还有其他诸如:后置通知,环绕通知,最终通知,异常通知。这里我们都在配置文件中加上,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <aop:config > <aop:pointcut expression ="execution(* redisCache.service.impl.PersonDaoImpl.*(..))" id ="perform" /> <aop:aspect ref ="transaction" > <aop:before method ="beginTransaction" pointcut-ref ="perform" /> </aop:aspect > </aop:config >
总结一句话就是:AOP 在不修改源代码的情况下给程序动态统一添加功能。 这样就能够在一个项目及时要在中途需要这么一个功能,那也就只需修改配置文件和加一个类,而没有该已经写好的类的代码。aop明显增加了代码的复用性,也省去了重新测试的时间。
通过实例大概了解aop的用途和优势后我们再结合上面的实例理解aop的原理和各种术语。
先上图:
上面的三条红色的竖向框就是经常说的切面,在这个切面里面有很多的方法,你大可以吧a()看做上面的说道的前置通知,b()看做后置通知,c()看做最终通知等等。总而言之,这些方法都不需要我们去写的,而是aop自动帮我们做好的。我们只要触动了我们的比如“保存方法”就会执行切面里的一系列方法。这样就省去了很多开发时间,也精简了代码。
因为这个AOP–面向切面编程是基于动态代理模式的,所以,要想搞清楚这个AOP,就必须得先了解下,什么是代理模式 ,什么又是动态代理模式 。动态代理模式的2种实现方式。
在小编的这篇博文中有简单解释代理: https://blog.csdn.net/csdnliuxin123524/article/details/81236007
下面重新写一下动态代理的实例步骤,代码转自一位很强的博主的博文:https://blog.csdn.net/qq_27093465/article/details/53351403
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package proxy1; import java.lang.reflect.Method; import org.springframework.cglib.proxy.Enhancer;import org.springframework.cglib.proxy.MethodInterceptor;import org.springframework.cglib.proxy.MethodProxy; public class MyInterceptor implements MethodInterceptor { private Object target; public MyInterceptor (Object o) { this .target=o; } @Override public Object intercept (Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if (method.getName().equals("getPerson" )){ System.out.println("aaaaa" ); method.invoke(this .target, objects); System.out.println("bbbbb" ); } return null ; } public Object createProxy () { Enhancer enhancer = new Enhancer(); enhancer.setCallback(this ); enhancer.setSuperclass(this .target.getClass()); return enhancer.create(); } }
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 package redisCache;import redisCache.service.impl.PersonDaoImpl; public class TestAop2 { public static void main (String[] args) throws Exception { PersonDaoImpl personDaoImpl=new PersonDaoImpl(); MyInterceptor myInterceptor=new MyInterceptor(personDaoImpl); PersonDaoImpl personDaoImpl2=(PersonDaoImpl) myInterceptor.createProxy(); personDaoImpl2.getPerson(); } }
结果:
上面代码,看完之后,就来对应AOP里面的各个概念到实际代码里面去 。
图上说了5个术语。加上下面的织入 ,也就是6个啦。
再加上代理对象 ,这个就比较简单了,测试代码有写注释啦。那么就是一共7个啦。
唯一漏掉的就是“引入” ,这个是系统自己实现的,我们就没必要去深究了。
注意理解以下几个概念:
代理对象的方法 = 目标对象的目标方法 + 所有切面的通知。
织入:形成代理对象的方法的过程
通知:实际上就是切面中的方法。
切入点 的理解:只有符合切入点的目标方法,才能加载通知。也就是调用切面的通知(方法)啦,看代码也就是说,切入点是控制代理对象内部的切面方法和目标对象的目标方法是否执行的条件。切面可以不止是一个。每个切面里面的通知即切面方法也是可以有很多的。
连接点 的理解:所谓连接点,也就是目标对象或者代理对象之中的方法。为什么说2个都 可以呢?因为如果是jdk实现的动态代理的话,那么目标对象和代理对象要实现共同的接口,如果是cglib实现的动态代理的话,那么代理对象类是目标对象类的子类。都是一个方法啦。所以这么理解就OK的啦。
上面的过程就可以理解为@Transcational注解所起的作用,因为spring事务的管理使用的就是aop动态代理的功能。