[b]AOP的实现原理[/b]
[list]
[*]编译期:切面在编译期织入,不过需要重新定义新的语法关键字并由特殊的编译器编译来实现 AspectJ
[*]类装载器:目标类被装载到Java虚拟机时,有一个特殊的类装载器对目标类的字节码进行增强
[*]运行期:目标对象和切面都是标准的Java类,通过Java虚拟机的动态代理功能或CGLib事项的运行期动态织入
[/list]
术语解释
[list]
[*]Aspect:切面
切面是一个横跨多个核心逻辑的功能,或者称之为系统关注点,例如日志记录,事务处理,安全检查。以前我们在每个方法中调用重复编写这些代码,现在我们将这些散落的代码集中起来,放在一个模块中,这个模块就是切面
[*]Joinpoint:连接点
连接点就是应用程序流程的何处插入切面,(一个方法调用时,访问一个字段时,特定的异常抛出时)
[*]Pointcut:切入点
切入点是一组连接点的集合,比如一个切面要在调用,所有以delete开头的方法时执行,就可以用通配符delete*来表示这一组连接点,我们将其称为切面
[*]Advice:增强
在特定连接点上执行的动作,执行这个动作就相当于对原始对象的功能做了增强
[*]Introduction:引介
为一个已有的Java对象,动态的添加新的接口
[*]Weaving:织入
织入是将切面整合到程序中的过程
[*]Interceptor:拦截器
拦截器是一种实现增强的方式
[*]Target Object:目标对象
真正执行核心逻辑的对象。
[*]Advisor:一个Advice与Pointcut的结合
定义如何将advice织入目标对象
[/list]
常用Advice
1.MethodBeforeAdvice:在一个方法执行前增强
2.AfterReturningAdvice:在一个方法执行后增强
3.ThrowsAdvice:在一个方法执行前后增强
4.MethodInterceptor:在一个方法执行前后增强 可以控制整个方法执行与否,修改返回值,功能最强大。能使用上述三种简单的实现,就不要使用MethodInterceptor
三种Advice执行的位置不冲突,所以先后顺序无关,若加入MethodInterceptor,并且放置在最前面,则出现异常时,不执行invocation.proceed();其他三种Advice将不会被执行
[b]Advisor 控制只在某些方法上执行[/b]
1.NameMatchMethodPointcutAdvisor
2.RegexpMethodPointcutAdvisor
spring aop代理实际上包含了一个有Advisor和Advice组成的拦截链,Advice总是增强所有方法,Advisor同时包含Advice和Pointcut,因此Advisor会通过Pointcut计算是否应该调用Advice对某个方法进行增强。
[b]使用自动代理[/b]
1.自动为多个目标Bean实现Aop代理
2.避免客户端直接访问目标Bean
通过BeanPostProcessor实现,BeanNameAutoProxyCreator DefaultAdvisorAutoProxyCreator AspectJInvocationContextExposingAdvisorAutoProxyCreator AnnotationAwareAspectJAtuoProxyCreator
[b]引介 Introduction[/b]
一种类型特殊的拦截器,不能作用于任何切入点,只能作用于类,而非方法级别,换句话说就是给类动态的添加接口
想让User类具有Mutable接口,通过isReadonly来判断是否可以更改User类的信息
可以看到User对象却是具有了Mutable接口,因为没有ClassCastException。但是我们
ProxyFactoryBean返回的是一个AOP代理对象,因此它可以截获所有方法的调用,并委托给MethodInterceptor处理,MethodInterceptor集成自DelegatingIntroductionInterceptor,DelegatingIntroductionInterceptor实现了MethodInterceptor接口,因此可以实现方法拦截。
DelegatingIntroductionInterceptorde的invoke方法
禁止在运行期改变AOP代理
<property name="frozen" value="true"/>
[b]使用AspectJ实现AOP[/b]
executionde的完整表达式:execution(修饰符? 返回类型 声明类型? 方法名称(参数类型) 异常类型?)
声明Pointcut
上面的Advice中,我们直接把Pointcut的表达式卸载相应的注解中。可以通过@Pointcut注解提取出来单独定义
[list]
[*]编译期:切面在编译期织入,不过需要重新定义新的语法关键字并由特殊的编译器编译来实现 AspectJ
[*]类装载器:目标类被装载到Java虚拟机时,有一个特殊的类装载器对目标类的字节码进行增强
[*]运行期:目标对象和切面都是标准的Java类,通过Java虚拟机的动态代理功能或CGLib事项的运行期动态织入
[/list]
public class AopProxyFactory {
public static Object createProxy(final Object target,
final MethodBeforeAdvice methodBeforeAdvice) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
methodBeforeAdvice.before(method, args, target);
return method.invoke(target, args);
}
});
}
}
public class Main {
public static void main(String[] args) {
BookDao bookDao = new BookDao();
BookServiceImpl target = new BookServiceImpl();
target.setBookDao(bookDao);
MethodBeforeAdvice log = new MethodBeforeAdvice() {
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
System.out.println("call method" + arg0.getName());
}
};
BookService bookService = (BookService) AopProxyFactory.createProxy(target, log);
bookService.query();
}
}
术语解释
[list]
[*]Aspect:切面
切面是一个横跨多个核心逻辑的功能,或者称之为系统关注点,例如日志记录,事务处理,安全检查。以前我们在每个方法中调用重复编写这些代码,现在我们将这些散落的代码集中起来,放在一个模块中,这个模块就是切面
[*]Joinpoint:连接点
连接点就是应用程序流程的何处插入切面,(一个方法调用时,访问一个字段时,特定的异常抛出时)
[*]Pointcut:切入点
切入点是一组连接点的集合,比如一个切面要在调用,所有以delete开头的方法时执行,就可以用通配符delete*来表示这一组连接点,我们将其称为切面
[*]Advice:增强
在特定连接点上执行的动作,执行这个动作就相当于对原始对象的功能做了增强
[*]Introduction:引介
为一个已有的Java对象,动态的添加新的接口
[*]Weaving:织入
织入是将切面整合到程序中的过程
[*]Interceptor:拦截器
拦截器是一种实现增强的方式
[*]Target Object:目标对象
真正执行核心逻辑的对象。
[*]Advisor:一个Advice与Pointcut的结合
定义如何将advice织入目标对象
[/list]
常用Advice
1.MethodBeforeAdvice:在一个方法执行前增强
2.AfterReturningAdvice:在一个方法执行后增强
3.ThrowsAdvice:在一个方法执行前后增强
4.MethodInterceptor:在一个方法执行前后增强 可以控制整个方法执行与否,修改返回值,功能最强大。能使用上述三种简单的实现,就不要使用MethodInterceptor
public interface UserService {
void login(String username, String password);
boolean create(String username, String password);
}
@Service("userServiceTarget")
public class UserServiceImpl implements UserService {
public void login(String username, String password) {
if ("zhang".equals(username) && "zhang".equals(password)) {
} else {
throw new RuntimeException();
}
}
public boolean create(String username, String password) {
return "admin".equals(username) ? false : true;
}
}
@Component
public class LoginMethodBeforeAdvice implements MethodBeforeAdvice {
public void before(Method method, Object[] args, Object target)
throws Throwable {
System.out.println("[LoginMethodBeforeAdvice] User " + args[0] + " try to login....");
}
}
@Component
public class LoginMethodAfterAdvice implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args,
Object target) throws Throwable {
System.out.println("[LoginMethodAfterAdvice] User " + args[0] + " is login...");
}
}
@Component
public class LoginMethodThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Throwable subClass) {
System.out.println("[LoginMethodThrowsAdvice] An exception occur: "
+ subClass.getClass().getSimpleName());
}
}
public class LoginMethodAroundAdvice implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
// 这里实现MethodBeforeAdvice的功能
Object ret = null;
try {
ret = invocation.proceed();
} catch (Exception e) {
// 这里实现ThrowsAdvice的功能
}
// 这里实现AfterReturningAdvice的功能
return ret;
}
}
public class Drive {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService) context.getBean("userService");
try {
userService.login("zhang", "zhang");
Thread.sleep(500);
userService.login("xxxx", "invaild-password");
} catch (RuntimeException e) {
//System.err.println("invalid user...");
}
}
}
输出
[LoginMethodBeforeAdvice] User zhang try to login....
[LoginMethodAfterAdvice] User zhang is login...
[LoginMethodBeforeAdvice] User xxxx try to login....
[LoginMethodThrowsAdvice] An exception occur: RuntimeException
<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="userServiceTarget"/>
<property name="interceptorNames">
<list>
<value>loginMethodBeforeAdvice</value>
<value>loginMethodAfterAdvice</value>
<value>loginMethodThrowsAdvice</value>
</list>
</property>
</bean>
三种Advice执行的位置不冲突,所以先后顺序无关,若加入MethodInterceptor,并且放置在最前面,则出现异常时,不执行invocation.proceed();其他三种Advice将不会被执行
[b]Advisor 控制只在某些方法上执行[/b]
1.NameMatchMethodPointcutAdvisor
2.RegexpMethodPointcutAdvisor
@Component
public class CreateAfterReturningAdvice implements AfterReturningAdvice {
public void afterReturning(Object returnValue, Method method, Object[] args,
Object target) throws Throwable {
System.out.println("[CreateAfterReturningAdvice] create user " + args[0]);
}
}
public class Drive {
public static void main(String[] args) throws InterruptedException {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
UserService userService = (UserService) context.getBean("userService");
try {
userService.create("zhang", "zhang");
userService.login("zhang", "zhang");
Thread.sleep(500);
userService.create("admin", "invaild-password");
userService.login("admin", "invaild-password");
} catch (RuntimeException e) {
//System.err.println("invalid user...");
}
}
}
输出
[CreateAfterReturningAdvice] create user zhang
[LoginMethodAfterAdvice] User zhang is login...
[LoginMethodBeforeAdvice] User zhang try to login....
[LoginMethodAfterAdvice] User zhang is login...
[LoginMethodThrowsAdvice] An exception occur: RuntimeException
<bean id="createAfterReturningAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="createAfterReturningAdvice"/>
<property name="mappedName" value="create"/>
</bean>
<bean id="loginMethodBeforeAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="loginMethodBeforeAdvice"/>
<property name="pattern" value=".*login.*"/>
</bean>
<bean id="userService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="userServiceTarget"/>
<property name="interceptorNames">
<list>
<value>loginMethodBeforeAdvisor</value>
<value>createAfterReturningAdvisor</value>
<!-- loginMethodAfterAdvice loginMethodThrowsAdvice 每个方法执行时都会被调用 -->
<value>loginMethodAfterAdvice</value>
<value>loginMethodThrowsAdvice</value>
</list>
</property>
</bean>
spring aop代理实际上包含了一个有Advisor和Advice组成的拦截链,Advice总是增强所有方法,Advisor同时包含Advice和Pointcut,因此Advisor会通过Pointcut计算是否应该调用Advice对某个方法进行增强。
[b]使用自动代理[/b]
1.自动为多个目标Bean实现Aop代理
2.避免客户端直接访问目标Bean
通过BeanPostProcessor实现,BeanNameAutoProxyCreator DefaultAdvisorAutoProxyCreator AspectJInvocationContextExposingAdvisorAutoProxyCreator AnnotationAwareAspectJAtuoProxyCreator
<!-- BeanNameAutoProxyCreator 所有以Service结尾的Bean -->
<bean id="beanNameAutoProxy"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" ref="*Service"/>
<property name="interceptorNames">
<list>
<value>loginMethodBeforeAdvisor</value>
<value>loginMethodAfterAdvice</value>
<value>loginMethodThrowsAdvice</value>
<value>createAfterReturningAdvisor</value>
</list>
</property>
</bean>
<!-- DefaultAdvisorAutoProxyCreator -->
<bean id="defaultAutoProxy"
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
[b]引介 Introduction[/b]
一种类型特殊的拦截器,不能作用于任何切入点,只能作用于类,而非方法级别,换句话说就是给类动态的添加接口
想让User类具有Mutable接口,通过isReadonly来判断是否可以更改User类的信息
public class User {
private String username;
private String password;
private String email;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
public interface Mutable {
boolean isReadonly();
void setReadonly(boolean isReadOnly);
}
public class MutableInterceptor extends DelegatingIntroductionInterceptor
implements Mutable {
private boolean readonly;
public boolean isReadonly() {
return this.readonly;
}
public void setReadonly(boolean readonly) {
this.readonly = readonly;
}
}
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
User user = (User) context.getBean("user");
System.out.println(user.getEmail());
((Mutable)user).setReadonly(true);
System.out.println(((Mutable)user).isReadonly());
user.setEmail("abc@xyz.com");
System.out.println(user.getEmail());
}
输出
default-email
false
abc@xyz.com
<bean id="user" scope="prototype"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="target"/>
<!-- 对类代理,因此设置为true,同时需要CGlib -->
<property name="proxyTargetClass" value="true"/>
<property name="proxyInterfaces">
<list>
<value>demo.aop.introduction.Mutable</value>
</list>
</property>
<property name="interceptorNames">
<list>
<value>introductionAdvisor</value>
</list>
</property>
</bean>
<bean id="introductionAdvisor" scope="prototype"
class="org.springframework.aop.support.DefaultIntroductionAdvisor">
<constructor-arg>
<bean class="demo.aop.introduction.MutableInterceptor"/>
</constructor-arg>
</bean>
<bean id="target" class="demo.aop.introduction.User">
<property name="username" value="default-username"/>
<property name="password" value="default-password"/>
<property name="email" value="default-email"/>
</bean>
可以看到User对象却是具有了Mutable接口,因为没有ClassCastException。但是我们
ProxyFactoryBean返回的是一个AOP代理对象,因此它可以截获所有方法的调用,并委托给MethodInterceptor处理,MethodInterceptor集成自DelegatingIntroductionInterceptor,DelegatingIntroductionInterceptor实现了MethodInterceptor接口,因此可以实现方法拦截。
DelegatingIntroductionInterceptorde的invoke方法
public Object invoke(MethodInvocation mi) throws Throwable {
// 调用前是否是新的接口的方法?
if (isMethodOnIntroducedInterface(mi)) {
// 调用delegate对象的方法
Object retVal = AopUtils.invokeJoinpointUsingReflection(this.delegate,
mi.getMethod(), mi.getArguments());
if (retVal == this.delegate && mi instanceof ProxyMethodInvocation) {
Object proxy = ((ProxyMethodInvocation) mi).getProxy();
if (mi.getMethod().getReturnType().isInstance(proxy)) {
retVal = proxy;
}
}
return retVal;
}
// 重写invoke方法,会拦截所有方法
// 如果只希望拦截原始对象(User)的方法,就覆写doProceed方法
return doProceed(mi);
}
// 修改后的 MutableInterceptor
public class MutableInterceptor extends DelegatingIntroductionInterceptor implements Mutable {
private boolean readonly;
public boolean isReadonly() {
return this.readonly;
}
public void setReadonly(boolean readonly) {
this.readonly = readonly;
}
@Override
protected Object doProceed(MethodInvocation mi) throws Throwable {
if (readonly && mi.getMethod().getName().startsWith("set")) {
throw new UnsupportedOperationException("Object is set to readonly");
}
return super.doProceed(mi);
}
}
禁止在运行期改变AOP代理
<property name="frozen" value="true"/>
[b]使用AspectJ实现AOP[/b]
// 1 声明Aspect
@Aspect
public class LoggerAspect {
}
// 2 声明Advice
@Before("execution(* demo.aop.impl.UserServiceImpl.login(..)) && args(username, ..)")
public void logBefore(String username){
System.out.println("[logger] User "+ username +" try to login....");
}
@Around("execution(* demo.aop.impl.UserServiceImpl.login(..))")
public Object securityCheck(ProceedingJoinPoint pjp) throws Throwable {
String username = (String) pjp.getArgs()[0];
if ("admin".equals(username)) {
System.out.println("[security check admin is forbidden]");
throw new RuntimeException();
}
return pjp.proceed();
}
// 3 开启AspectJ支持
<aop:aspectj-autoproxy/>
executionde的完整表达式:execution(修饰符? 返回类型 声明类型? 方法名称(参数类型) 异常类型?)
声明Pointcut
上面的Advice中,我们直接把Pointcut的表达式卸载相应的注解中。可以通过@Pointcut注解提取出来单独定义
@Aspect
@Component
public class LoggerAspect {
@Pointcut("execution(* demo.service.impl.UserServiceImpl.login(..))")
public void loginMethod(){}
@Before("loginMethod() && args(username, ..)")
public void logBefore(String username) {
System.out.println("[logger] User " + username + " try to login....");
}
@AfterReturning("loginMethod() && args(username, ..)")
public void logSuccess(String username) {
System.out.println("[logger] User " + username + " has login....");
}
@AfterThrowing(pointcut = "loginMethod()", throwing = "e")
public void logFailure(RuntimeException e) {
System.out.println("[logger] Exception: " + e.getMessage());
}
@Around("loginMethod()")
public Object securityCheck(ProceedingJoinPoint pjp) throws Throwable {
String username = (String) pjp.getArgs()[0];
if ("admin".equals(username)) {
System.out.println("[security check] admin is forbidden");
throw new RuntimeException();
}
return pjp.proceed();
}
}