您的位置:首页 > 编程语言 > Java开发

Spring的AOP思想研究和实现

2013-06-12 16:57 309 查看
AOP思想在Spring中使用最多的是事务管理。在没有AOP之前的时候都是使用OOP的思想按照纵向的方向来编程。哪里需要事务,哪里不需要,需要什么样的事务。因此,导致了很多繁琐的操作。现在AOP思想面向切面编程。事务管理就是J2EE应用中一个横切多个对象的横切关注点的例子。

AOP面向切面编程,可用于权限验证,效率检查,事务,异常管理等

事务的处理一般有两种模式:依赖特定事务资源的事务处理与依赖容器的参数化事务管理

针对第二种依赖Spring容器来参数化事务管理。

Spring框架中所提供的AOP支持,是基于动态AOP机制实现的,即通过JDK动态代理模式,在目标对象的方法调用前后插入相应的处理代码。AOP代理可以是基于JDK动态代理,也可以是基于CGLIB代理。Spring默认使用的是基于Java Dynamic Proxy模式实现,这样任何的接口都能被代理。基于Spirng框架的应用程序开发,程序员会有一种自然的倾向性来实现面向接口编程而不是类,业务对象通常也是实现一个或者多个接口,这也是一种良好的编程习惯。Spring也可以基于CGLIB实现AOP代理,这样所代理的是类而不是接口。如果一个业务对象没有实现某一个接口,那么CGLIB将被使用。

总结点就是:AOP基于Java Dynamic Proxy是面向接口

AOP基于CGLIB面向的是类

现在来分析一下JDK动态代理的原理。

JDK proxy invocationHanlder

JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,在并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。

而Proxy为InvocationHandler实现类动态创建一个符合某一接口的代理实例。

User类

package model;

public class User {

privateint id;

privateString name;

publicint getId() {

returnid;

}

publicvoid setId(int id) {

this.id= id;

}

publicString getName() {

returnname;

}

publicvoid setName(String name) {

this.name= name;

}

}

UserDao类

package dao;

import model.User;

public interface UserDao{

public void save(User user);

public void delete(User user);

}

UserDaoImpl类

package DaoImpl;

import model.User;

import dao.UserDao;

public class UserDaoImpl implementsUserDao{

@Override

publicvoid save(User user) {

System.out.println("保存用户");

}

@Override

publicvoid delete(User user) {

//TODO Auto-generated method stub

System.out.println("删除用户");

}

}

LogicMethod类

package util;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import logic.LogicMethod;

public
class
LogHander implements InvocationHandler{
private Objecttarget;//被代理的对象
//通过构造函数设置被代理对象
public LogHander(Object target){
this.target=target;
}

//自定义在Hanlder内部就切入的方法
public
void
beforeMethod(Method m) {

System.out.println(target.getClass().getName()+" "+m.getName()+" start");
}

/**
* invoke方法再反射时调用
*/
@Override
public Object invoke(Object proxy, Method method, Object[]args)
throws Throwable {
LogicMethod.Begin();
beforeMethod(method);
//反射调用目标对象的方法
method.invoke(target, args);
LogicMethod.end();
return
null
;
}

}

LogHander类

package util;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

import logic.LogicMethod;

public
class
LogHander implements InvocationHandler{
private Objecttarget;//被代理的对象
//通过构造函数设置被代理对象
public LogHander(Object target){
this.target=target;
}

//自定义在Hanlder内部就切入的方法
public
void
beforeMethod(Method m) {

System.out.println(target.getClass().getName()+" "+m.getName()+" start");
}

/**
* invoke方法再反射时调用
*/
@Override
public Object invoke(Object proxy, Method method, Object[]args)
throws Throwable {
LogicMethod.Begin();
beforeMethod(method);
//反射调用目标对象的方法
method.invoke(target, args);
LogicMethod.end();
return
null
;
}

}

测试类TestProxy

import java.lang.reflect.Proxy;

import model.User;

import util.LogHander;
import DaoImpl.UserDaoImpl;
import dao.UserDao;

public
class
TestProxy {

public
static void
main(String[] args) {
// TODO Auto-generatedmethod stub
UserDao userDao=new UserDaoImpl();
//创建一个Handerler对象并将Handler对象和被代理对象关联
LogHander logHander=newLogHander(userDao);
/*newProxyInstance参数含义
* 第一个参数:代理的类加载器,必须和被代理的对象是一个类加载器
* 第二个参数含义:代理对象要实现的那些接口
* 第三个参数:指派方法调用的调用处理程序
* */
//编织了目标业务类逻辑和性能监视横切逻辑的handler创建代理类
UserDao userDaoProxy=(UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(), logHander);
userDaoProxy.save(new User());
userDaoProxy.delete(new User());
}

}
运行结果:

自己的逻辑方法
DaoImpl.UserDaoImpl save start
保存用户
结束自己的的逻辑方法
自己的逻辑方法
DaoImpl.UserDaoImpl deletestart
删除用户
结束自己的的逻辑方法

详细解说:

1. Proxy即动态代理类;
2. Static Object newProxyInstance(ClassLoader loader, Class[] interfaces,InvocationHandler h):返回代理类的一个实例,返回后的代理类可以当作被代理类使用;

它有三个参数:

ClassLoaderloader ----指定被代理对象的类加载器

Class[]Interfaces ----指定被代理对象所以事项的接口

InvocationHandlerh ----指定需要调用的InvocationHandler对象

3.invocationHandler解决必须重写invoke方法

invoke方法就是切入逻辑的地方,可以在里面加入个人的逻辑方法

JDK中具体的动态代理类是怎么产生的呢?
1.产生代理类$Proxy0类

执行了Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)

将产生$Proxy0类,它继承Proxy对象,并根据第二个参数,实现了被代理类的所有接口,自然就可以生成接口要实现的所有方法了(这时候会重写hashcode,toString和equals三个方法),但是还没有具体的实现体;

2. 将代理类$Proxy0类加载到JVM中

这时候是根据Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)它的第一个参数----就是被代理类的类加载器,把当前的代理类加载到JVM中

3. 创建代理类$Proxy0类的对象

调用的$Proxy0类的$Proxy0(InvocationHandler)构造函数,生成$Proxy0类的对象

参数就是Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)它的第三个参数就是我们自己实现的InvocationHandler对象,我们知道InvocationHandler对象中组合加入了代理类代理的接口类的实现类;所以,$Proxy0对象调用所有要实现的接口的方法,都会调用InvocationHandler对象的invoke()方法实现;

4. 生成代理类的class byte
动态代理生成的都是二进制class字节码

时序图:

代码优化:

在不影响外部逻辑和不对现有的代码做任何改动的前提下,代理模式是一个不错的选择。但是如果有多个类似的接口,面对每个接口都要实现一个类似的Proxy,实在是一个烦琐无味的苦力过程。Spring的AOP是如何实现面对这么多的代理的呢?

将LogHander类改装成

package util;

importjava.lang.reflect.InvocationHandler;

importjava.lang.reflect.Method;

importjava.lang.reflect.Proxy;

import java.util.List;

import logic.LogicMethod;

public class AOPHandlerimplements InvocationHandler{

private Object target;//被代理的对象

//该方法返回动态代理对象 与之前的写法一样,只是将代码实现搬到这里来了

public Object bind(Object obj){

this.target=obj;

returnProxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),this);

}

//自定义在Hanlder内部就切入的方法

public void beforeMethod(Method m) {

System.out.println(target.getClass().getName()+" "+m.getName()+ " start");

}

/**

* invoke方法再反射时调用

*/

@Override

public Object invoke(Object proxy, Method method, Object[]args)

throws Throwable {

Object result=null;

if (method.getName().startsWith("save")){

LogicMethod.Begin();

beforeMethod(method);

//反射调用目标对象的方法

result=method.invoke(target, args);

LogicMethod.end();

}

return result;

}

}

测试类就可以这样写

import model.User;

import util.AOPHandler;

importDaoImpl.UserDaoImpl;

import dao.UserDao;

/**

* Spring AOP 实现

* @author Administrator

*

*/

public class TestProxy {

public static void main(String[] args) {

// TODO Auto-generated method stub

UserDao userDao=new UserDaoImpl();

//创建一个Handerler对象并将Handler对象和被代理对象关联

AOPHandler aopHandler=new AOPHandler();

UserDao handler=(UserDao) aopHandler.bind(userDao);

handler.save(new User());

handler.delete(new User());

}

}

运行结果:
自己的逻辑方法

DaoImpl.UserDaoImpl save start

保存用户

结束自己的的逻辑方法

对所有的类都适用。解决了用静态Proxy类实现所产生的弊端。

弄懂了原理的东西,现在可以开始如何使用了。SPring 的AoP实现其实比较简单,但是,重要的是还是理解了思想。

AOP术语:

• 切面(Aspect):一个关注点的模块化,这个关注点实现可能另外横切多个对象.切面可以使用基于 XML Schema
的风格或者以 @Aspect
注解( @AspectJ
风格)来实现.

• 连接点(Joinpoint):
• 通知(Advice):
• 切入点(Pointcut):
• 引入(Introduction):
• 目标对象(Target Object):
• AOP代理(AOP Proxy):
通知类型:

Before通知,After通知,After returning
通知,Throws通知,Around通知

通知类型介绍:

— Around通知:包围一个连接点的通知,如方法调用。这是最强大的通知。Aroud通知在方法调用前后完成自定义的行为,它们也会选择继续执行连接点或返回它们自己的返回值或抛出异常来结束执行。使用 @Around 注解来声明

— Before通知:在一个连接点之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。使用@Before 注解来声明

— After returning通知:在连接点正常完成后执行的通知,例如,一个方法正常返回,没有抛出异常。使用 @AfterReturning 注解来声明

— After 通知:在连接点退出后执行的通知,不论正常返回还是抛出异常退出。使用 @After 注解来声明

— Throws通知:在方法抛出异常时执行的通知。使用 @AfterThrowing 注解来声明

了解了概念之后就可以直接上例子了,使用AOP其实不是太大问题!

package com.xiehande.service;

public
interface
StudentService {
public
void
save();
public
void
delete();
public
void
update();
public
void
get();
}

package com.xiehande.service.impl;

import com.xiehande.service.StudentService;

public
class
StudentServiceImpl implements StudentService{

@Override
public
void
save() {
// TODO Auto-generatedmethod stub
System.out.println("调用持久层保存学生信息");
}

@Override
public
void
delete() {
// TODO Auto-generatedmethod stub
System.out.println("调用持久层删除学生信息");
}

@Override
public
void
update() {
// TODO Auto-generatedmethod stub
System.out.println("调用持久层更新学生信息");
}

@Override
public
void
get() {
// TODO Auto-generatedmethod stub
System.out.println("调用持久层获得学生信息");
}

}

package com.xiehande.aop;

import java.util.Date;
import java.util.concurrent.ExecutionException;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component(value =
"logInterceptor")
@Aspect
public
class
LogInterceptor{
/**
* 声明一个切入点
*/
@Pointcut("execution(*com.xiehande.service..*.*(..))")
public
void
anyMethod() {

};

@Before(value =
"anyMethod()")
public
void
before() {
System.out.println("前置通知");
}

/*
* 传参的前置通知
*
*@Before(value="anyMethod()&args(name)") public voidbeforeParam(String
* name){System.out.println("前置通知"+name); }
*/

/*
* @AfterReturning(pointcut="anyMethod()")public void afterReturn(Object
* result){System.out.println("后置通知"+result); }
*/

@After(value =
"anyMethod()")
public
void
after() {
System.out.println("最后后置通知,开发人员的操作被记录下来"
+ "某某在" +
new Date() +
"操作了类");
}

/*
* @AfterThrowing(pointcut="anyMethod()",throwing="e")public void
* afterThrows(Exception e){System.out.println("异常通知:"+e.getMessage()); }
*/
@Around("anyMethod()")
public Object doBasicProfiling(ProceedingJoinPoint pjp)
throws Throwable {
System.out.println("环绕通知开始");
Object object = pjp.proceed();
System.out.println("环绕通知结束");
return object;
}

}

package test;

import org.junit.Test;

importorg.springframework.context.ApplicationContext;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.xiehande.service.StudentService;

public class StudentTest {

@Test

public void test(){

ApplicationContext ctx=newClassPathXmlApplicationContext("beans.xml");

StudentService ss=(StudentService)ctx.getBean("studentService");

ss.save();

ss.delete();

ss.update();

ss.get();

}

}

(AOP可以用注解和可以使用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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"> <!-- 配置实用annotation注解 -->
<context:annotation-config
/>
<!-- 配置扫描 -->
<!-- 开启自动扫描功能,如果类包含 @Component, @Repository, @Service, and @Controller,@Required,

@Autowired, @PostConstruct, @PreDestroy, @Resource,@PersistenceContext and

@PersistenceUnit这些注解,将会被spring容易管理,并且提供相应的注入 -->
<context:component-scan
base-package="com.xiehande"></context:component-scan>
<!-- 配置实用Spring AOP-->
<aop:aspectj-autoproxy
proxy-target-class="false"/>
<!--
<bean id="logInterceptor"class="com.xiehande.aop.LogInterceptor"></bean>
-->
<bean
id="studentService"
class="com.xiehande.service.impl.StudentServiceImpl"></bean>
<!--<aop:config>
<aop:pointcut expression="execution(public *com.xiehande.dao.impl..*.*(..))"
id="userDAOImplPointCut" />
<aop:aspect id="logAspect" ref="logInterceptor">
<aop:pointcutexpression="execution(public * com.xiehande.dao.impl..*.*(..))"

id="userDAOImplPointCut"/>
这个也是可以写到里面的,只是pointcut的范围只能在这个aspect里面
<aop:before method="before"
pointcut
-ref="userDAOImplPointCut"/>
<aop:beforemethod="before"
pointcut
-ref="public *com.xiehande.dao.impl..*.*(..)"/>
</aop:aspect>
</aop:config>

--></beans>

导包

运行结果:

log4j:WARN No appenders could befound for logger(org.springframework.context.support.ClassPathXmlApplicationContext).
log4j:WARN Please initialize thelog4j system properly.
前置通知
环绕通知 开始
调用持久层保存学生信息
最后后置通知,开发人员的操作被记录下来某某在Wed Jun 12 16:36:28 CST 2013操作了类
环绕通知 结束
前置通知
环绕通知 开始
调用持久层删除学生信息
最后后置通知,开发人员的操作被记录下来某某在Wed Jun 12 16:36:28 CST 2013操作了类
环绕通知 结束
前置通知
环绕通知 开始
调用持久层更新学生信息
最后后置通知,开发人员的操作被记录下来某某在Wed Jun 12 16:36:28 CST 2013操作了类
环绕通知 结束
前置通知
环绕通知 开始
调用持久层获得学生信息
最后后置通知,开发人员的操作被记录下来某某在Wed Jun 12 16:36:28 CST 2013操作了类
环绕通知 结束

说明
1.pointcut语法
execution ——for matching method executionjoin points
within —— 指定连接点所在的Java类型。
this ——bean reference (Spring AOP proxy)is an instance of the given type
target —— the target object (application objectbeing proxied) is an instance of the given type
args —— 指定传入到连接点的参数
@target —— the class of the executingobject has an annotation of the given type
@args —— the runtime type of the actualarguments passed have annotations of the given type(s)
@within —— limits matching to join pointswithin types that have the given annotation
@annotation —— the subject of the joinpoint (method being executed in Spring AOP) has the given annotation
组合pointcut表达式
可以使用 &&
、||
、! 组合pointcut表达式。
execution格式
execution(
可见性(可选) ——
使用public、protected、private指定可见性。也可以为*,表示任意可见性
返回值类型(必须) ——
指定返回值类型
声明类型(可选)—— java包
方法名(参数模式)(必须) ——
方法名称,和方法接收的参数
异常模式(可选) ——
指定方法签名中是否存在异常类型
)
在pointcut表达式中可以使用 *
、..
、 +等通配符。
* :表示若干字符(排除 .
在外)
.. :表示若干字符(包括 .
在内)
+ :表示子类,比如Info+
表示Info类及其子类
2.定义Advice
advice在关联的pointcut表达式的方法运行前(before)、运行后(after)、运行前后(around)执行。
Before Advice:
使用@Before定义Before Advice。
参数:value :绑定到Advice的Pointcut表达式
After returningadvice
在匹配的方法执行之后运行该Adivce。使用@AfterReturning进行注释。
参数:value、pointcut:绑定到Advice的Pointcut表达式
returning:String类型,将方法的返回值绑定到Advice的参数名上。
After throwingadvice
在pointcut匹配的方法抛出异常后,执行pointcut关联的Advice。使用@AfterThrowing进行注解。
参数:value、pointcut:绑定到Advice的Pointcut表达式
throwing:String类型,将抛出的异常绑定到Advice的参数上。
After advice
使用After进行注解。
参数:value :绑定到Advice的Pointcut表达式

3.参数
使用JoinPoint
可以在Advice中的第一个参数中定义org.aspectj.lang.JoinPoint类型的参数(在around
Advice中使用ProceedingJoinPoint类型的参数)
java.lang.Object[] getArgs() —— returns the methodarguments
Signature getSignature() —— returns a description of themethod that is being advised
java.lang.Object getTarget() —— returns the targetobject
java.lang.Object getThis() —— returns the proxy object
pointcut中使用args传递参数
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: