AOP称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
一 AOP的基本概念
(1)Aspect(切面):通常是一个类,里面可以定义切入点和通知
(2)JointPoint(连接点):程序执行过程中明确的点,一般是方法的调用
(3)Advice(通知):AOP在特定的切入点上执行的增强处理,有before,after,afterReturning,afterThrowing,around
(4)Pointcut(切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
(5)AOP代理:AOP框架创建的对象,代理就是目标对象的加强。Spring中的AOP代理可以使JDK动态代理,也可以是CGLIB代理,前者基于接口,后者基于子类
二 Spring AOP
Spring中的AOP代理还是离不开Spring的IOC容器,代理的生成,管理及其依赖关系都是由IOC容器负责,Spring默认使用JDK动态代理,在需要代理类而不是代理接口的时候,Spring会自动切换为使用CGLIB代理,不过现在的项目都是面向接口编程,所以JDK动态代理相对来说用的还是多一些。
三 基于注解的AOP配置方式
1.启用@AsjectJ支持
在applicationContext.xml中配置下面一句:
<aop:aspectj-autoproxy />
2.通知类型介绍
(1)Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可
(2)AfterReturning:在目标方法正常完成后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值
(3)AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名
来访问目标方法中所抛出的异常对象
(4)After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式
(5)Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务,日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint
3.例子:
(1)Operator.java --> 切面类
@Component
@Aspect
public class Operator {
@Pointcut("execution(* com.aijava.springcode.service..*.*(..))")
public void pointCut(){}
@Before("pointCut()")
public void doBefore(JoinPoint joinPoint){
System.out.println("AOP Before Advice...");
}
@After("pointCut()")
public void doAfter(JoinPoint joinPoint){
System.out.println("AOP After Advice...");
}
@AfterReturning(pointcut="pointCut()",returning="returnVal")
public void afterReturn(JoinPoint joinPoint,Object returnVal){
System.out.println("AOP AfterReturning Advice:" + returnVal);
}
@AfterThrowing(pointcut="pointCut()",throwing="error")
public void afterThrowing(JoinPoint joinPoint,Throwable error){
System.out.println("AOP AfterThrowing Advice..." + error);
System.out.println("AfterThrowing...");
}
@Around("pointCut()")
public void around(ProceedingJoinPoint pjp){
System.out.println("AOP Aronud before...");
try {
pjp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("AOP Aronud after...");
}
}
(2)UserService.java --> 定义一些目标方法
@Service
public class UserService {
public void add(){
System.out.println("UserService add()");
}
public boolean delete(){
System.out.println("UserService delete()");
return true;
}
public void edit(){
System.out.println("UserService edit()");
int i = 5/0;
}
}
(3).applicationContext.xml
<context:component-scan base-package="com.aijava.springcode"/>
<aop:aspectj-autoproxy />
(4).Test.java
public class Test {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
userService.add();
}
}
上面是一个比较简单的测试,基本涵盖了各种增强定义。注意:做环绕通知的时候,调用ProceedingJoinPoint的proceed()方法才会执行目标方法。
4.通知执行的优先级
进入目标方法时,先织入Around,再织入Before,退出目标方法时,先织入Around,再织入AfterReturning,最后才织入After。
注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用!同时使用也没啥意义。
5.切入点的定义和表达式
切入点表达式的定义算是整个AOP中的核心,有一套自己的规范
Spring AOP支持的切入点指示符:
(1)execution:用来匹配执行方法的连接点
A:@Pointcut("execution(* com.aijava.springcode.service..*.*(..))")
第一个*表示匹配任意的方法返回值,..(两个点)表示零个或多个,上面的第一个..表示service包及其子包,第二个*表示所有类,第三个*表示所有方法,第二个..表示
方法的任意参数个数
B:@Pointcut("within(com.aijava.springcode.service.*)")
within限定匹配方法的连接点,上面的就是表示匹配service包下的任意连接点
C:@Pointcut("this(com.aijava.springcode.service.UserService)")
this用来限定AOP代理必须是指定类型的实例,如上,指定了一个特定的实例,就是UserService
D:@Pointcut("bean(userService)")
bean也是非常常用的,bean可以指定IOC容器中的bean的名称
6.基于XML形式的配置方式
开发中如果选用XML配置方式,通常就是POJO+XML来开发AOP,大同小异,无非就是在XML文件中写切入点表达式和通知类型
例子:
(1)Log.java
public class Log {
private Integer id;
//操作名称,方法名
private String operName;
//操作人
private String operator;
//操作参数
private String operParams;
//操作结果 成功/失败
private String operResult;
//结果消息
private String resultMsg;
//操作时间
private Date operTime = new Date();
setter,getter
}
(2).Logger.java
/**
* 日志记录器 (AOP日志通知)
*/
public class Logger {
@Resource
private LogService logService;
public Object record(ProceedingJoinPoint pjp){
Log log = new Log();
try {
log.setOperator("admin");
String mname = pjp.getSignature().getName();
log.setOperName(mname);
//方法参数,本例中是User user
Object[] args = pjp.getArgs();
log.setOperParams(Arrays.toString(args));
//执行目标方法,返回的是目标方法的返回值,本例中 void
Object obj = pjp.proceed();
if(obj != null){
log.setResultMsg(obj.toString());
}else{
log.setResultMsg(null);
}
log.setOperResult("success");
log.setOperTime(new Date());
return obj;
} catch (Throwable e) {
log.setOperResult("failure");
log.setResultMsg(e.getMessage());
} finally{
logService.saveLog(log);
}
return null;
}
}
(3).applicationContext.xml
<aop:config>
<aop:aspect id="loggerAspect" ref="logger">
<aop:around method="record" pointcut="(execution(* com.aijava.distributed.ssh.service..*.add*(..))
or execution(* com.aijava.distributed.ssh.service..*.update*(..))
or execution(* com.aijava.distributed.ssh.service..*.delete*(..)))
and !bean(logService)"/>
</aop:aspect>
</aop:config>
注意切入点表达式,!bean(logService) 做日志通知的时候,不要给日志本身做日志,否则会造成无限循环!
有关更详细的Spring AOP知识,可以查看Spring官方文档第9章Aspect Oriented Programming with Spring
7.JDK动态代理介绍
例子:
(1)UserService.java
public interface UserService {
public void add();
}
(2)UserServiceImpl.java
public class UserServiceImpl implements UserService{
public void add() {
System.out.println("User add()...");
}
}
(3)ProxyUtils.java
public class ProxyUtils implements InvocationHandler{
private Object target;
public ProxyUtils(Object target){
this.target = target;
}
public Object getTarget() {
return target;
}
public void setTarget(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("do sth before...");
method.invoke(target, args);
System.out.println("do sth after...");
return null;
}
}
(4)Test.java
public class Test {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
ProxyUtils proxyUtils = new ProxyUtils(userService);
UserService proxyObject = (UserService) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),UserServiceImpl.class.getInterfaces(), proxyUtils);
proxyObject.add();
}
}
1 public interface IHello {
2 /**
3 * 业务方法
4 * @param str
5 */
6 void sayHello(String str);
7 }
目标类代码:
1 public class Hello implements IHello{
2 @Override
3 public void sayHello(String str) {
4 System.out.println("hello "+str);
5 }
6
7 }
代理类代码,我们给它添加日志记录功能,在方法开始前后执行特定的方法,是不是和AOP特别像呢?
public class ProxyHello implements IHello{
private IHello hello;
public ProxyHello(IHello hello) {
super();
this.hello = hello;
}
@Override
public void sayHello(String str) {
Logger.start();//添加特定的方法
hello.sayHello(str);
Logger.end();
}
}
日志类代码:
1 public class Logger {
2 public static void start(){
3 System.out.println(new Date()+ " say hello start...");
4 }
5
6 public static void end(){
7 System.out.println(new Date()+ " say hello end");
8 }
9 }
测试代码:
1 public class Test {
2 public static void main(String[] args) {
3 IHello hello = new ProxyHello(new Hello());//如果我们需要日志功能,则使用代理类
4 //IHello hello = new Hello();//如果我们不需要日志功能则使用目标类
5 hello.sayHello("明天");
6 }
7 }
这样我们就实现了一个最简单的AOP,但是这样会存在一个问题:如果我们像Hello这样的类很多,那么,我们是不是要去写很多个HelloProxy这样的类呢。其实也是一种很麻烦的事。在jdk1.3以后,jdk跟我们提供了一个API java.lang.reflect.InvocationHandler的类, 这个类可以让我们在JVM调用某个类的方法时动态的为些方法做些什么事。下面我们就来实现动态代理的实现。
动态代理实现主要是实现InvocationHandler,并且将目标对象注入到代理对象中,利用反射机制来执行目标对象的方法。
接口实现与静态代理相同,代理类代码:
1 public class DynaProxyHello implements InvocationHandler{
2
3 private Object target;//目标对象
4 /**
5 * 通过反射来实例化目标对象
6 * @param object
7 * @return
8 */
9 public Object bind(Object object){
10 this.target = object;
11 return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
12 }
13
14 @Override
15 public Object invoke(Object proxy, Method method, Object[] args)
16 throws Throwable {
17 Object result = null;
18 Logger.start();//添加额外的方法
19 //通过反射机制来运行目标对象的方法
20 result = method.invoke(this.target, args);
21 Logger.end();
22 return result;
23 }
24
25 }