面向切面编程 AOP
也就是面向横切面编程, 在软件原有流程中横向织入扩展功能代码, 在不改变原有功能的情况下扩展横切功能.
生活中的横切面功能:
面向切面编程解决的横切问题:
面向切面编程中的关键概念:
- 切面组件
- 通知
- 切入点
- 运行期间织入
1. 切面组件
切面组件本身也是一个被Spring管理的Bean组件, 这个组件用于封装面向切面的功能扩展方法.
在软件中执行切向功能的组件. 是一种"拦截器"组件.
案例:
@Component
@Aspect
public class DemoAspect {
//通知(切入点) 通知+切入点=织入位置
@Before(
"execution(* cn.tedu.note.service.*Service.*(..))")
public void hello(){
System.out.println("Hello World!");
}
}
使用如上功能必须导入 AspectJ 相关的包, Spring提供的AOP功能是利用 AspectJ 的API实现的.
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjlib</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
2. 通知
用于指定发生切向事件的位置点:
通知有如下几种:
- @Before
- @AfterRetuning
- @AfterThrowing
- @After
- @Around
其在方法的切位置关系如下:
try{
// @Before
切入点 方法
// @AfterRetuning
}catch(e){
// @AfterThrowing
}finally{
// @After
}
@Around 通知, 是在方法前后调用的通知:
@Around("bean(userService)")
public Object testAround(
ProceedingJoinPoint target)
throws Throwable{
System.out.println("调用开始");
//执行目标方法
Object obj=target.proceed();
System.out.println("调用结束");
return obj;
}
3. 切入点
切入点是指明 通知发送的位置.
仅仅指定通知是不能精确定位 切面 的织入位置, 还要在通知中指定切入点.
有3种:
3.1:Bean组件切入 (按照Bean对象进行切入)
bean(beanId)
如: bean(userService)
bean(noteService)
bean(*Service)
多个Bean
bean(userService) || bean(noteService)
3.2:类限定名: 包和类进行切入
within(包名.类名)
如:
within(cn.tedu.note.service.UserService)
within(cn.tedu.note.service.*)
within(cn.tedu.note.service.*Service)
within(cn.tedu.note..*Service)
3.3方法限定名: 切入到特定方法
execution(* 包.类.方法(参数))
如:
execution(* cn.tedu.note.service.UserService.login(..))
execution(* cn.tedu.note.service.UserService.find*(..))
execution(* cn.tedu.note.service.*Service.find*(..))
execution(* cn.tedu.note.service.*.find*(..))
execution(* cn.tedu.note..*.find*(..))
execution(* cn.tedu.note.service.*.*(..))
织入位置
通过通知和切入点的组合,才能明确指定切面代码的位置, 也就是:
通知+切入点=织入位置
AOP 的配置
AOP注解必须与Spring配合才能起到作用, Spring的配置如下:
<context:component-scan
base-package="cn.tedu.aop"/>
<!-- 注解驱动 aspectj AOP,
使 @Aspect 和 @Before 等注解起作用 -->
<aop:aspectj-autoproxy />
案例:
@Component
@Aspect
public class DemoAspect {
//通知(切入点) 通知+切入点=织入位置
@Before("execution(* cn.tedu.note.service.*Service.*(..))")
public void hello(){
System.out.println("Hello World!");
}
@Around("bean(userService)")
public Object testAround( ProceedingJoinPoint target)
throws Throwable{
System.out.println("调用开始");
//执行目标方法
Object obj=target.proceed();
System.out.println("调用结束");
return obj;
}
}
测试结果说明: 如上代码在执行时候, 会在所有的业务层方法中织入 hello() 方法, 也就是在所有业务方法之前执行 hello().
AOP 是如何做到的:
- 采用动态代理模式实现.
- 动态代理是利用反射技术实现的.
代理模式:
案例:
@Component("userServiceProxy")
public class UserServiceProxy
implements UserService{
@Autowired
DemoAspect demoAspect;
@Autowired
UserService target;
public User login(String name, String password) throws NameOrPasswordException {
//调用切面组件的方法?
demoAspect.hello();
System.out.println("Hello Proxy");
return target.login(name, password);
}
public User regist(String name, String nick, String password, String confirm) {
System.out.println("Hello Proxy");
return target.regist(name, nick, password, confirm);
}
}
如上类就是一个静态代理类, 代理的业务层UserService的方法.
动态代理技术
- JDK 的反射API提供了动态代理
- JDK的动态代理必须依赖于接口
- CBLIB API 提供了基于类的动态代理
- Spring会自动的使用如上API, 在有接口时候 优先选JDK代理, 如果没有接口,会自动利用CGLIB代理.
Spring的AOP底层就是利用动态代理技术实现的.
提示: 可以查看异常出现时候的方法调用栈, 来了解Spring内部的调用关系:
如:
java.lang.NullPointerException
at cn.tedu.note.service.NoteServiceImpl.createNote(NoteServiceImpl.java:76)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:51)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at com.sun.proxy.$Proxy24.createNote(Unknown Source)
at cn.tedu.note.web.NoteController.addNote(NoteController.java:58)
at cn.tedu.note.web.NoteController$$FastClassBySpringCGLIB$$fb9c5e1b.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:700)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:80)
at cn.tedu.aop.ExceptionAspect.process(ExceptionAspect.java:21)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:621)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:610)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:65)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:633)
at cn.tedu.note.web.NoteController$$EnhancerBySpringCGLIB$$d281c6f4.addNote(<generated>)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:215)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:953)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:855)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:829)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at cn.tedu.note.util.AccessFilter.checkdo(AccessFilter.java:66)
at cn.tedu.note.util.AccessFilter.doFilter(AccessFilter.java:45)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Spring事务处理
关键特性: ACID
- A 原子性
- C 一致性
- I 隔离性
- D 持久性
Oracle 支持ACID
MySQL 有两种数据库引擎:
- MyISAM 引擎 不支持 ACID, 适合数据一致性要求不高,性能好就行
- INNODB 引擎 支持ACID, 适合数据一致性高的场合.
编程式事务控制:
经典代码
try{
开启事务
业务处理.
业务处理.
提交事务
}catch(e){
回滚事务
}finally{
回收资源
}
很明显, 编程式事务处理, 非常适合替换为AOP事务处理:
Spring 事务处理, 是AOP的经典用途. Spring的事务处理底层就是AOP.
使用Spring 事务
使用Spring 事务 并不需要AOP编程, 只需要使用注解即可
配置:
<!-- 添加 注解事务支持 -->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource"
ref="dataSource" />
</bean>
<tx:annotation-driven
transaction-manager="txManager"/>
使用:
@Transactional(readOnly=true,
isolation=Isolation.READ_COMMITTED,
propagation=Propagation.REQUIRED)
public Note loadNote(String noteId) {
if(noteId==null || noteId.trim().isEmpty()){
throw new IllegalArgumentException("noteId不能空");
}
return noteDao.findNoteById(noteId);
}