-
注解配置AOP
-
注解开发AOP制作步骤
- 在XML格式基础上
- 导入坐标(伴随spring-context坐标导入已经依赖导入完成)
- 开启AOP注解支持
- 配置切面@Aspect
- 定义专用的切入点方法,并配置切入点@Pointcut
- 为通知方法配置通知类型及对应切入点如@Before
-
注解开发AOP注意事项
- 1.切入点最终体现为一个方法,无参无返回值,无实际方法体内容,但不能是抽象方法
- 2.引用切入点时必须使用方法调用名称,方法后面的()不能省略
- 3.切面类中定义的切入点只能在当前类中使用,如果想引用其他类中定义的切入点要使用“类名.方法名()”引用
- 4.可以在通知类型注解后添加参数,实现XML配置中的属性,例如after-returning后的returning属性
-
小记
- 名称:@Aspect
- 类型:注解
- 位置:类定义上方
- 作用:设置当前类为切面类
- 格式:
- @Aspect
- public class AopAdvice{}
- 说明:一个beans标签中可以配置多个aop:config标签
- 名称:@Pointcut
- 类型:注解
- 位置:方法定义上方
- 作用:使用当前方法名作为切入点引用名称
- 格式:
- @Pointcut("execution(* *(..))")
- public void pt(){}
- 说明:被修饰的方法忽略其业务功能,格式设定为无参无返回值的方法,方法体内空实现(非抽象)
- 名称:@Before
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为前置通知
- 格式:
- @Before("pt()")
- public void before(){}
- 特殊参数:
- 无
- 名称:@After
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为后置通知
- 格式:
- @After("pt()")
- public void after(){}
- 特殊参数:
- 无
- 名称:@AfterReturning
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为返回后通知
- 格式:
- @AfterReturning(value="pt()",returning="ret")
- public void afterReturning(Object ret) {}
- 特殊参数:
- returning:设定使用通知方法参数接收返回值的变量名
- 名称:@AfterThrowing
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为异常后通知
- 格式:
- @AfterThrowing(value="pt()",throwing="t")
- public void afterThrowing(Throwable t){}
- 特殊参数:
- throwing:设定使用通知方法参数接收原始方法中抛出的异常对象名
- 名称:@Around
- 类型:注解
- 位置:方法定义上方
- 作用:标注当前方法作为环绕通知
- 格式:
- @Around("pt()")
- public Object around(ProceedingJoinPoint pjp) throws Throwable{Object ret=pjp.proceed();
- return ret;}
- 特殊参数:
- 无
-
实例演示
-
<!--SSS2.开启磁盘扫描-->
<context:component-scan base-package="com.superdemo"/>
<!--SSS1.开启aop注解支持-->
<aop:aspectj-autoproxy/>
<!--<bean id="userService" class="com.superdemo.service.impl.UserServiceImpl"/>-->
<!--2.配置共性功能成为spring控制的资源-->
<!--<bean id="myAdvice" class="com.superdemo.aop.AOPAdvice"/>-->
<!--4.配置AOP-->
<!--<aop:config>
5.配置切入点
<aop:pointcut id="pt" expression="execution(* *..*(..))"/>
6.配置切面(切入点与通知的关系)
<aop:aspect ref="myAdvice">
7.配置具体的切入点对应通知中的哪个操作方法
<aop:before method="before" pointcut-ref="pt"/>
<aop:after method="after" pointcut-ref="pt"/>
<aop:after-returning method="afterReturing" pointcut-ref="pt" returning="ret"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pt" throwing="t"/>
<aop:around method="around" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>-->
-
@Service("userService")
public class UserServiceImpl implements UserService {
public void save(int i){
//0.将共性功能抽取出来
//System.out.println("共性功能");
System.out.println("user service running..."+i);
//int i=1/0;
}
@Override
public int update(int a,int b) {
System.out.println("user service update running...");
return a+b;
}
@Override
public void delete() {
System.out.println("user service delete running...");
int i = 1/0;
}
}
-
public class AOPPointcut {
@Pointcut("execution(* *..*(..))")
public void pt1(){}
}
-
//1.制作通知类,在类中定义一个方法用于完成共性功能
@Component
@Aspect
public class AOPAdvice {
//@Pointcut("execution(* *..*(..))")
//public void pt(){}
@Before("AOPPointcut.pt1()")
public void before(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("前置before..."+args[0]);
}
@After("AOPPointcut.pt1()")
public void after(JoinPoint jp){
Object[] args = jp.getArgs();
System.out.println("后置after..."+args[0]);
}
@AfterReturning(value = "AOPPointcut.pt1()",returning = "ret")
public void afterReturing(Object ret){
System.out.println("返回后afterReturing..."+ret);
}
@AfterThrowing(value = "AOPPointcut.pt1()",throwing = "t")
public void afterThrowing(Throwable t){
System.out.println("抛出异常后afterThrowing..."+t.getMessage());
}
@Around("AOPPointcut.pt1()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前around before...");
//对原始方法的调用
/*Object ret = null;
try {
ret = pjp.proceed();
} catch (Throwable e) {
System.out.println("around...exception..."+e.getMessage());
}*/
Object ret = pjp.proceed();
System.out.println("环绕后around after...");
return ret;
}
}
-
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) ctx.getBean("userService");
//userService.save(666);
int tj = userService.update(666,123);
System.out.println("执行结果...."+tj);
//userService.delete();
}
}
-
注解AOP通知执行顺序控制
- AOP使用XML配置情况下,通知的执行顺序由配置顺序决定
- 在注解情况下由于不存在配置顺序的概念的概念,参照通知所配置的方法名字符串对应的编码值顺序,可以简单理解为字母排序
- 同一个通知类中,相同通知类型以方法名排序为准
- 不同通知类中,以类名排序为准
- 使用@Order注解通过变更bean的加载顺序改变通知的加载顺序
- 企业开发经验
- 通知方法名由3部分组成,分别是前缀、顺序编码、功能描述
- 前缀为固定字符串,例如baidu、Swisse等,无实际意义
- 顺序编码为6位以内的整数,通常3位即可,不足位补0
- 功能描述为该方法对应的实际通知功能,例如exception、strLenCheck
- 控制通知执行顺序使用顺序编码控制,使用时做一定空间预留
- 003使用,006使用,预留001、002、004、005、007、008
- 使用时从中段开始使用,方便后期做前置追加或后置追加
- 最终顺序以运行顺序为准,以测试结果为准,不以设定规则为准
-
AOP注解驱动
- 名称:@EnableAspectJAutoProxy
- 类型:注解
- 位置:Spring注解配置类定义上方
- 作用:设置当前类开启AOP注解驱动的支持,加载AOP注解
- 格式:
- @Configuration
- @ComponentScan("com.superdemo")
- @EnableAspectJAutoProxy
- public class SpringConfig{}
-
实例演示
-
@Configuration
@ComponentScan("com.superdemo")
@EnableAspectJAutoProxy
public class SpringConfig {
}
-
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void testupdate(){
int ret = userService.update(666, 123);
Assert.assertEquals(789,ret);
}
}
-
注解多方整合案例
-
案例介绍
- 对项目进行业务层接口执行监控,测量业务层接口的执行效率
-
案例分析
- 测量接口执行效率:接口方法执行前后获取执行时间,求出执行时长
- System.currentTimeMillis()
- 对项目进行监控:项目中所有接口方法,AOP思想,执行期动态织入代码
- 环绕通知
- proceed()方法执行前后获取系统时间
-
案例制作步骤
- 定义切入点(务必要绑定到接口上,而不是接口实现类上)
- 制作AOP环绕通知,完成测量功能
- 注解配置AOP
- 开启注解驱动支持
-
案例后续思考与设计
- 测量真实性
- 开发测量是隔离性反复执行某个操作,是理想情况,上线测量差异过大
- 上线测量服务器性能略低于单机开发测量
- 上线测量基于缓存的性能查询要优于数据库查询测量
- 上线测量接口的性能与最终对外提供的服务性能差异过大
- 当外部条件发生变化(硬件),需要进行回归测试,例如数据库迁移
- 测量结果展示
- 测量结果无需每一个都展示,需要设定检测阈值
- 阈值设定要根据业务进行区分,一个复杂的查询与简单的查询差异化很大
- 阈值设定需要做独立的配置文件或通过图形工具配置(工具级别的开发)
- 配合图形界面展示测量结果
-
实例演示
- src下domain层
-
public class Account {
private Integer id;
private String name;
private Double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getMoney() {
return money;
}
public void setMoney(Double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
- src下dao层
-
public interface AccountDao {
@Insert("INSERT INTO account(name, money) VALUES (#{name},#{money})")
void save(Account account);
@Delete("DELETE FROM account WHERE id = ${id}")
void delete(Integer id);
@Update("UPDATE account SET name = #{name},money = #{money} WHERE id = #{id}")
void update(Account account);
@Select("SELECT * FROM account")
List<Account> findAll();
@Select("SELECT * FROM account WHERE id = #{id}")
Account findByid(Integer id);
}
- src下service层
-
public interface AccountService {
void save(Account account);
void delete(Integer id);
void update(Account account);
List<Account> findAll();
Account findByid(Integer id);
}
-
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
/*public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}*/
public void save(Account account){
accountDao.save(account);
}
public void delete(Integer id){
accountDao.delete(id);
}
public void update(Account account){
accountDao.update(account);
}
public List<Account> findAll(){
return accountDao.findAll();
}
public Account findByid(Integer id){
return accountDao.findByid(id);
}
}
- src下resources
-
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/dp1?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
jdbc.username=root
jdbc.password=109923
- src下aop
-
@Component
@Aspect
public class RunTimeMonitorAdvice {
//切入点,监控业务层接口
@Pointcut("execution(* com.superdemo.service.*Service.find*(..))")
public void pt(){}
@Around("pt()")
public Object runtimeAround(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
String className = signature.getDeclaringTypeName();
String methodName = signature.getName();
Object ret = null;
long sum = 0L;
for(int i = 0; i < 10000; i++){
long startTime = System.currentTimeMillis();
ret= pjp.proceed();
long endTime = System.currentTimeMillis();
sum += endTime-startTime;
}
System.out.println(className+":"+methodName+":万次运行了:"+sum+"ms");
return ret;
}
}
- src下config层
-
public class JDBCConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(password);
return ds;
}
}
-
public class MybatisConfig {
/*
对应XML格式下:
spring整合mybatis后控制的创建连接用的对象
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.superdemo.domain"/>
</bean>
加载mybatis映射配置的扫描,将其作为spring的bean进行管理
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.superdemo.dao"/>
</bean>*/
@Bean
public SqlSessionFactoryBean getSqlSessionFactoryBean(@Autowired DataSource dataSource){
SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasesPackage("com.superdemo.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
@Bean
public MapperScannerConfigurer getMapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.superdemo.dao");
return msc;
}
}
-
@Configuration
@ComponentScan("com.superdemo")
@PropertySource("classpath:jdbc.properties")
@Import({JDBCConfig.class, MybatisConfig.class})
@EnableAspectJAutoProxy
public class SpringConfig {
}
- test下测试
-
//设定spring专用的类加载器
@RunWith(SpringJUnit4ClassRunner.class)
//设定加载的spring上下文对应的配置
@ContextConfiguration(classes = SpringConfig.class)
public class UserServiceTest {
@Autowired
private AccountService accountService;
@Test
public void testFindById(){
Account ac = accountService.findByid(2);
System.out.println(ac);
}
@Test
public void testFindAll(){
List<Account> list = accountService.findAll();
Assert.assertEquals(3,list.size());
}
}