1.AOP概念

Aspect Oriented Programming 面向切面编程

AOP概述

●AOP(Aspect-Oriented Programming,面向切面编程):是一种新的方法论,
是对传统 OOP(Object-Oriented Programming,面向对象编程)的补充。
●参见图例和doc文档解释AOP的各个术语!
●Spring的AOP既可以使用xml配置的方式实现,也可以使用注解的方式来实现!

 

2.作用:本质上来说是一种简化代码的方式

继承机制

封装方法

动态代理

……

3.情景举例


①数学计算器接口[MathCalculator]

int add(int i,int j);
int sub(int i,int j);
int mul(int i, int j);
int div(int i,int j

②提供简单实现[EasyImpl]


③在简单实现的基础上让每一个计算方法都能够打印日志[LoginImpl]

④缺陷

[1]手动添加日志繁琐,重复

[2]统一修改不便
[3]对目标方法本来要实现的核心功能有干扰,使程序代码很臃肿,不易于开发维护

⑤使用动态代理实现

[1]创建一个类,让这个类能够提供一个目标对象的代理对象
[2]在代理对象中打印日志

4.在Spring中使用AOP实现日志功能


①Spring中可以使用注解或XML文件配置的方式实现AOP。
②导入jar包

com.springsource.net.sf.cglib -2.2.0.jar
com.springsource.org.aopalliance-1.0.0 .jar
com.springsource.org.aspectj.weaver-1.6.8 .RELEASE.jar
commons-logging-1.1.3. jar
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE. jar
View Code

③开启基于注解的AOP功能
  < aop:aspectj-autoproxy />
④声明一个切面类,并把这个切面类加入到IOC容器中
  @Aspect//表示这是一个切面类
  @Component//加入IOC容器
  public class LogAspect {}

⑤在切面类中声明通知方法
  [1]前置通知:@Before
  [2]返回通知:@AfterReturning
  [3]异常通知:@AfterThrowing
  [4]后置通知:@After
  [5]环绕通知:@Around :环绕通知是前面四个通知的集合体!

@Aspect//表示这是一个切面类
@Component//将本类对象加入到IOC容器中!
public class LogAspect {
@Before(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
public void showBeginLog(){
System.out.println("AOP日志开始");
}
@After(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
public void showReturnLog(){
System.out.println("AOP方法返回");
}
@AfterThrowing(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
public void showExceptionLog(){
System.out.println("AOP方法异常");
}
@AfterReturning(value="execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))")
public void showAfterLog(){
System.out.println("AOP方法结束");
}
}
View Code

⑥被代理的对象也需要加入IOC容器

5.切入点表达式

1)上述案例通过junit测试,会发现,我们调用目标类的四个方法只有add方法被加入了4个通知,如果想所有的方法都加上这些通知,可以
在切入点表达式处,将execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int)) 换成:
execution(public int com.neuedu.aop.target.MathCalculatorImpl.*(int, int))这样只要是有两个参数,且
参数类型为int的方法在执行的时候都会执行其相应的通知方法!

2)切入点表达式的语法格式 
  execution([权限修饰符] [返回值类型] [简单类名/全类名] [方法名]([参数列表]))

最详细的切入点表达式:
  execution(public int com.neuedu.aop.target.MathCalculatorImpl.add(int, int))
最模糊的切入点表达式:
  execution (* *.*(..))

6.统一声明切入点表达式


@Pointcut(value= "execution(public int com.atguigu.aop.target.EazyImpl.add(int,int))")
public void myPointCut(){}

7.通知方法的细节

①在通知中获取目标方法的方法名和参数列表
  [1]在通知方法中声明一个JoinPoint类型的形参
  [2]调用JoinPoint对象的getSignature()方法获取目标方法的签名
  [3]调用JoinPoint对象的getArgs()方法获取目标方法的实际参数列表

②在返回通知中获取方法的返回值
  [1]在@AfterReturning注解中添加returning属性
    @AfterReturning (value="myPointCut()", returning= "result")
  [2]在返回通知的通知方法中声明一个形参,形参名和returning属性的值一致
    showReturnLog(JoinPoint joinPoint, Object result)
③在异常通知中获取异常对象
  [1]在@ AfterThrowing注解中添加throwing属性
  @AfterThrowing (value="myPointCut()",throwing= "throwable" )
[2]在异常通知的通知方法中声明一个形参,形参名和throwing属性值一致
  showExceptinLog(JoinPoint joinPoint, Throwable throwable)

   根据接口类型获取target对象时,实际上真正放在IOC容器中的对象是代理对象,而并不是目标对象本身!

八、环绕通知:@Around

  1.环绕通知需要在方法的参数中指定JoinPoint的子接口类型ProceedingJoinPoint为参数      

 

 @Around(value="pointCut()")
        public void around(ProceedingJoinPoint joinPoint){
        }
View Code

    2.环绕通知会将其他4个通知能干的,自己都给干了!

          注意:@Around修饰的方法一定要将方法的返回值返回!本身相当于代理!
        
     

  @Around(value="pointCut()")
        public Object around(ProceedingJoinPoint joinPoint){
            Object[] args = joinPoint.getArgs();
            Signature signature = joinPoint.getSignature();
            String methodName = signature.getName();
            List<Object> list = Arrays.asList(args);
            Object result = null;
            try {
                //目标方法之前要执行的操作
                System.out.println("[环绕日志]"+methodName+"开始了,参数为:"+list);
                //调用目标方法
                result = joinPoint.proceed(args);
                
                //目标方法正常执行之后的操作
                System.out.println("[环绕日志]"+methodName+"返回了,返回值为:"+result);
            } catch (Throwable e) {
                //目标方法抛出异常信息之后的操作
                System.out.println("[环绕日志]"+methodName+"出异常了,异常对象为:"+e);
                throw new RuntimeException(e.getMessage());
            }finally{
                //方法最终结束时执行的操作!
                System.out.println("[环绕日志]"+methodName+"结束了!");
            }
            return result;
        }
View Code

 

九、切面的优先级

   对于同一个代理对象,可j以同时有多个切面共同对它进行代理。
   可以在切面类上通过@Order (value=50)注解来进行设置,值越小优先级越高!

@Aspect
@Component
@Order(value=40)
public class TxAspect {
@Around(value="execution(public * com.neuedu.aop.target.MathCalculatorImpl.*(..))")
public Object around(ProceedingJoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
String methodName = signature.getName();
List<Object> list = Arrays.asList(args);
Object result = null;
try {
//目标方法之前要执行的操作
System.out.println("[事务日志]"+methodName+"开始了,参数为:"+list);
//调用目标方法
result = joinPoint.proceed(args);

//目标方法正常执行之后的操作
System.out.println("[事务日志]"+methodName+"返回了,返回值为:"+result);
} catch (Throwable e) {
//目标方法抛出异常信息之后的操作
System.out.println("[事务日志]"+methodName+"出异常了,异常对象为:"+e);
throw new RuntimeException(e.getMessage());
}finally{
//方法最终结束时执行的操作!
System.out.println("[事务日志]"+methodName+"结束了!");
}
return result;
}
}
View Code

注意:上面的AOP都是通过注解实现的,AOP实际上也可以通过xml配置的方式实现!

事务的管理是和AOP是有很大关系的,即声明式事务的底层是用事务实现的!

<!-- 1.将需要加载到IOC容器中的bean配置好 -->
<bean id="logAspect" class="com.neuedu.aop.proxy.LogAspect"></bean>
<bean id="txAspect" class="com.neuedu.aop.target.TxAspect"></bean>
<bean id="calculator" class="com.neuedu.aop.target.MathCalculatorImpl"></bean>

<!-- 2.配置AOP,需要导入AOP名称空间 -->
<aop:config>
<!-- 声明切入点表达式 -->
<aop:pointcut expression="execution(* com.neuedu.aop.target.MathCalculatorImpl.*(..))" id="myPointCut"/>
<!-- 配置日志切面类,引用前面的类 ,通过order属性控制优先级-->
<aop:aspect ref="logAspect" order="25">
<!-- 通过method属性指定切面类的切面方法,通过pointcut-ref指定切入点表达式 -->
<aop:before method="showBeginLog" pointcut-ref="myPointCut"/>
<aop:after method="showAfterLog" pointcut-ref="myPointCut"/>
<aop:after-throwing method="showExceptionLog" pointcut-ref="myPointCut" throwing="ex"/>
<aop:after-returning method="showReturnLog" pointcut-ref="myPointCut" returning="result"/>
<aop:around method="around" pointcut-ref="myPointCut"/>
</aop:aspect>    
<!-- 配置事务切面类,引用前面的类 -->
<aop:aspect ref="txAspect" order="20">
<aop:around method="around" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
View Code

 十、批处理(batch)

   1、概念

- 批处理指的是一次操作中执行多条SQL语句
- 批处理相比于一次一次执行效率会提高很多

 2、主要步骤

      1)将要执行的SQL语句保存
       2)执行SQL语句

     - Statement和PreparedStatement都支持批处理操作,这里我们只需要掌握PreparedStatement的批处理方式:
  - 方法:
    void addBatch()
      - 将要执行的SQL先保存起来,先不执行
      - 这个方法在设置完所有的占位符之后调用
    int[] executeBatch()
      - 这个方法用来执行SQL语句,这个方法会将批处理中所有SQL语句执行

  - mysql默认批处理是关闭的,所以我们还需要去打开mysql的批处理:

 rewriteBatchedStatements=true
View Code

   我们需要将以上的参数添加到mysql的url地址中
  - 注意:低版本的mysql-jdbc驱动也不支持批处理,一般都是在修改的时候使用批处理,查询的时候不使用!


案例演示:
  1)创建一张新的数据表

CREATE TABLE t_emp(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(50)
)
View Code

  2)反复打开数据库客户端,插入语句【相当于每次获取一个connection连接,执行executeUpdate语句】

INSERT INTO t_emp(NAME) VALUES('张三');
SELECT * FROM t_emp;
View Code

  3)引出批处理--->执行效率高,资源利用率好!

@Test//测试批处理
public void testBatch(){
//向t_emp表中插入10000条数据

//准备两个变量
Connection connection = null;
PreparedStatement ps = null;

try {
//获取数据库连接
connection=JDBCUtil.getConnection();
//准备SQL模板
String sql = "INSERT INTO t_emp(NAME) VALUES(?)";
//获取PrepareStatement
ps = connection.prepareStatement(sql);
//创建一个for循环,来设置占位符
for(int i = 0; i < 10000 ;i++){
//填充占位符
ps.setString(1,"emp"+i);
//添加到批处理方法中,调用无参的,有参的是Statement来调用的!
ps.addBatch();
} 
//获取一个时间戳
long start = System.currentTimeMillis();
//执行批处理
ps.executeBatch();
//获取一个时间戳
long end = System.currentTimeMillis();
System.out.println("共花费了:"+(end-start));
} catch (SQLException e) {
e.printStackTrace();
}
}
View Code