Spring之Spring AOP
2016-07-24
AOP是Aspect Oriented Programming的缩写,意为:面向切面编程,是一种
spring AOP核心技术就是使用了Java 的动态代理技术
有jdk和cglib两种技术。
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。
字面的区别是面向切面编程与面向对象编程。我概括为,区别是AOP是面向动作(V)编程、OOP是面向实体(n)编程。比如我们要封装一个学生类,类里有学生的属性和方法,此时我们需要用OOP编程,但是我们要封装一个日志记录的动作,就需要AOP编程了。
一个不错的例子,有多个访问类需要访问一个封装好的数据类(比如学生类),访问类里有三个方法,涉及到线程,所以访问类有三个方法
数据上锁、数据访问、数据解锁。
我们可以发现几个问题
1.这个数据访问类的主要职责是访问数据,但是因为类中存在数据上锁和数据解锁的方法,当这个访问类需要被重用时,我们发现它只能被重用在需要上锁和解锁的场景,重用范围很窄。
2.这个访问类的主要功能是访问数据、我们当然希望这个类中只有访问数据的相关代码(单一职责)!而上锁解锁这两个方法是多种访问类都涉及到的,是多种访问类访问数据的一个门槛,也可以说是一个切面!so我们完全可以把这部分代码独立出去,当访问类需要访问这个数据时,需要经过这个门槛,这个门槛给这些访问类上锁,这样不就解决了上面两个问题呢!
3.每个访问类都含有上锁和解锁的代码,统一独立出来,也精简了代码与类的结构!
对于像只能单一继承的比如JAVA语言,AOP可以克服这个弱点。
在web工程中,AOP的主要应用场景有:
Authentication 权限
Caching 缓存
Context passing 内容传递
Error handling 错误处理
Lazy loading 懒加载
Debugging 调试
logging, tracing, profiling and monitoring 记录跟踪 优化 校准
Performance optimization 性能优化
Persistence 持久化
Resource pooling 资源池
Synchronization 同步
Transactions 事务
切面(Aspect):横切的关注点,是切入点和通知的结合,比如日志切面。
连接点(Joinpoint):在哪里切入。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
切入点:(Pointcut):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义,也就是JoinPoint的集合。
通知/增强:(Advice):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知.通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)。
引介:(Introduction) : 引介是一种特殊的通知,在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
目标对象:(Target) : 代理的目标对象。
织入(Weaving):是指把增强应用到目标对象来创建新的代理对象的过程。
spring采用动态代理织入,而AspectJ采用编译期织入和类装在期织入。
Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
Before(前) org.apringframework.aop.MethodBeforeAdvice
After-returning(返回后) org.springframework.aop.AfterReturningAdvice
After-throwing(抛出后) org.springframework.aop.ThrowsAdvice
Arround(周围) org.aopaliance.intercept.MethodInterceptor
Introduction(引入) org.springframework.aop.IntroductionInterceptor
周围通知:他是由AOP Alliance中的接口定义的而非Spring,周围通知相当于前通知、返回后通知、抛出后通知的结合
Spring实现有4种方式
1.经典的基于代理的AOP
2.@AspectJ注解驱动的切面
3.纯POJO切面
4.注入式AspectJ切面
(1) 、编写一个sayHello的接口,并实现:
package com.spring.hello;
public interface HelloSpring {
public void sayHello();
}
package com.spring.hello;
public class HelloSpringImpl implements HelloSpring{
@Override
public void sayHello() {
System.out.println("i am like");
}
}
并在spring配置中注册这个bean:
<bean id="helloBean" class="com.spring.hello.HelloSpringImpl">
</bean>
(2)、在说你好之前和说你好之后我们也会做一些事情,比如这些事情是我们都需要做的,我们便希望把这些共用的事情独立出去,一方面简化代码,另一方面单一职责。所以我们要对sayHello创建通知,并继承通知类型的一些接口:
package com.spring.hello;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
public class HelloHelper implements MethodBeforeAdvice,AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2,
Object arg3) throws Throwable {
System.out.println("说你好之后要干嘛?");
}
@Override
public void before(Method arg0, Object[] arg1, Object arg2)
throws Throwable {
System.out.println("说你好之前放松哦");
}
}
(3)、把这个通知放到spring配置文件的bean里:
<bean id="helloHelper" class="com.spring.hello.HelloHelper"></bean>
在spring配置文件中配置通知:
<bean id="helloHelperAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<property name="advice" ref="helloHelper"/>
<property name="pattern" value=".*sayHello"/>
</bean>
加入如下配置让切点和通知自动匹配(Spring提供的自动代理的功能)
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
(4)、测试:
package com.spring.hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.hello.HelloSpring;
public class TestAOP {
public static void main(String[] args){
ApplicationContext appCtx = new ClassPathXmlApplicationContext("Spring.xml");
HelloSpring sleeper = (HelloSpring)appCtx.getBean("helloBean");
sleeper.sayHello();
}
}
执行结果:
说你好之前放松哦
i am like
说你好之后要干嘛?
百度百科:
AspectJ是一个面向切面的框架,它扩展了Java语言。AspectJ定义了AOP语法所以它有一个专门的编译器用来生成遵守Java字节编码规范的Class文件。
@Pointcut指定注解的切入点
@Before和@AfterReturning指定了通知
(1) 、同样第一步需要编写一个sayHello的接口,并实现:
package com.spring.hello;
public interface HelloSpring {
public void sayHello();
}
package com.spring.hello;
public class HelloSpringImpl implements HelloSpring{
@Override
public void sayHello() {
System.out.println("i am like");
}
}
并在spring配置中注册这个bean:
<bean id="helloBean" class="com.spring.hello.HelloSpringImpl">
</bean>
(2)、对sayHello创建通知:
package com.spring.hello;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class HelloHelper {
@Pointcut("execution(* *.sayHello()))")
public void helloPoint()
{
}
@Before("helloPoint()")
public void beforeHello(){
System.out.println("说你好之前放松注解!");
}
@AfterReturning("helloPoint()")
public void afterHello(){
System.out.println("说你好之后的注解!");
}
}
(3)、修改配置文件:
<?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-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<aop:aspectj-autoproxy/>
<bean id="human" class="com.spring.hello.HelloSpringImpl"/>
<bean id="hellohelper" class="com.spring.hello.HelloHelper">
</bean>
</beans>
加入了
xmlns:aop="http://www.springframework.org/schema/aop"
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
加入<aop:aspectj-autoproxy/>
,可以使spring自动扫描被@Aspect标注的切面。
配置文件的bean只需要放入需要被切入的bean和切入到需要被切入的bean中的通知bean。
(4)、测试(同上一种方法)
spring AOP的一些配置元素:
<aop:advisor> 定义一个AOP通知者
<aop:after> 后通知
<aop:after-returning> 返回后通知
<aop:after-throwing> 抛出后通知
<aop:around> 周围通知
<aop:aspect>定义一个切面
<aop:before>前通知
<aop:config>顶级配置元素,类似于<beans>这种东西
<aop:pointcut>定义一个切点
所以我们把上面实现的方式改用aop配置实现:
通知类里的注解全部去掉,spring配置文件里去掉<aop:aspectj-autoproxy/>
加入
<aop:config>
<aop:aspect ref="hellohelper">
<aop:before method="beforeHello" pointcut="execution(* *.sayHello(..))"/>
<aop:after method="afterHello" pointcut="execution(* *.sayHello(..))"/>
</aop:aspect>
</aop:config>
方法2和3中execution语法:
execution(<访问修饰符>?<返回类型><方法名>(<参数>)<异常>)
例如
匹配所有类public方法 execution(public * *(..))
匹配指定包下所有类方法 execution(* cn.itcast.dao.*(..)) 不包含子包
execution(* cn.itcast.dao..*(..)) ..*表示包、子孙包下所有类
匹配指定类所有方法 execution(* cn.itcast.service.UserService.*(..))
匹配实现特定接口所有类方法 execution(* cn.itcast.dao.GenericDAO+.*(..))
匹配所有save开头的方法 execution(* save*(..))