一、什么是AOP?
Spring AOP是OOP思想的补充和完善,OOP引进的是抽象、封装、继承和多态等的概念,它强调的是一个完整的事务自上而下的概念,对于事务的具体内部实现是不清楚的。OOP允许开发者定义的是纵向的关系,而AOP是定义的是横向的关系。这里就像日志功能,它的功能是横向的分布在所有对象层次中,与对象的核心代码功能是无关的,这样造成了大量代码的重用,不利于模块之间的重用。而AOP是利用横切的技术,能够剖解开封装对象内部,并将影响多个类的共同代码且不影响主要的核心代码,剥离出出来封装成一个独立的模块(切面),减少系统重复代码的调用的,降低模块之间的耦合度,有利于系统的可操作性和可维护性。
AOP将系统软件分为核心业务和周边业务,核心业务具指的是实现某种特定的功能,而周边业务指的是与核心业务无关的、系统的重复调用的代码,来配合核心业务的周边业务,比如日志、权限和事务等。一句就是,AOP作用于将核心关注点和横切关注点分离出来。
二、AOP的基本技术术语和概念
1、横切关注点
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切面(aspect)
类是对物体特征的抽象,切面就是对横切关注点的抽象
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
代理的目标对象
7、织入(weave)
将切面应用到目标对象并导致代理对象创建的过程
8、引入(introduction)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段
三、基于Spring实现AOP
Spring 中的AOP代理是由Spring的IOC容器负责生成和管理的,其依赖关系也是由IOC来管理的,所以AOP代理可以直接使用容器中的bean实列作为目标,这种关系可以由IOC容器的依赖注入提供。
AOP的实现策略:
(1)JAVA SE动态代理:
使用动态代理可以为一个或者多个接口在运行期动态生成实现对象,生成的对象接口的实现方法可以添加增强代码,从而实现AOP。但是只正对接口代理,实现动态代理是通过反射,从而增加了反射的开销。
(2)字节码生成 CGLib动态代理
动态字节码生成技术是指在运行时动态生成指定类的一个子类对象,并覆盖其中特定的方法,覆盖方法时可以增强代码,c从而实现AOP。
(3)定制的类加载器
当需要对类的所有对象都需要增加增强代码,动态代理和字节码生成的本质上都需要动态的构造动态代理对象,即最终呗增强的对象是有AOP宽假生成的。
(4)代码生成
利用工具在已有的代码基础上生成新的代码,其中的可以增加任意横切代码来实现AOP。
(5)语言增强
可以对构造和属相的赋值进行增强
下面举例说明一个简单的实现AOP过程的:(原文: http://www.cnblogs.com/xrq730/p/4919025.html )
package com.maven.study.service;
public interface HelloWorld {
public void printHelloWorld();
public void doPrint();
}
package com.maven.study.service.impl;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import com.maven.study.service.HelloWorld;
@Service
@Component
public class HelloWorldImpl implements HelloWorld{
@Override
public void printHelloWorld() {
System.out.println("say printHelloWorld");
}
@Override
public void doPrint() {
System.out.println("say doPrint");
}
}
package com.maven.study.service;
public class TimeHandle {
public void printTime()
{
System.out.println("CurrentTime = " + System.currentTimeMillis());
}
}
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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<bean id="helloWorldImpl" class="com.maven.study.service.impl.HelloWorldImpl" />
<bean id="timeHandler" class="com.maven.study.service.TimeHandle" />
<aop:config>
<aop:aspect id="time" ref="timeHandler">
<aop:pointcut id="hello" expression="execution(* com.maven.study.service.HelloWorld.*(..))"/>
<aop:before method="printTime" pointcut-ref="hello"/>
<aop:after method="printTime" pointcut-ref="hello"/>
</aop:aspect>
</aop:config>
</beans>
测试:
package com.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.maven.study.service.HelloWorld;
public class TestAop {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:aop.xml");
HelloWorld hw = (HelloWorld) ctx.getBean("helloWorldImpl");
hw.printHelloWorld();
System.out.println("===============");
hw.doPrint();
}
}
结果:
CurrentTime = 1553828546555
say printHelloWorld
CurrentTime = 1553828546555
===============
CurrentTime = 1553828546556
say doPrint
CurrentTime = 1553828546556
这就是增强代码,定义了<aop:pointcut id="hello" expression="execution(* com.maven.study.service.HelloWorld.*(..))"/>为切入点,这里运行代码需要增加项目所需的jar包,我是maven项目,所以依赖加入:
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
四、AOP增强
4.1 编程式增强
接口:
package com.maven.study.service;
public interface SayHello {
public void sayHello();
}
实现:
package com.maven.study.service.impl;
import org.springframework.stereotype.Component;
import com.maven.study.service.SayHello;
public class SayHelloImpl implements SayHello {
@Override
public void sayHello() {
System.out.println("你好,美女!");
}
}
增强代码:
package com.maven.study.service.impl;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;
public class SayHelloAdvice implements MethodBeforeAdvice,AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("成功!");
}
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("鼓足勇气的说:");
}
}
测试:
package com.test;
import org.springframework.aop.framework.ProxyFactory;
import com.maven.study.service.SayHello;
import com.maven.study.service.impl.SayHelloAdvice;
import com.maven.study.service.impl.SayHelloAroundAdvice;
import com.maven.study.service.impl.SayHelloImpl;
public class TestSayHello {
public static void main(String[] args) {
ProxyFactory proxyFactory = new ProxyFactory();//创建代理工厂
proxyFactory.setTarget(new SayHelloImpl());//设置目标对象
/**
* 前置增强和后置增强
*/
proxyFactory.addAdvice(new SayHelloAdvice());//添加增强对象
SayHello sayHello = (SayHello) proxyFactory.getProxy();//从工厂中获取代理,调用代理的方法
sayHello.sayHello();
}
}
结果:
鼓足勇气的说:
你好,美女!
成功!
还有一种方式是环绕增强:
增强代码:
package com.maven.study.service.impl;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class SayHelloAroundAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
before();
Object obj = invocation.proceed();
after();
return obj;
}
private void before(){
System.out.println("鼓足勇气:");
}
private void after(){
System.out.println("成功!!!");
}
}
测试的时候只要将代理兑现添加增强对象换成SayHelloAroundAdvice就可以了,总结上面的实现方法就是通过代码的形式来实现的。
4.2 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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">
<context:component-scan base-package="com.maven.study.service.*"></context:component-scan>
<!-- 声明式增强 -->
<bean id="sayHelloProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="com.maven.study.service.SayHello"></property>
<property name="target" ref="sayHelloImpl"></property>
<property name="interceptorNames" value="sayHelloAroundAdvice"></property>
</bean>
</beans>
在相应的SaHelloImol.java和SayHelloAroundAdvice.java添加相应的注解@Component
测试:
package com.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.maven.study.service.HelloWorld;
import com.maven.study.service.Love;
import com.maven.study.service.SayHello;
import com.maven.study.service.impl.HelloWorldImpl;
import com.maven.study.service.impl.SayHelloImpl;
public class TestBeanAroundAdvice {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
SayHello sayHello = (SayHello) ctx.getBean("sayHelloProxy");
sayHello.sayHello();
}
}
结果:
鼓足勇气:
你好,美女!
成功!!!
注意:xml配值的interfaces和target一定是想对应的,否则会报错,而interceptorNames这个是增强的代码的,当调用业务的是时候会增强。进项强制转换的时候是转换的interfaces。上面的增强是对方法的增强。
4.3引入式增强 Introduction Advice
引入式的增强是对类的增强,如果看完不想让SayHelloImpl.java去实现SayHello.java的接口。这是门可以应如是增强,来实现此功能。
先创建一个增强接口:
package com.maven.study.service;
public interface Love {
public void sayLove();
}
定义一个授权引入增强类,它的意思就是当调用的SayHelloImpl的时候,我们可以转换成增强类,从而调用增强类的方法,使SayHelloImpl不必去实现接口:
package com.maven.study.service.impl;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import org.springframework.stereotype.Component;
import com.maven.study.service.Love;
@Component
public class SayLoveImpl extends DelegatingIntroductionInterceptor implements Love {
/**
*
*/
private static final long serialVersionUID = -1412651594048232141L;
@Override
public void sayLove() {
System.out.println("l love you !!!");
}
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
return super.invoke(mi);
}
}
xml配置:
<!-- 引入式增强 -->
<bean id="sayHelloProxy2" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="com.maven.study.service.Love"></property>
<property name="target" ref="sayHelloImpl"></property>
<property name="interceptorNames" value="sayLoveImpl"></property>
<property name="proxyTargetClass" value="false"></property>
</bean>
测试:
package com.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.maven.study.service.HelloWorld;
import com.maven.study.service.Love;
import com.maven.study.service.SayHello;
import com.maven.study.service.impl.HelloWorldImpl;
import com.maven.study.service.impl.SayHelloImpl;
public class TestBeanAroundAdvice {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
/*SayHelloImpl sayHelloImpl = (SayHelloImpl) ctx.getBean("sayHelloProxy2");
Love love = (Love)sayHelloImpl;
love.sayLove();*/
Love love = (Love) ctx.getBean("sayHelloProxy2");
love.sayLove();
}
}
proxyTargetClass代表是是否地理的接口,默认是false,如果设置成true,说明代理是不是接口,而是target的目标类。
4.4 定义切面类
<!-- 自动代理 -->
<!-- 定义切面 -->
<bean id="sayHelloAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="sayHelloAroundAdvice"></property>
<property name="pattern" value="com.maven.study.service.impl.SayHelloImpl.say.*"></property>
</bean>
<bean id="sayHelloProxy3" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interfaces" value="com.maven.study.service.Love"></property>
<property name="target" ref="sayHelloImpl"></property>
<property name="interceptorNames" value="sayHelloAdvisor"></property>
<property name="proxyTargetClass" value="true"></property>
</bean>
sayHelloAdvisor这是定义的一个切面,作用于com.maven.study.service.impl.SayHelloImpl.say.*,表达的意思就是这个类下以say开头的方法(切点)。
4.5 AOP自动代理
<bean id="sayHelloAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="sayHelloAroundAdvice"></property>
<property name="pattern" value="com.maven.study.service.impl.SayHelloImpl.say.*"></property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
<property name="optimize" value="true"></property>
</bean>
optimize 表示代理生成优化,如果是true,则表示目标类有接口则代理接口,没有则代理类,这样的就可以省略proxyTargetClass的设置。
4.6 根据bean名称来生成自动代理
<bean id="sayHelloAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="sayHelloAroundAdvice"></property>
<property name="pattern" value="com.maven.study.service.impl.SayHelloImpl.say.*"></property>
</bean>
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames" value="*Impl"></property>
<property name="optimize" value="true"></property>
<property name="interceptorNames" value="sayHelloAdvisor"></property>
</bean>
beanNames的意思属性代表只能是以Impl结尾,
测试:
package com.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.maven.study.service.SayHello;
import com.maven.study.service.impl.SayHelloImpl;
public class TestSpringAop {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
SayHello sayHelloImpl = (SayHello) ctx.getBean("helloWorld");
sayHelloImpl.sayHello();
}
}
报错: No bean named 'helloWorld' is defined,这就是beanNames限制作用。
总结:AOP就是核心业务和周边业务的分离,最常见的日志通过AOP实现的。
===========================================================================================
最简单的、直接的方式的来实现了AOP功能,对于多种博客,相结合的,里面的代码敲一遍,肯定有了自己的理解和想法。