Spring有两大核心,一个是IOC,一个是AOP。IOC比较好理解,控制反转,也就是将bean的控制权交给Spring来管理,让对象之间解耦。但是对于AOP,只是听老师说功能非常强大,却一直没有领略到其强大之处。却是被通知,切入点,切面,切点表达式这一堆的概念所迷惑。感觉AOP离我们遥遥无期,遥不可及。导致这种情况并不能怪老师,而是想要领略到AOP的强大,是需要大的项目规模来进行支撑的。
AOP的应用场景有很多,比如记录日志,开启事务,异常处理等等。使用AOP结合注解功能更是不得了,比如缓存的使用,权限的控制等等。那么下面我们就来了解了解这些内容。
开启AOP
要使用AOP首先要开启Spring的自动代理配置,配置方法非常简单,只需要在配置类中加上@EnableAspectJAutoProxy注解就可以了。对Spring的Java配置不熟悉的朋友可以参考这篇文章:
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com")
@EnableAspectJAutoProxy // 开启自动代理
public class JavaConfig {
}
案例
我们通过一个例子来演示如何通过AOP来实现对指定方法的动态代理。
1.创建配置类
像上面提到的那样,在核心配置类中,加上@EnableAspectJAutoProxy注解。
package com.hy.spring.test9;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.hy.spring.test9")
@EnableAspectJAutoProxy
public class Config {
}
2.创建服务接口
这个接口是一个普通的接口,定义了一个hello方法,这个方法是后面使用AOP处理的方法,接收两个参数,并且返回一个Result。
package com.hy.spring.test9;
public interface BusinessService {
public Result hello (String first, Param param) ;
}
3.创建服务实现类
这个实现类就是普通的实现类,实现了上面接口中的hello方法。
package com.hy.spring.test9;
import org.springframework.stereotype.Service;
@Service
public class BusinessServiceImpl implements BusinessService {
@Override
public Result hello(String first, Param param) {
// 进行一些无厘头的操作
Result result = new Result();
if(first == null) {
result.setCode("001");
result.setMessage("参数异常!");
return result;
}
first = "处理后的数据:" + first;
result.setMessage(first);
result.setObj(param);
return result;
}
}
参数封装类
package com.hy.spring.test9;
public class Param {
private String name;
private Integer age;
// 省略 getter and setter and toString ...
}
响应封装类
package com.hy.spring.test9;
public class Result {
private String code;
private String message;
private Object obj;
// 省略 getter and setter and toString ...
}
4.创建运行的主类
通过创建一个带有main方法的类,来调用上面接口中定义的hello方法
package com.hy.spring.test9;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
// 初始化Spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
// 获取服务层对象
BusinessService service = context.getBean(BusinessService.class);
// 调用服务方法
Result result = service.hello("hi", new Param());
// 输出响应结果
System.out.println("执行的最终结果为 : " + result);
context.close();
}
}
到这里,Spring的一个简单结构就搭建起来了,运行上面的代码,会调用到Spring管理的接口中的方法,这里没有用到任何的AOP,还是使用了IOC。先执行看下效果,然后使用AOP进行动态代理,执行上面代码的结果如下:
八月 30, 2017 4:39:27 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6e2c634b: startup date [Wed Aug 30 16:39:27 CST 2017]; root of context hierarchy
Result [code=null, message=处理后的数据:hi, obj=com.hy.spring.test9.Param@679b62af]
八月 30, 2017 4:39:28 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6e2c634b: startup date [Wed Aug 30 16:39:27 CST 2017]; root of context hierarchy
5.创建切面
上面做了很多的准备工作,下面才到了AOP的核心,通过AOP对服务方法进行代理,修改方法的执行参数,修改方法的返回值等等,代码如下
package com.hy.spring.test9;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Service;
@Service
@Aspect // 声明为一个切面
public class LogAspect {
/**
* 在方法执行前执行,并且能够拿到方法所传入的参数
*
* @param joinPoint
*/
@Before(value="execution(* * (..))")
public void logBefore (JoinPoint joinPoint) {
System.out.println("方法执行前执行...");
// 获取所有的参数列表
Object []args = joinPoint.getArgs();
printArgs(args);
}
/**
* 在方法执行后执行
*
* @param joinPoint
*/
@After(value = "execution(* * (..))")
public void logAfter (JoinPoint joinPoint) {
System.out.println("方法执行后执行...");
}
/**
* 在方法返回值之后执行,并且能够拿到返回值。
*
* @param joinPoint
* @param obj 所拦截的方法的返回值
*/
@AfterReturning(value = "execution(* * (..))",returning="obj")
public void logAfterReturning (JoinPoint joinPoint,Object obj) {
System.out.println("方法执行后执行,并且方法已经返回返回值...");
System.out.println("方法的返回结果为 : " + obj);
}
/**
* 环绕通知,控制方法执行,可以修改方法的参数,修改方法的返回值,控制方法的执行
*
* @param joinPoint
* @param obj
*/
@Around(value = "execution(* * (..))")
public Object logAround (ProceedingJoinPoint joinPoint) {
// 获取方法的参数
Object []params = joinPoint.getArgs();
printArgs(params);
// 修改方法的参数,将 传入的hi 修改为 hello
params[0] = "hello";
// 控制方法执行
Object obj = null; // 用来接收方法的返回值
try {
obj = joinPoint.proceed(params); // 方法执行,获取到方法返回值
} catch (Throwable e) {
e.printStackTrace(); // 这里处理异常
}
// 修改返回值并返回
Result result = (Result) obj;
result.setMessage("代理处理之后的数据:" + result.getMessage());
return result;
}
/**
* 打印方法的参数
*
* @param params
*/
private void printArgs(Object[] params) {
for(Object obj : params) {
System.out.println("param : " + obj);
}
}
}
执行后的结果如下
八月 30, 2017 5:06:18 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext prepareRefresh
信息: Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@6e2c634b: startup date [Wed Aug 30 17:06:18 CST 2017]; root of context hierarchy
param : hi
param : com.hy.spring.test9.Param@16d04d3d
方法执行前执行...
param : hello
param : com.hy.spring.test9.Param@16d04d3d
方法执行后执行...
方法执行后执行,并且方法已经返回返回值...
方法的返回结果为 : Result [code=null, message=代理处理之后的数据:处理后的数据:hello, obj=com.hy.spring.test9.Param@16d04d3d]
执行的最终结果为 : Result [code=null, message=代理处理之后的数据:处理后的数据:hello, obj=com.hy.spring.test9.Param@16d04d3d]
八月 30, 2017 5:06:19 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@6e2c634b: startup date [Wed Aug 30 17:06:18 CST 2017]; root of context hierarchy
到这里,AOP的核心你就了解了,对于切点表达式一些知识点,可以参考Spring的官方文档,另外通过注解来实现缓存或者记录日志功能,可以参考