AOP在Java里是利用反射机制实现(你也可以认为是动态代理,不过动态代理也是反射机制实现的。
问题
有两个流程,取款流程和查询余额流程
两者有一个共同的验证流程:
有没有想过可以把这个验证用户的代码是提取出来,不放到主流程里去呢,这就是AOP的作用了,有了AOP,你写代码时不要把这个验证用户步骤写进去,即完全不考虑验证用户,你写完之后,在另外一个地方,写好验证用户的代码,然后告诉Spring你要把这段代码加到哪几个地方,Spring就会帮你加过去,而不要你自己Copy过去,这里还是两个地方,如果你有多个控制流呢,这个写代码的方法可以大大减少你的时间,不过AOP的目的不是这样,这只是一个“副作用”,真正目的是,你写代码的时候,事先只需考虑主流程,而不用考虑那些不重要的流程,懂C的都知道,良好的风格要求在函数起始处验证参数,如果在C上可以用AOP,就可以先不管校验参数的问题,事后使用AOP就可以隔山打牛的给所有函数一次性加入校验代码,而你只需要写一次校验代码。不知道C的没关系,举一个通用的例子,经常在debug的时候要打log吧,你也可以写好主要代码之后,把打log的代码写到另一个单独的地方,然后命令AOP把你的代码加过去,注意AOP不会把代码加到源文件里,但是它会正确的影响最终的机器代码。
术语解释
切面(Aspect):就是具有共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通java类,之所以能被AOP容器识别成切面,是在配置中实现的。
通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。
连接点(Joincut):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。
切入点(Poincut):用于定义通知应该切入到哪些连接点上,不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。
目标对象(Target):就是哪些即将切入切面的对象,也就是那些被通知的对象,这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的公共功能代码等待AOP容器的切入。
代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。
织入(Weaving):将切面应用到目标对象从而创建一个新代理对象的过程。这个过程可以在编译器、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。
不使用代理
public class Account {
private static float MONEY=500.00f;
/**
* 显示余额
* */
public void showAccount(){
//验证用户
Authentication.auth();
System.out.println("您的余额:"+MONEY);
}
/**
* 取款
* */
public void withdrawCash(float count){
//验证用户
Authentication.auth();
if(count>MONEY)
System.out.println("余额不足!");
else{
MONEY=MONEY-count;
System.out.println("您已成功取款:"+count);
}
}
}
public class Authentication{
/**
* 验证用户
* */
public static void auth(){
System.out.println("验证成功,请继续操作......");
}
}
public class Client {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
Account account = new Account();
boolean exist = false;
while (!exist) {
System.out.println("查询余额请按1");
System.out.println("取款请按2");
System.out.println("退出请按0");
int flag = input.nextInt();
switch (flag) {
case 1:
account.showAccount();
break;
case 2:
System.out.println("请输入取款金额:");
float c = input.nextFloat();
account.withdrawCash(c);
break;
default:
System.exit(0);
break;
}
System.out.println("=========================");
}
}
}
使用代理
静态代理(代理模式)
/**
* 抽象主题角色:声明了真实主题和代理主题的共同接口
*/
public interface CommonAccount {
public void showAccount();
public void withdrawCash(float count);
}
/**
* 真实主题角色:定义真实的对象
* */
public class Account implements CommonAccount{
private static float MONEY=500.00f;
/**
* 显示余额
* */
public void showAccount(){
System.out.println("您的余额:"+MONEY);
}
/**
* 取款
* */
public void withdrawCash(float count){
if(count>MONEY)
System.out.println("余额不足!");
else{
MONEY=MONEY-count;
System.out.println("您已成功取款:"+count);
}
}
}
public class Authentication{
/**
* 验证用户
* */
public static void auth(){
System.out.println("验证成功,请继续操作......");
}
}
/**
* 代理主题角色:内部包含对真实主题的引用,并且提供和真实主题相同的接口
*/
public class ProxyAccount implements CommonAccount {
private CommonAccount commonAccount;
public ProxyAccount(CommonAccount commonAccount) {
this.setCommonAccount(commonAccount);
}
public void showAccount() {
// 验证用户
Authentication.auth();
commonAccount.showAccount();
}
public void withdrawCash(float count) {
// 验证用户
Authentication.auth();
commonAccount.withdrawCash(count);
}
public CommonAccount getCommonAccount() {
return commonAccount;
}
public void setCommonAccount(CommonAccount commonAccount) {
this.commonAccount = commonAccount;
}
}
import java.util.Scanner;
public class Client {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
Account account = new Account();
CommonAccount commonAccount=new ProxyAccount(account);
boolean exist = false;
while (!exist) {
System.out.println("查询余额请按1");
System.out.println("取款请按2");
System.out.println("退出请按0");
int flag = input.nextInt();
switch (flag) {
case 1:
commonAccount.showAccount();
break;
case 2:
System.out.println("请输入取款金额:");
float c = input.nextFloat();
commonAccount.withdrawCash(c);
break;
default:
System.exit(0);
break;
}
System.out.println("=========================");
}
}
}
总结:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.
JDK动态代理
/**
* 抽象主题角色:声明了真实主题和代理主题的共同接口
*/
public interface CommonAccount {
public void showAccount();
public void withdrawCash(float count);
}
/**
* 真实主题角色:定义真实的对象
* */
public class Account implements CommonAccount{
private static float MONEY=500.00f;
/**
* 显示余额
* */
public void showAccount(){
System.out.println("您的余额:"+MONEY);
}
/**
* 取款
* */
public void withdrawCash(float count){
if(count>MONEY)
System.out.println("余额不足!");
else{
MONEY=MONEY-count;
System.out.println("您已成功取款:"+count);
}
}
}
public class Authentication{
/**
* 验证用户
* */
public static void auth(){
System.out.println("验证成功,请继续操作......");
}
}
/**
* 动态代理
*/
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler {
private Object target;
/**
* 写法固定:AOP专用,绑定委托对象并返回一个代理类
* */
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
/**
* @param Object
* proxy:指被代理的对象
* @param Method
* methos:要调用的方法
* @param Object
* [] args:方法调用时所需要的参数
* */
public Object invoke(Object proxy,
Method method,
Object[] args)
throws Throwable {
Object result=null;
//切面之前执行
System.out.println("切面之前执行");
// 验证用户
Authentication.auth();
// 执行业务
result=method.invoke(target, args);
//切面之后执行
System.out.println("切面之后执行");
return result;
}
}
import java.util.Scanner;
import org.dynamic.proxy.DynamicProxy;
public class Client {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
Account account = new Account();
CommonAccount commonAccount = (CommonAccount) new DynamicProxy().bind(account);
boolean exist = false;
while (!exist) {
System.out.println("查询余额请按1");
System.out.println("取款请按2");
System.out.println("退出请按0");
int flag = input.nextInt();
switch (flag) {
case 1:
commonAccount.showAccount();
break;
case 2:
System.out.println("请输入取款金额:");
float c = input.nextFloat();
commonAccount.withdrawCash(c);
break;
default:
System.exit(0);
break;
}
System.out.println("=========================");
}
}
}
总结:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理
Cglib动态代理
public class Account{
private static float MONEY=500.00f;
/**
* 显示余额
* */
public void showAccount(){
System.out.println("您的余额:"+MONEY);
}
/**
* 取款
* */
public void withdrawCash(float count){
if(count>MONEY)
System.out.println("余额不足!");
else{
MONEY=MONEY-count;
System.out.println("您已成功取款:"+count);
}
}
}
public class Authentication{
/**
* 验证用户
* */
public static void auth(){
System.out.println("验证成功,请继续操作......");
}
}
import java.lang.reflect.Method;
import org.noaop.Authentication;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Object target;
/**
* 创建代理对象
* */
public Object getInstance(Object target){
this.target=target;
//工具类
Enhancer enhancer=new Enhancer();
//设置代理对象的父类
enhancer.setSuperclass(this.target.getClass());
//回调方法
enhancer.setCallback(this);
//创建代理对象
return enhancer.create();
}
public Object intercept(Object proxy,
Method method,
Object[] args,
MethodProxy methodProxy)
throws Throwable {
Object result=null;
System.out.println("事务开始");
//验证用户
Authentication.auth();
result=methodProxy.invoke(target, args);
System.out.println("事务结束");
return result;
}
}
import org.cglib.proxy.CglibProxy;
public class Client {
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
Account account = (Account) new CglibProxy().getInstance(new Account());
boolean exist = false;
while (!exist) {
System.out.println("查询余额请按1");
System.out.println("取款请按2");
System.out.println("退出请按0");
int flag = input.nextInt();
switch (flag) {
case 1:
account.showAccount();
break;
case 2:
System.out.println("请输入取款金额:");
float c = input.nextFloat();
account.withdrawCash(c);
break;
default:
System.exit(0);
break;
}
System.out.println("=========================");
}
}
}
Spring AOP的几种使用方式
实现接口
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 经典的基于代理的AOP
* Before(之前) org.springframework.aop.MethodBeforeAdvice
* After-returning(返回后) org.springframework.aop.AfterReturningAdvice
* After-throwing(抛出后) org.springframework.aop.ThrowsAdvice
* Arround(周围) org.springframework.intercept.MethodInterceptor (相当于将前三个结合起来)
* Introduction(引入) org.springframework.aop.IntroductionInterceptor
* */
public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {
public void afterReturning(Object returnValue,
Method method,
Object[] args,
Object target)
throws Throwable {
System.out.println("起床后,完成新变更的需求");
}
public void before(Method method,
Object[] args,
Object target)
throws Throwable {
System.out.println("吃午饭");
}
}
public interface Sleepable {
void sleep();
}
public class Human implements Sleepable {
public void sleep() {
System.out.println("等老夫吃完午饭就回来睡觉");
}
}
两种配置方式:
第一种:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext appCtx=new ClassPathXmlApplicationContext("classpath:context.xml");
Sleepable sleeper=(Sleepable) appCtx.getBean("human");
sleeper.sleep();
}
}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- 切面 -->
<bean id="sleepHelper" class="org.Springaop.baseonProxy.SleepHelper"></bean>
<!-- 通知 -->
<bean id="sleepHelperAdvisor"
class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="sleepHelper" />
<property name="pattern" value=".*sleep" />
</bean>
<bean id="human" class="org.Springaop.baseonProxy.Human" />
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />
</beans>
第二种:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
public static void main(String[] args) {
ApplicationContext appCtx=new ClassPathXmlApplicationContext("classpath:context.xml");
Sleepable sleeper=(Sleepable) appCtx.getBean("humanProxy");
sleeper.sleep();
}
}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<!-- 切面 -->
<bean id="sleepHelper" class="org.Springaop.baseonProxy.SleepHelper" />
<!-- 切入点:定义了故事发生的地点 -->
<!-- 1.正则表达式 2.AspectJ表达式 -->
<!-- 正则表达式 -->
<bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
<property name="pattern" value=".*sleep" />
</bean>
<!-- 通知 -->
<bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
<property name="advice" ref="sleepHelper" />
<property name="pointcut" ref="sleepPointcut" />
</bean>
<bean id="human" class="org.Springaop.baseonProxy.Human">
</bean>
<bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="human"></property>
<property name="interceptorNames" value="sleepHelperAdvisor"></property>
<property name="proxyInterfaces" value="org.Springaop.baseonProxy.Sleepable" />
</bean>
</beans>
基于AspectJ注解的AOP
package org.Springaop.baseonAnnotation;
public interface Sleepable {
void sleep();
}
public class Test {
public static void main(String[] args) {
ApplicationContext appCtx=new ClassPathXmlApplicationContext("classpath:context-annotation.xml");
Sleepable sleeper=(Sleepable) appCtx.getBean("human");
sleeper.sleep();
}
}
两种拦截方式:
第一种:
public class Human implements Sleepable {
public void sleep() {
System.out.println("等老夫吃完午饭就回来睡觉");
}
}
两种配置方式:
第一种:
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class SleepHelper {
@Pointcut("execution(* *.sleep())")
public void sleeppoint(){}
@Before("sleeppoint()")
public void beforeSleep() {
System.out.println("吃午饭");
}
@AfterReturning("sleeppoint()")
public void afterSleep() {
System.out.println("起床后,完成新变更的需求");
}
}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:component-scan base-package="org.Springaop.baseonAnnotation" />
<aop:aspectj-autoproxy />
<bean id="human" class="org.Springaop.baseonAnnotation.Human" />
</beans>
第二种:
import org.springframework.stereotype.Component;
@Component
public class SleepHelper {
public void beforeSleep() {
System.out.println("吃午饭");
}
public void afterSleep() {
System.out.println("起床后,完成新变更的需求");
}
}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:component-scan base-package="org.Springaop.baseonAnnotation" />
<aop:config>
<aop:aspect ref="sleepHelper">
<aop:before method="beforeSleep" pointcut="execution(* *.sleep())" />
<aop:after method="afterSleep" pointcut="execution(* *.sleep())" />
</aop:aspect>
</aop:config>
<bean id="human" class="org.Springaop.baseonAnnotation.Human" />
</beans>
第二种:
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action {
String name();
}
public class Human implements Sleepable {
@Action(name="注解式拦截")
public void sleep() {
System.out.println("等老夫吃完午饭就回来睡觉");
}
}
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class SleepHelper {
@Pointcut("@annotation(org.Springaop.baseonAnnotation.Action)")
public void sleeppoint(){}
@Before("sleeppoint()")
public void beforeSleep() {
System.out.println("吃午饭");
}
@AfterReturning("sleeppoint()")
public void afterSleep() {
System.out.println("起床后,完成新变更的需求");
}
}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<context:component-scan base-package="org.Springaop.baseonAnnotation" />
<aop:aspectj-autoproxy />
<bean id="human" class="org.Springaop.baseonAnnotation.Human" />
</beans>