代理模式与aspectJ实现AOP
一、问题
1、代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点
2、代码分散:以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块方法(方法)里多次重复相同的日志代码。如果日志需求发生变化,必修修改所有的模块。
二、使用动态代理解决上述问题
代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象,任何对原始对象调用都要通过代理,代理对象决定是否以及何时将方法调用转到原始对象上
原对象
public class AtithmeticCalculatorImpl implements AtithmeticCalculator {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
代理对象
public class ArithmeticCalculatorLoggingProxy {
/**
* 需要代理的对象
*/
private AtithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(AtithmeticCalculator target) {
this.target = target;
}
public AtithmeticCalculator getLoggerPorxy(){
//指定代理对象由哪个类加载器进行加载
ClassLoader loader = target.getClass().getClassLoader();
//代理对象的类型
Class[] interfaces = new Class[]{AtithmeticCalculator.class};
//代理对象需要执行的逻辑,当调用代理对象其中的方法时,执行该代码
InvocationHandler h = new InvocationHandler() {
/**
* 记录日志的方法
* @param proxy 正在返回的那个代理对象,一般情况下,在invoke中不使用该对象
* @param method 正在被调用的方法
* @param args 调用方法时,传入的参数
* @return 一般返回代理对象返回的结果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被调用对象的方法名
String methodName = method.getName();
//记录开始日志
System.out.println("调用"+methodName+"方法,处理"+ Arrays.asList(args));
//执行原对象的方法
Object invoke = method.invoke(target, args);
//记录结束日志
System.out.println("处理结果为:"+invoke);
return invoke;
}
};
return (AtithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
}
}
/**
* 需要代理的对象
*/
private AtithmeticCalculator target;
public ArithmeticCalculatorLoggingProxy(AtithmeticCalculator target) {
this.target = target;
}
public AtithmeticCalculator getLoggerPorxy(){
//指定代理对象由哪个类加载器进行加载
ClassLoader loader = target.getClass().getClassLoader();
//代理对象的类型
Class[] interfaces = new Class[]{AtithmeticCalculator.class};
//代理对象需要执行的逻辑,当调用代理对象其中的方法时,执行该代码
InvocationHandler h = new InvocationHandler() {
/**
* 记录日志的方法
* @param proxy 正在返回的那个代理对象,一般情况下,在invoke中不使用该对象
* @param method 正在被调用的方法
* @param args 调用方法时,传入的参数
* @return 一般返回代理对象返回的结果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//被调用对象的方法名
String methodName = method.getName();
//记录开始日志
System.out.println("调用"+methodName+"方法,处理"+ Arrays.asList(args));
//执行原对象的方法
Object invoke = method.invoke(target, args);
//记录结束日志
System.out.println("处理结果为:"+invoke);
return invoke;
}
};
return (AtithmeticCalculator) Proxy.newProxyInstance(loader,interfaces,h);
}
}
运行Main方法
public class Main {
public static void main(String[] args) {
AtithmeticCalculator target = new AtithmeticCalculatorImpl();
AtithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggerPorxy();
proxy.add(1, 2);
proxy.mul(3, 5);
}
}
public static void main(String[] args) {
AtithmeticCalculator target = new AtithmeticCalculatorImpl();
AtithmeticCalculator proxy = new ArithmeticCalculatorLoggingProxy(target).getLoggerPorxy();
proxy.add(1, 2);
proxy.mul(3, 5);
}
}
运行结果
三、使用AOP解决上述问题
1、是什么是AOP
--AOP(Aspect-Oriented-Programming,面向切面编程):是一种新的方法论,是对传统OOP的补充
--AOP的主要编程对象是切面,而切面模块化横切关注点
--在应用AOP编程时,仍然需要定义公共功能,但可以明确定的定义在个功能在哪里,以书面方式进行应用,并且不必修改收影响的类。这样一来横切关注点就被模块化到特殊的对象(切面)里。
--AOP的好处:
每个视为逻辑位于一个位置,代码不分散,便于维护和升级
业务模块更简洁只包含核心业务代码
2、AOP术语
--切面(Aspect):横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
--通知(Advice):切面必须要完成的工作
--目标(Target):被通知的对象
--代理(Proxy):向目标对象应用通知之后创建的对象
--连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。如ArithmeticCalculator中的add()方法指向前的连接点,执行点为add();方位为该方法执行前的位置
--切点(pointcut):每个类都拥有多个连接点:例如ArithmeticCalculator的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,相当于查询条件。切点的连接点不是一对一的关系,一个切点匹配多个连接点,切点通过org.springframeword.aop.Pointcut接口进行描述,它使用;类和方法作为连接点的查询条件
3.AspectJ
--AspectJ是java社区里最完整最流行的AOP框架
--在Spring2.0以上版本中,可以使用基于AspectJ注解或基于Xml配置的AOP
4、在Spring中启用AspectJ注解支持
--要在Spring应用中视同AspectJ注解,必须在classpath下包含AspectJ类库:aopalliance.jar、aspectj.weaver.jar和spring-aspects.jar
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.1.RELEASE</version>
</dependency>
--将aopSchema添加到<beans>根元素中。
--要在SpringIOC容器中启用AspectJ注解支持,只要在Bean的配置文件中定义一个空的XML元素<aop:aspectj-autoproxy>
--当SpringIOC容器侦测到Bean配置文件中的<aop:aspectj-autoproxy>元素时,会自动为与AspectJ切面匹配的Bean创建代理
4.使用AspectJ实现切面
(1)配置Spring 文件
<?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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置自动扫描路径 -->
<context:component-scan base-package="aop"></context:component-scan>
<!-- 使用aspjectJ注释起作用:自动为匹配的对象生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置自动扫描路径 -->
<context:component-scan base-package="aop"></context:component-scan>
<!-- 使用aspjectJ注释起作用:自动为匹配的对象生成代理对象 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
(2)实现切面类
/**
* 内容摘要:日志切面
*
* 将这个类声明为一个切面
* 1、通过@Component注解把内放入Ioc容器中
* 2、通过@Aspect声明为切面
*/
@Aspect
@Component
public class LoggingAspect {
/**
* 声明该方法是一个前置通知:在目标方法开始之前执行
*/
@Before("execution(public int aop.AtithmeticCalculatorImpl.*(int,int))")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("调用"+joinPoint.getSignature().getName()+"方法,处理"+ Arrays.asList(joinPoint.getArgs()));
}
}
* 内容摘要:日志切面
*
* 将这个类声明为一个切面
* 1、通过@Component注解把内放入Ioc容器中
* 2、通过@Aspect声明为切面
*/
@Aspect
@Component
public class LoggingAspect {
/**
* 声明该方法是一个前置通知:在目标方法开始之前执行
*/
@Before("execution(public int aop.AtithmeticCalculatorImpl.*(int,int))")
public void beforeMethod(JoinPoint joinPoint){
System.out.println("调用"+joinPoint.getSignature().getName()+"方法,处理"+ Arrays.asList(joinPoint.getArgs()));
}
}
(3)编写运行Main方法
public class Main {
public static void main(String[] args) {
//创建IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//从IOC实例中获取Bean的实例
AtithmeticCalculator atithmeticCalculator = (AtithmeticCalculator) ctx.getBean("atithmeticCalculatorImpl");
//使用bean
atithmeticCalculator.add(1, 2);
}
}
public static void main(String[] args) {
//创建IOC容器
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//从IOC实例中获取Bean的实例
AtithmeticCalculator atithmeticCalculator = (AtithmeticCalculator) ctx.getBean("atithmeticCalculatorImpl");
//使用bean
atithmeticCalculator.add(1, 2);
}
}
(4)结果
5.用aspectJ注解声明
--要在 Spring 中声明 AspectJ 切面, 只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后, Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理.
--在 AspectJ 注解中, 切面只是一个带有 @Aspect 注解的 Java 类.
--通知是标注有某种注解的简单的 Java 方法.
--AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行