前言
本篇紧接着 spring 入门详细教程 (二), 建议阅读本篇前, 先阅读第一篇和第二篇. 链接如下:
Spring 入门详细教程 (一) https://www.cnblogs.com/jichi/p/10165538.html
Spring 入门详细教程 (二) https://www.cnblogs.com/jichi/p/10176601.html
本篇主要讲解 spring 的 aop 相关.
一, aop 的概念
在软件业, AOP 为 Aspect Oriented Programming 的缩写, 意为: 面向切面编程, 通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. AOP 是 OOP 的延续, 是软件开发中的一个热点, 也是 Spring 框架中的一个重要内容, 是函数式编程的一种衍生范型. 利用 AOP 可以对业务逻辑的各个部分进行隔离, 从而使得业务逻辑各部分之间的耦合度降低, 提高程序的可重用性, 同时提高了开发的效率. AOP 是可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术.
AOP 主要实现功能日志记录, 性能统计, 安全控制, 事务处理, 异常处理等等. 将日志记录, 性能统计, 安全控制, 事务处理, 异常处理等代码从业务逻辑代码中划分出来, 通过对这些行为的分离, 我们希望可以将它们独立到非指导业务逻辑的方法中, 进而改变这些行为的时候不影响业务逻辑的代码.
AOP 主要思想总结为横向重复, 纵向抽取.
二, spring 实现 aop 的原理及底层实现
spring 实现 aop 的底层使用了两种代理机制. 一种是 jdk 的动态代理, 一种是 cglib 的动态代理. 下面来分析一下两种代理模式.
1,jdk 的动态代理
被代理对象必须要实现接口才能产生代理对象, 如果被代理对象不能实现接口, 则这种方式的动态代理技术无效.
接下来做一个底层代码的编写来进行理解.
(1) 首先 jdk 的动态代理要求被代理对象必须实现接口. 我们准备一个接口以及一个接口的实现类.
- public interface UserDao {
- public void saveUser();
- }
- public class UserDaoImpl implements UserDao {
- public void saveUser(){
- System.out.println("保存用户");
- }
- }
(2) 建立一个 UserDao 的动态代理类, 实现接口 InvocationHandler.
- public class UserProxy implements InvocationHandler{
- private UserDao userDao ;
- public UserProxy(UserDao userDao) {
- this.userDao = userDao;
- }
- public UserDao createProxy(){
- UserDao userDaoProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(), this);
- return userDaoProxy;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- System.out.println("动态代理");
- return method.invoke(userDao, args);
- }
- }
(3) 进行单元测试, 发现第一个方法执行的时候没有被动态代理, 第二个执行的时候进行了动态代理.
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:applicationContext.xml")
- public class TestJunit {
- @Test
- public void test3(){
- UserDaoImpl userDaoImpl = new UserDaoImpl();
- userDaoImpl.saveUser();
- UserProxy userProxy = new UserProxy(userDaoImpl);
- UserDao createProxy = userProxy.createProxy();
- createProxy.saveUser();
- }
- }
2,cglib 动态代理
针对一些不能实现接口的代理对象产生代理, 可以对没有被 final 修饰的任何对象进行继承代理, 其底层应用的是字节码增强的技术, 生成代理对象的子类对象. 如果被 final 修饰, 类不可继承, 便不可使用 cglib 动态代理.
(1) 创建一个 cglib 动态代理对象实现接口.
- public class CglibProxy implements MethodInterceptor{
- private UserDaoImpl userDaoImpl;
- public CglibProxy(UserDaoImpl userDaoImpl) {
- this.userDaoImpl = userDaoImpl;
- }
- public UserDaoImpl createProxy(){
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(UserDaoImpl.class);
- enhancer.setCallback(this);
- UserDaoImpl udi = (UserDaoImpl) enhancer.create();
- return udi;
- }
- @Override
- public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
- Object obj = methodProxy.invokeSuper(proxy, args);
- System.out.println("动态代理");
- return obj;
- }
- }
(2) 进行单元测试.
- public void test4(){
- UserDaoImpl userDaoImpl = new UserDaoImpl();
- userDaoImpl.saveUser();
- CglibProxy cglib = new CglibProxy(userDaoImpl);
- UserDaoImpl userDaoImpl2 = cglib.createProxy();
- userDaoImpl2.saveUser();
- }
可以发现第一个 saveUser 没有执行动态代理, 第二个执行了动态代理.
结论: 两种代理技术针对不同情况, 互相弥补, 从而使任何对象都可以实现动态代理. spring 在进行 aop 的时候, 默认使用 jdk 的动态代理技术, 当发现 jdk 的动态代理技术不好使的情况下, 使用 cglib 动态代理技术, 保证被代理对象能够被正常代理. 如需使用 cglib 动态代理可以再 spring 的配置文件中进行配置.
<aop:config proxy-target-class="true">
三, aop 开发中的相关概念
1,Joinpoint(连接点): 目标对象中, 所有可以增强的方法.
2,Pointcut(切入点): 目标对象中, 已经增强的方法.
3,Advice(通知): 对于目标对象来说, 需要给目标对象增强的方法.
4,Target(目标对象): 被代理对象.
5,Weaving(织入): 将通知应用到切入点的过程.
6,Proxy(代理): 将通知织入到目标对象后, 形成的增强后的对象.
7,Aspect(切面): 切入点和通知的结合.
四, spring 中 aop 的实现方式
分两种方式介绍, 一种是 xml 配置方式, 一种是注解方式.
1,xml 配置方式
(1) 实现 spring 的 aop 需要导入 aop 包, aspect 包, aopalliance 包, weaver 包. 在 spring 教程一中可以找到获取这些包的方法.
(2) 编写需要增加的方法类.
- public class UserDaoImpl{
- public void saveUser(){
- System.out.println("保存用户");
- }
- public void deleteUser(){
- System.out.println("删除用户");
- }
- }
(3) 编写通知, 也就是说想要增加的代码方法.
- public class UserAdvice{
- public void before(){
- System.out.println("前置通知");
- }
- public void afterReturning(){
- System.out.println("后置通知 (不发生异常的情况下调用)");
- }
- public Object around(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("执行前");
- Object proceed = pjp.proceed();
- System.out.println("执行后");
- return proceed;
- }
- public void afterThrowException(){
- System.out.println("发生异常调用");
- }
- public void after(){
- System.out.println("后置通知, 发生异常也会调用");
- }
- }
(4) 在 spring 的配置文件中进行配置
- <bean name = "userDaoImpl" class="com.jichi.aop.UserDaoImpl"></bean>
- <bean name="userAdvice" class="com.jichi.aop.UserAdvice"></bean>
- <aop:config>
- <aop:pointcut expression="execution(* com.jichi.aop..UserDaoImpl.*(..))" id="pc"/>
- <aop:aspect ref="userAdvice">
- <aop:before method="before" pointcut-ref="pc"/>
- <aop:after-returning method="afterReturning" pointcut-ref="pc"/>
- <aop:around method="around" pointcut-ref="pc"/>
- <aop:after-throwing method="afterThrowException" pointcut-ref="pc"/>
- <aop:after method="after" pointcut-ref="pc"/>
- </aop:aspect>
- </aop:config>
(5) 进行单元测试
- @RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration("classpath:applicationContext.xml")
- public class TestAop {
- @Resource
- private UserDaoImpl userDaoImpl;
- @Test
- public void test1(){
- userDaoImpl.saveUser();
- }
- }
结果如下: 织入成功.
2, 注解配置方式
(1) 同第一种方式需要导入包
(2) 编写需要增加的方法类.
- public class UserDaoImpl{
- public void saveUser(){
- System.out.println("保存用户");
- }
- public void deleteUser(){
- System.out.println("删除用户");
- }
- }
(3) 编写通知, 也就是说想要增加的代码方法.
- public class UserAdvice{
- public void before(){
- System.out.println("前置通知");
- }
- public void afterReturning(){
- System.out.println("后置通知 (不发生异常的情况下调用)");
- }
- public Object around(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("执行前");
- Object proceed = pjp.proceed();
- System.out.println("执行后");
- return proceed;
- }
- public void afterThrowException(){
- System.out.println("发生异常调用");
- }
- public void after(){
- System.out.println("后置通知, 发生异常也会调用");
- }
- }
(4) 在 spring 配置文件中进行配置, 并开启注解 aop
- <bean name = "userDaoImpl" class="com.jichi.aop.UserDaoImpl"></bean>
- <bean name="userAdvice" class="com.jichi.aop.UserAdvice"></bean>
- <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
(5) 在通知类上打上 aspect 的注解. 在方法上打上相应注解
- @Aspect
- public class UserAdvice{
- @Before("execution(* com.jichi.aop..UserDaoImpl.*(..))")
- public void before(){
- System.out.println("前置通知");
- }
- @AfterReturning("execution(* com.jichi.aop..UserDaoImpl.*(..))")
- public void afterReturning(){
- System.out.println("后置通知 (不发生异常的情况下调用)");
- }
- @Around("execution(* com.jichi.aop..UserDaoImpl.*(..))")
- public Object around(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("执行前");
- Object proceed = pjp.proceed();
- System.out.println("执行后");
- return proceed;
- }
- @AfterThrowing("execution(* com.jichi.aop..UserDaoImpl.*(..))")
- public void afterThrowException(){
- System.out.println("发生异常调用");
- }
- @After("execution(* com.jichi.aop..UserDaoImpl.*(..))")
- public void after(){
- System.out.println("后置通知, 发生异常也会调用");
- }
- }
优化方式: 每个方法都配置方法抽取, 显得比较臃肿, 可以进行提取, 方法如下
- @Aspect
- public class UserAdvice{
- @Pointcut("execution(* com.jichi.aop..UserDaoImpl.*(..))")
- public void adc(){}
- @Before("UserAdvice.adc()")
- public void before(){
- System.out.println("前置通知");
- }
- @AfterReturning("UserAdvice.adc()")
- public void afterReturning(){
- System.out.println("后置通知 (不发生异常的情况下调用)");
- }
- @Around("UserAdvice.adc()")
- public Object around(ProceedingJoinPoint pjp) throws Throwable{
- System.out.println("执行前");
- Object proceed = pjp.proceed();
- System.out.println("执行后");
- return proceed;
- }
- @AfterThrowing("UserAdvice.adc()")
- public void afterThrowException(){
- System.out.println("发生异常调用");
- }
- @After("UserAdvice.adc()")
- public void after(){
- System.out.println("后置通知, 发生异常也会调用");
- }
- }
来源: https://www.cnblogs.com/jichi/p/10177004.html