更多spring相关的文章可查看
spring学习(一)
spring学习(二) 自动装配的歧义
四、spring AOP
spring AOP(Aspect Oriented Programming)即面向切面的编程,在我们平常的oop(Object Oriented Programming,面向对象编程)中,难免会有一些照顾不到的地方,举个例子,我们的日志功能。在大多数的业务逻辑中,日志是必不可少的,基于oop,我们需要在实现每个对象的核心功能时,加上启用日志的代码。这些代码与我们本身需要实现的业务逻辑并无关联,而且会在多处复用到,因此我们会希望将这些与业务无关,却被多个模块共同调用的逻辑提取并封装起来,这样系统中的重复代码数量会大大减少,功能的逻辑也会更加清晰,易于维护。
1、常用术语
Advice
:通知,切面所要完成的工作。spring切面有5种类型的通知:前置通知(Before),后置通知(After),环绕通知(Around),返回通知(After-returning),异常通知(After-throwing)。Join point
:连接点,应用通知的点,是能够插入切面的一个点,spring只支持方法级别的连接点Poincut
:切点,定义了切面在哪里插入,直观点讲一般体现为一个切入点表达式Aspect
:切面,通知和切点的结合,一般是一个类Introduction
:引入,允许我们向现有的类添加新方法或属性Weaving
:织入,把切面应用到目标对象并创建新的对象代理的过程。
2、使用注解创建简单的切面
在xml文件中启用AspectJ自动代理
<!--启用AspectJ自动代理 -->
<aop:aspectj-autoproxy proxy-target-class="true"/>
注意proxy-target-class="true"
,当该属性为true
时,是基于类的代理,采用cglib,当为false
时,则是基于接口的jdk动态代理,默认值是false
我们可以通过一个简单的类来看明白.
package com.shiqy.controller;
import com.shiqy.config.MyAspectConfig;
import com.shiqy.dao.UserDao;
import com.shiqy.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@Component
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-mybatis.xml")
//@ContextConfiguration(classes = MyAspectConfig.class)
public class MyLogAspectTest {
@Autowired
private UserService userService;
@Autowired
private UserDao userDao;
@Test
public void test() {
System.out.println(userDao.getClass());
System.out.println(userService.getClass());
}
}
当proxy-target-class="false"
时,结果为
class com.sun.proxy.$Proxy25
class com.sun.proxy.$Proxy27
当为true
时,结果为
class com.sun.proxy.$Proxy25
class com.shiqy.service.impl.UserServiceImpl$$EnhancerBySpringCGLIB$$cc3824e3
一个简单的切面
package com.shiqy.service.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyLogAspect {
@Before("execution(* com.shiqy.service.impl.UserServiceImpl.*(..))")
public void beforeMethod(){
System.out.println("记录日志!!!");
}
@After("execution(* com.shiqy.service.impl.UserServiceImpl.*(..))")
public void afterMethod(){
System.out.println("记录成功!!!");
}
}
@AspectJ
注解表明该类是一个切面,@Before
和@After
注解代表通知的类型为前置通知和后置通知,execution()
用于匹配是连接点的执行方法,execution(* com.shiqy.service.impl.UserServiceImpl.*(..))
代表UserServiceImpl
类的所有方法,该切面的作用是当执行指定实现类中的任意方法前后都会发送通知.
在上述代码中,@Before
和@After
所注解的切点表达式冗余了,我们可以使用@PointCut
注解定义可重用的切点
package com.shiqy.service.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class MyLogAspect {
@Pointcut("execution(* com.shiqy.service.impl.UserServiceImpl.*(..))")
public void myMethod(){ }
@Before("myMethod()")
public void beforeMethod(){
System.out.println("记录日志!!!");
}
@After("myMethod()")
public void afterMethod(){
System.out.println("记录成功!!!");
}
}
接下来写测试类
package com.shiqy.controller;
import com.shiqy.config.MyAspectConfig;
import com.shiqy.dao.UserDao;
import com.shiqy.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@Component
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring-mybatis.xml")
//@ContextConfiguration(classes = MyAspectConfig.class)
public class MyLogAspectTest {
@Autowired
private UserService userService;
@Test
public void test(){
long id =1;
System.out.println(userService.selectUser(id).getUsername());
}
}
在spring-mybatis.xml
中,我们需要配置
<context:component-scan base-package="com.shiqy.service"/>
自动扫描得到我们的切面,运行结果为
20:52:26.701 [main] DEBUG com.mchange.v2.cfg.MConfig - The configuration file for resource identifier '/mchange-commons.properties' could not be found. Skipping.
20:52:26.702 [main] DEBUG com.mchange.v2.cfg.MConfig - The configuration file for resource identifier '/mchange-log.properties' could not be found. Skipping.
20:52:26.702 [main] DEBUG com.mchange.v2.cfg.MConfig - The configuration file for resource identifier 'hocon:/reference,/application,/c3p0,/' could not be found. Skipping.
20:52:26.702 [main] DEBUG com.mchange.v2.cfg.MConfig - The configuration file for resource identifier '/c3p0.properties' could not be found. Skipping.
20:52:26.801 [main] INFO com.mchange.v2.c3p0.C3P0Registry - Initializing c3p0-0.9.5.2 [built 08-December-2015 22:06:04 -0800; debug? true; trace: 10]
20:52:26.850 [main] DEBUG com.mchange.v2.c3p0.management.DynamicPooledDataSourceManagerMBean - MBean: com.mchange.v2.c3p0:type=PooledDataSource,identityToken=z8kflt9u1r0kgrq1ycbj82|7fd50002,name=z8kflt9u1r0kgrq1ycbj82|7fd50002 registered.
20:52:26.874 [main] DEBUG com.mchange.v2.c3p0.management.DynamicPooledDataSourceManagerMBean - MBean: com.mchange.v2.c3p0:type=PooledDataSource,identityToken=z8kflt9u1r0kgrq1ycbj82|7fd50002,name=z8kflt9u1r0kgrq1ycbj82|7fd50002 unregistered, in order to be reregistered after update.
20:52:26.874 [main] DEBUG com.mchange.v2.c3p0.management.DynamicPooledDataSourceManagerMBean - MBean: com.mchange.v2.c3p0:type=PooledDataSource,identityToken=z8kflt9u1r0kgrq1ycbj82|7fd50002,name=z8kflt9u1r0kgrq1ycbj82|7fd50002 registered.
记录日志!!!
20:52:27.380 [main] INFO com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 2, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 10000, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> z8kflt9u1r0kgrq1ycbj82|7fd50002, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> z8kflt9u1r0kgrq1ycbj82|7fd50002, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/springmvc?useUnicode=true&characterEncoding=utf8&useSSL=true, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 30, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 10, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
20:52:27.392 [main] DEBUG com.mchange.v2.cfg.MConfig - The configuration file for resource identifier '/mchange-commons.properties' could not be found. Skipping.
20:52:27.392 [main] DEBUG com.mchange.v2.cfg.MConfig - The configuration file for resource identifier '/mchange-log.properties' could not be found. Skipping.
20:52:27.392 [main] DEBUG com.mchange.v2.cfg.MConfig - The configuration file for resource identifier '/c3p0.properties' could not be found. Skipping.
20:52:27.392 [main] DEBUG com.mchange.v2.cfg.MConfig - The configuration file for resource identifier 'hocon:/reference,/application,/c3p0,/' could not be found. Skipping.
20:52:27.393 [main] WARN com.mchange.v2.resourcepool.BasicResourcePool - Bad pool size config, start 3 < min 10. Using 10 as start.
20:52:27.394 [main] DEBUG com.mchange.v2.resourcepool.BasicResourcePool - com.mchange.v2.resourcepool.BasicResourcePool@2b0f373b config: [start -> 10; min -> 10; max -> 30; inc -> 3; num_acq_attempts -> 2; acq_attempt_delay -> 1000; check_idle_resources_delay -> 0; max_resource_age -> 0; max_idle_time -> 0; excess_max_idle_time -> 0; destroy_unreturned_resc_time -> 0; expiration_enforcement_delay -> 0; break_on_acquisition_failure -> false; debug_store_checkout_exceptions -> false; force_synchronous_checkins -> false]
20:52:27.394 [main] DEBUG com.mchange.v2.c3p0.impl.C3P0PooledConnectionPoolManager - Created new pool for auth, username (masked): 'ro******'.
20:52:27.394 [main] DEBUG com.mchange.v2.resourcepool.BasicResourcePool - acquire test -- pool size: 0; target_pool_size: 10; desired target? 1
20:52:27.394 [main] DEBUG com.mchange.v2.resourcepool.BasicResourcePool - awaitAvailable(): [unknown]
记录成功!!!