发布
登录
注册
关于IOC和AOP思想的一些理解
程序员节频道

图灵联邦

恒河沙
关注

图灵联邦

0

评论

0

点赞

IOC

IOC是什么

Inversion of Control,即控制反转,不是什么技术,而是一种设计思想。

IOC能做什么

它能让我们设计出松耦合的程序。

传统javaSE在类内部通过new去创建对象使用,是程序主动去创建对象,这样程序之间耦合严重;有了IOC容器以后,把创建和查找依赖的控制器交给了容器,由容器去进行注入对象。对于spring来说,就是由spring的BeanFactory来创建及注入这些对象。控制反转,即获取依赖的方式反转了。

1600846155100




Spring 依赖注入的几种方式

Setter注入

被注入的属性需要有set方法, Setter注入支持简单类型和引用类型,Setter注入时在bean实例创建完成后执行的。

构造函数注入

构造注入也就是通过构造方法注入依赖,构造函数的参数一般情况下就是依赖项,spring容器会根据bean中指定的构造函数参数来决定调用那个构造函数。

自动装配与注解注入

(1)基于xml的自动装配

除了上述手动注入的情况,Spring还非常智能地为我们提供自动向Bean注入依赖的功能,这个过程一般被称为自动装配(autowiring)。这是一个非常酷的功能,当注入的bean特别多时,它将极大地节省编写注入程序的时间,因此在开发中,非常常见。Spring的自动装配有三种模式:byTpye(根据类型),byName(根据名称)、constructor(根据构造函数)。

在byTpye模式中,Spring容器会基于反射查看bean定义的类,然后找到与依赖类型相同的bean注入到另外的bean中,这个过程需要借助setter注入来完成,因此必须存在set方法,否则注入失败。

(2)基于注解的自动装配(@Autowired&@Resource&@Value)

①基于@Autowired注解的自动装配

在Spring 2.5 中引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 通过 @Autowired的使用标注到成员变量时不需要有set方法。

②基于@Resource注解的自动装配

与@Autowried具备相同功效的还有@Resource,默认按 byName模式 自动注入,由J2EE提供,需导入Package: javax.annotation.Resource,可以标注在成员变量和set方法上,但无法标注构造函数。@Resource有两个中重要的属性:name和type。Spring容器对于@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。因此使用name属性,则按byName模式的自动注入策略,如果使用type属性则按 byType模式自动注入策略。倘若既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入。

③基于@Value注解的自动装配以及properties文件读取

上述两种自动装配的依赖注入并不适合简单值类型,如int、boolean、long、String以及Enum等,对于这些类型,Spring容器也提供了@Value注入的方式,这是非常具备人性化的,可以解决很多硬编码问题。@Value接收一个String的值,该值指定了将要被注入到内置的java类型属性值,放心,不必关系类型转换,大多数情况下Spring容器都会自动处理好的。一般情况下@Value会与properties文件结合使用,也分两种情况一种是SpEL(有点类似于jsp的EL),另外一种是占位符方式。

以流模块是否使用树形加载算子为例

在properties文件配置好

operatorTree=on

在需要的类中使用@Value注入

@Value("${operatorTree}")
private String treeSwithch;








AOP

理解:在AOP中,切面独立于业务,但是又作用于业务。




AOP简介

在软件业,AOP 为 Aspect Oriented Programming 的缩写,意为:面相切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。





AOP 底层采用两种动态代理模式实现:JDK 的动态代理,与 CGLIB 动态代理。根据是否实现接口来选择哪种代理方式。

AOP编程的一些术语

1.切面(Aspect):交叉业务逻辑,例如事务、安全、日志等,称为切面。

2.目标对象(Target):业务类的对象,称为目标对象。

3.织入(Weaving):将切面插入到目标对象的目标方法的过程,称为织入。

4.连接点(JoinPoint):目标对象中可以被切面织入的方法。

5.切入点(Pointcut):目标对象中真正被切面织入的方法。切入点一定是连接点,但连接点不一定是切入点。被标记为 final 的方法是不能作用连接点与切入点的。

6.通知(Advice):通知是切面的一种,可以完成简单的织入功能。通知可以定义切面织入的时间点,切入点定义了切面织入的位置。

7.顾问(Advisor):顾问是切面的一种,能够将通知以更为复杂的方式织入到目标对象中,是将通知包装为更复杂的切面的装配器。

基于spring aop开发的案例

  1. 定义目标类接口和实现类

//接口类
public interface UserDao {
  int addUser();
  void updateUser();
  void deleteUser();
  void findUser();
}

//实现类
import com.zejian.spring.springAop.dao.UserDao;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImp implements UserDao {

  @Override
  public int addUser() {
    System.out.println("add user ......");
    return 6666;
  }

  @Override
  public void updateUser() {
    System.out.println("update user ......");
  }

  @Override
  public void deleteUser() {
    System.out.println("delete user ......");
  }

  @Override
  public void findUser() {
    System.out.println("find user ......");
  }
}
  1. 使用Spring 2.0引入的注解方式,编写Spring AOP的aspect 类:

注:通过@Component注解把此类交给springIoc容器管理。

@Aspect
@Component
public class MyAspect {

  /**
   * 前置通知
   */
  @Before("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
  public void before(){
    System.out.println("前置通知....");
  }

 

  /**
   * 后置通知
   * returnVal,切点方法执行后的返回值
   */
  @AfterReturning(value="execution(*com.zejian.spring.springAop.dao.UserDao.addUser(..))",returning = "returnVal")


  public void AfterReturning(Object returnVal){
    System.out.println("后置通知...."+returnVal);
  }


  /**
   * 环绕通知
   * @param joinPoint 可用于执行切点的类
   * @return
   * @throws Throwable
   */

  @Around("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
  public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("环绕通知前....");
    Object obj= (Object) joinPoint.proceed();
    System.out.println("环绕通知后....");
    return obj;
  }


  /**
   \* 抛出通知
   \* @param e
   */

  @AfterThrowing(value="execution(*com.zejian.spring.springAop.dao.UserDao.addUser(..))",throwing = "e")
  public void afterThrowable(Throwable e){
    System.out.println("出现异常:msg="+e.getMessage());
  }


  /**
   \* 无论什么情况下都会执行的方法
   */
  @After(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")
  public void after(){
    System.out.println("最终通知....");
  }
}
  1. 编写测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= "classpath:spring/spring-aspectj.xml")
public class UserDaoAspectJ {

  @Autowired
  UserDao userDao;

 
  @Test
  public void aspectJTest(){
    userDao.addUser();
  }
}





4. 简单说明一下,定义了一个目标类UserDaoImpl,利用Spring2.0引入的aspect注解开发功能定义aspect类即MyAspect,在该aspect类中,编写了5种注解类型的通知函数,分别是前置通知@Before、后置通知@AfterReturning、环绕通知@Around、异常通知@AfterThrowing、最终通知@After,这5种通知与前面分析AspectJ的通知类型几乎是一样的,并注解通知上使用execution关键字定义的切点表达式,即指明该通知要应用的目标函数,当只有一个execution参数时,value属性可以省略,当含两个以上的参数,value必须注明,如存在返回值时。当然除了把切点表达式直接传递给通知注解类型外,还可以使用@pointcut来定义切点匹配表达式,这个与AspectJ使用关键字pointcut是一样的,后面分析。目标类和aspect类定义完成后,最后使用@Componet注解代替xml配置文件,将类的创建都交由SpringIOC容器处理。




基于注解的Sping AOP开发

在案例中,定义过滤切入点函数时,是直接把execution已定义匹配表达式作为值传递给通知类型的如下:

@After(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))") 
public void after(){ System.out.println("最终通知...."); }





除了上述方式外,还可采用与ApectJ中使用pointcut关键字类似的方式定义切入点表达式如下,使用@Pointcut注解:

/** * 使用Pointcut定义切点 */ 

@Pointcut("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))") 
private void myPointcut(){} 

/** * 应用切入点函数 */ 

@After(value="myPointcut()") 
public void afterDemo()

{ System.out.println("最终通知...."); }

使用@Pointcut注解进行定义,应用到通知函数afterDemo()时直接传递切点表达式的函数名称myPointcut()即可,比较简单,下面接着介绍切点指示符。

  1. 通配符:在定义匹配表达式时,通配符几乎随处可见,如*、.. 、+ ,它们的含义如下:





.. :匹配方法定义中的任意数量的参数,此外还匹配类定义中的任意数量包

例如:

//任意返回值,任意名称,任意参数的公共方法

execution(public * *(..))

//匹配com.zejian.dao包及其子包中所有类中的所有方法

within(com.zejian.dao..*)





+ :匹配给定类的任意子类

//匹配实现了DaoUser接口的所有子类的方法

within(com.zejian.dao.DaoUser+)





* :匹配任意数量的字符

//匹配com.zejian.service包及其子包中所有类的所有方法

within(com.zejian.service..*)

//匹配以set开头,参数为int类型,任意返回值的方法

execution(* set*(int))





2. 类型签名表达式

为了方便类型(如接口、类名、包名)过滤方法,Spring AOP 提供了within关键字。其语法格式如下:

within(<type name>)





例:

//匹配com.zejian.dao包及其子包中所有类中的所有方法
@Pointcut("within(com.zejian.dao..*)")

 
//匹配UserDaoImpl类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl)")
 

//匹配UserDaoImpl类及其子类中所有方法
@Pointcut("within(com.zejian.dao.UserDaoImpl+)")


//匹配所有实现UserDao接口的类的所有方法
@Pointcut("within(com.zejian.dao.UserDao+)")





3. 方法签名表达式

如果想根据方法签名进行过滤,可以使用关键字execution,语法表达式如下:

//scope :方法作用域,如public,private,protect
//returnt-type:方法返回值类型
//fully-qualified-class-name:方法所在类的完全限定名称
//parameters 方法参数
execution(  .*(parameters))





对于给定的作用域、返回值类型、完全限定类名以及参数匹配的方法将会应用切点函数指定的通知,以下是案例:

//匹配UserDaoImpl类中的所有方法
@Pointcut("execution(* com.zejian.dao.UserDaoImpl.*(..))")

 
//匹配UserDaoImpl类中的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(..))")

 
//匹配UserDaoImpl类中的所有公共方法并且返回值为int类型
@Pointcut("execution(public int com.zejian.dao.UserDaoImpl.*(..))")

 
//匹配UserDaoImpl类中第一个参数为int类型的所有公共的方法
@Pointcut("execution(public * com.zejian.dao.UserDaoImpl.*(int , ..))")
  1. 其他指示符

bean:Spring AOP扩展的,AspectJ没有对于指示符,用于匹配特定名称的Bean对象的执行方法;

//匹配名称中带有后缀Service的Bean。
@Pointcut("bean(*Service)")
private void myPointcut1(){}





this :用于匹配当前AOP代理对象类型的执行方法;请注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配

//匹配了任意实现了UserDao接口的代理对象的方法进行过滤
@Pointcut("this(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut2(){}





target :用于匹配当前目标对象类型的执行方法;

//匹配了任意实现了UserDao接口的目标对象的方法进行过滤
@Pointcut("target(com.zejian.spring.springAop.dao.UserDao)")
private void myPointcut3(){}









@within:用于匹配所以持有指定注解类型内的方法;请注意与within是有区别的, within是用于匹配指定类型内的方法执行;

//匹配使用了MarkerAnnotation注解的类(注意是类)
@Pointcut("@within(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut4(){}





@annotation(com.zejian.spring.MarkerMethodAnnotation) : 根据所应用的注解进行方法过滤。

//匹配使用了MarkerAnnotation注解的方法(注意是方法)
@Pointcut("@annotation(com.zejian.spring.annotation.MarkerAnnotation)")
private void myPointcut5(){}












5种通知函数

前置通知@Before

前置通知通过@Before注解进行标注,并可直接传入切点表达式的值,该通知在目标函数执行前执行,注意JoinPoint,是Spring提供的静态变量,通过joinPoint 参数,可以获取目标对象的信息,如类名称,方法参数,方法名称等,,该参数是可选的。

/**

 \* 前置通知

 \* @param joinPoint 该参数可以获取目标对象的信息,如类名称,方法参数,方法名称等

 */

@Before("execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))")

public void before(JoinPoint joinPoint){

  System.out.println("我是前置通知");

}




后置通知@AfterReturning

通过@AfterReturning注解进行标注,该函数在目标函数执行完成后执行,并可以获取到目标函数最终的返回值returnVal,当目标函数没有返回值时,returnVal将返回null,必须通过returning = “returnVal”注明参数的名称而且必须与通知函数的参数名称相同。请注意,在任何通知中这些参数都是可选的,需要使用时直接填写即可,不需要使用时,可以完成不用声明出来。如下

	/**

\* 后置通知,不需要参数时可以不提供

*/

@AfterReturning(value="execution(* com.zejian.spring.springAop.dao.UserDao.*User(..))")

public void AfterReturning(){

  System.out.println("我是后置通知...");

}

 
/**

\* 后置通知

\* returnVal,切点方法执行后的返回值

*/

@AfterReturning(value="execution(* com.zejian.spring.springAop.dao.UserDao.*User(..))",returning = "returnVal")

public void AfterReturning(JoinPoint joinPoint,Object returnVal){

  System.out.println("我是后置通知...returnVal+"+returnVal);

}




异常通知 @AfterThrowing

该通知只有在异常时才会被触发,并由throwing来声明一个接收异常信息的变量,同样异常通知也用于Joinpoint参数,需要时加上即可,如下

/**

\* 抛出通知

\* @param e 抛出异常的信息

*/

@AfterThrowing(value="execution(* com.zejian.spring.springAop.dao.UserDao.addUser(..))",throwing = "e")

public void afterThrowable(Throwable e){

 System.out.println("出现异常:msg="+e.getMessage());

}




最终通知 @After

该通知有点类似于finally代码块,只要应用了无论什么情况下都会执行。

/**

 \* 无论什么情况下都会执行的方法

 \* joinPoint 参数

 */

@After("execution(* com.zejian.spring.springAop.dao.UserDao.*User(..))")

public void after(JoinPoint joinPoint) {

  System.out.println("最终通知....");

}




环绕通知@Around

环绕通知既可以在目标方法前执行也可在目标方法之后执行,更重要的是环绕通知可以控制目标方法是否指向执行,但即使如此,我们应该尽量以最简单的方式满足需求,在仅需在目标方法前执行时,应该采用前置通知而非环绕通知。案例代码如下第一个参数必须是ProceedingJoinPoint,通过该对象的proceed()方法来执行目标函数,proceed()的返回值就是环绕通知的返回值。同样的,ProceedingJoinPoint对象也是可以获取目标对象的信息,如类名称,方法参数,方法名称等等。

@Around("execution(* com.zejian.spring.springAop.dao.UserDao.*User(..))")

public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

  System.out.println("我是环绕通知前....");

  //执行目标函数

  Object obj= (Object) joinPoint.proceed();

  System.out.println("我是环绕通知后....");

  return obj;

}




Aspect优先级

在不同的切面中,如果有多个通知需要在同一个切点函数指定的过滤目标方法上执行,那些在目标方法前执行(”进入”)的通知函数,最高优先级的通知将会先执行,在执行在目标方法后执行(“退出”)的通知函数,最高优先级会最后执行。而对于在同一个切面定义的通知函数将会根据在类中的声明顺序执行。如下:




package com.zejian.spring.springAop.AspectJ; 

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
@Component
public class AspectOne {


  /**
   * Pointcut定义切点函数
   */

  @Pointcut("execution(* com.zejian.spring.springAop.dao.UserDao.deleteUser(..))")
  private void myPointcut(){}

  @Before("myPointcut()")
  public void beforeOne(){
    System.out.println("前置通知....执行顺序1");
  }

 
  @Before("myPointcut()")
  public void beforeTwo(){
    System.out.println("前置通知....执行顺序2");
  }


  @AfterReturning(value = "myPointcut()")
  public void AfterReturningThree(){
    System.out.println("后置通知....执行顺序3");
  }

  @AfterReturning(value = "myPointcut()")
  public void AfterReturningFour(){
    System.out.println("后置通知....执行顺序4");
  }
}

运行结果如下:









如果在不同的切面中定义多个通知响应同一个切点,进入时则优先级高的切面类中的通知函数优先执行,退出时则最后执行,如下定义AspectOne类和AspectTwo类并实现org.springframework.core.Ordered 接口,该接口用于控制切面类的优先级,同时重写getOrder方法,定制返回值,返回值(int 类型)越小优先级越大。其中AspectOne返回值为0,AspectTwo的返回值为3,显然AspectOne优先级高于AspectTwo。

@Aspect
@Component
public class AspectOne implements Ordered {

 
  /**
   * Pointcut定义切点函数
   */
  @Pointcut("execution(* com.zejian.spring.springAop.dao.UserDao.deleteUser(..))")
  private void myPointcut(){}

  @Before("myPointcut()")
  public void beforeOne(){
      System.out.println("前置通知..AspectOne..执行顺序1");
  } 

  @Before("myPointcut()")
  public void beforeTwo(){
    System.out.println("前置通知..AspectOne..执行顺序2");
  }

  @AfterReturning(value = "myPointcut()")
  public void AfterReturningThree(){
   	System.out.println("后置通知..AspectOne..执行顺序3");
  }

  @AfterReturning(value = "myPointcut()")
  public void AfterReturningFour(){
    System.out.println("后置通知..AspectOne..执行顺序4");
  }

  /**
   * 定义优先级,值越低,优先级越高
   */
  @Override
  public int getOrder() {
    return 0;
  }
}


//切面类 AspectTwo.java
@Aspect
@Component
public class AspectTwo implements Ordered {

  /**
   * Pointcut定义切点函数
   */
  @Pointcut("execution(* com.zejian.spring.springAop.dao.UserDao.deleteUser(..))")
  private void myPointcut(){}

 
  @Before("myPointcut()")
  public void beforeOne(){
    System.out.println("前置通知....执行顺序1--AspectTwo");
  }

  @Before("myPointcut()")
  public void beforeTwo(){
    System.out.println("前置通知....执行顺序2--AspectTwo");
  }

  @AfterReturning(value = "myPointcut()")
  public void AfterReturningThree(){
   System.out.println("后置通知....执行顺序3--AspectTwo");
  }

  @AfterReturning(value = "myPointcut()")
  public void AfterReturningFour(){
    System.out.println("后置通知....执行顺序4--AspectTwo");
  }


  /**
   * 定义优先级,值越低,优先级越高
   * @return
   */
  @Override
  public int getOrder() {
    return 2;
  }
}

运行结果如下:








Spring AOP 简单应用场景

1.监控接口的运行时间

2.异常处理

/**
 * AOP统一处理
 * 记录接口运行时间和异常处理
 */
@Aspect
@Component
public class AopController {

    //根据方法签名进行过滤,public类型的方法,返回值为ResultBean,controller下所有类,不限个数参数
    @Pointcut("execution(public com.example.ds.utils.ResultBean com.example.ds.controller..*(..))")
    public void aopPoint(){}

    @Around("aopPoint()")
    public Object handlerControllerMethod(ProceedingJoinPoint pjp){
        long startTime = System.currentTimeMillis();

        com.example.ds.utils.ResultBean result = null;

        try{
            result = (com.example.ds.utils.ResultBean)pjp.proceed();
            System.out.println(pjp.getSignature()+"--use time:"+(System.currentTimeMillis()-startTime));

        }catch (Throwable e) {
            result = new ResultBean();
            //可以针对已知异常进行处理
            if (e instanceof NullPointerException){
                System.out.println(pjp.getSignature()+"空指针了");
                result.setCode(ResultBean.FAIL);
                result.setMsg(e.getLocalizedMessage());
            }else {
                System.out.println(pjp.getSignature()+" error " +e);
                e.printStackTrace();
                result.setCode(ResultBean.FAIL);
                result.setMsg("InternalServerError");
            }
        }
        return result;
    }
}
发布

评论 2