spring-aop-1

1.Spring的aop是什么?

与OOP对比,AOP是处理一些横切性问题,这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护。AOP就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。

2.aop的应用场景

日志记录

权限验证

效率检查

事务管理

3.关于aop的一些概念

join point

  -->连接点:目标对象中的方法

  -->例如:dao层有四个方法需要添加事务,这四个方法成为连接点.

Pointcut

  -->切点:连接点的集合,

  切点有两个关键的要素:

  1.where:增强到哪里方法?

  2.when:什么时候进行增强

  可以把切点理解为数据库中的一张表,那么连接点就是表中的一条数据.

Weaving

  -->织入:把代理逻辑加入到目标对象上的过程叫做织入

advice

  -->通知

  包含了两部分:

  1.通知具体的内容(代理逻辑代码)

  2.where:通知到哪里去-->代理逻辑代码织入到目标对象那个位置

Aspect

  -->切面

  在AspectJ中它是一个类

  在xml中它就是一个标签  

target 

  -->目标对象,原始对象

aop Proxy

  -->代理对象,包含了原始对象的代码和增加后的代码的那个对象

4.springAop和AspectJ的关系

(1)aop与springAop的关系

aop是一种思想,springAop是aop思想的实现.类似于ioc与di的关系

(2)spring AOP提供两种编程风格

  @AspectJ support ------------>利用aspectj的注解

  Schema-based AOP support ----------->xml aop:config 命名空间

  证明:spring,通过源码分析了,我们可以知道spring底层使用的是JDK或者CGLIB来完成的代理,并且在官网上spring给出了aspectj的文档,和springAOP是不同

(3)简单的总结

  spring支持AspectJ的语法,但具体的底层实现是spring自己实现的.

  类似与javaScript借鉴java语法一样.

5.springAop基于AspectJ实现

(1)

①使用Java配置启用@AspectJ支持---需要映入aspectj相应的pom文件

@Configuration
@ComponentScan("cn.cg.*")
//启动@AspectJ支持 @EnableAspectJAutoProxy
public class AppConfig { }

②使用xml配置启用@AspectJ支持

<aop:aspectj-autoproxy/>

(2)声明一个Target

@Component
public class IndexDao {
    public void query(){
        System.out.println("query");
    }
}

(3)声明一个aspect

@Component
@Aspect
public class DaoAspect {

}

(4)声明切点

@Component
@Aspect
public class DaoAspect {
  //切点
    @Pointcut("execution(* cn.cg.target.IndexDao.query(..))")
    public void pointCut(){
        //空,若写代码不执行
    }
    
}

(5)声明一个通知@Component@Aspect

public class DaoAspect {

    @Pointcut("execution(* cn.cg.target.IndexDao.query(..))")
    public void pointCut(){ }
    //关键词:
  //when-->before-->前置通知-->具体的逻辑代码织入到目标方法的前面
@Before(
"pointCut()") public void before(){ //具体的逻辑代码 System.out.println("before"); } }

(6)测试

  

public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        IndexDao dao = (IndexDao) ac.getBean("indexDao");
        dao.query();
    }
}

console:

before
query

success!!!

(7)Supported Pointcut Designators-->spring支持的切入点指示器

一共9种:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop-pointcuts

介绍其中的三种-->一般开发中最常用的

①execution

For matching method execution join points --->用于匹配方法执行连接点

例如:* public cn.cg.target.IndexDao.query(..)
既然切点是连接点的集合,所以支持*,&&,||等符号
例如:* cn.*.target.Index*.*(..)
--官方给出的execute表达式语法:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
            throws-pattern?)
这里问号表示当前项可以有也可以没有,其中各项的语义如下
modifiers-pattern:方法的可见性,如public,protected;
ret-type-pattern:方法的返回值类型,如int,void等;
declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect;
name-pattern:方法名类型,如buisinessService();
param-pattern:方法的参数类型,如java.lang.String;
throws-pattern:方法抛出的异常类型,如java.lang.Exception;

②within

 Limits matching to join points within certain types-->将匹配限制为特定类型中的连接点

表达式的最小粒度为类
  
@Pointcut("within(com.cg.dao.*)")//匹配com.chenss.dao包中的任意方法
@Pointcut("within(com.cg.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法

案例:

配置一个新的Dao

//添加新的Dao
@Component
public class UserDao { public void Personquery(){ System.out.println("query"); } }

配置切点

//cn.cg.target.UserDao下所有的方法
//如果将表达式改为:@Pointcut("within(cn.cg.target.UserDao.*)")则不生效
@Pointcut("within(cn.cg.target.UserDao)")
    public void pointCut2(){

    }

    @Before("pointCut2()")
    public void before( ){
        //具体的逻辑代码
        System.out.println("before");
    }

测试:

public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        IndexDao dao = (IndexDao) ac.getBean("indexDao");
        dao.query();
        System.out.println("--------------------------");
        UserDao dao2 = (UserDao) ac.getBean("userDao");
        dao2.Personquery();
    }
}

console:

query
--------------------------
before
query

结论:

表达式的最小粒度为类

③agr 

 args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关


/**
 * args同execution不同的地方在于:
 * args匹配的是运行时传递给方法的参数类型
 * execution(* *(java.io.Serializable))匹配的是方法在声明时指定的方法参数类型。
 */
@Pointcut("args(java.io.Serializable)")//匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配
@Pointcut("@args(com.chenss.anno.Chenss)")//接受一个参数,并且传递的参数的运行时类型具有@Classified

案例:

  修改indexDao

@Component
public class IndexDao {
    public void query(){
        System.out.println("query");
    }
    public void query(String str){
        System.out.println("query"+str);
    }
}

切点:

  

 @Pointcut("args(java.lang.String)")
    public void pointCut3(){

    }
    @Before("pointCut3()")
    public void before( ){
        //具体的逻辑代码
        System.out.println("before");
    }

测试:

public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        IndexDao dao = (IndexDao) ac.getBean("indexDao");
        dao.query();
        System.out.println("--------------------------");
        UserDao dao2 = (UserDao) ac.getBean("userDao");
        dao2.Personquery();
        System.out.println("--------------------------");
        dao.query("1111");
    }
}

console:

query
--------------------------
query
--------------------------
before
query1111

结论: 

args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关

(4)||,&&,!的使用

案例--->演示&&

修改UserDao用于做对照

@Component
public class UserDao {
    public void Personquery(){
        System.out.println("query");
    }
    public void Personquery(String str){
        System.out.println("query"+str);
    }
    
}

添加切点

//indexDao类中需传入参数为String类型的方法,并且该参数存在且只有一个
@Pointcut("within(cn.cg.target.IndexDao)&&args(java.lang.String)") public void pointCut4(){ } @Before("pointCut4()") public void before( ){ //具体的逻辑代码 System.out.println("before"); }

测试:

public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        IndexDao dao = (IndexDao) ac.getBean("indexDao");
        dao.query("index");
        System.out.println("--------------------------");
        UserDao dao2 = (UserDao) ac.getBean("userDao");
        dao2.Personquery("person");
        System.out.println("--------------------------");
        dao.query();
        System.out.println("--------------------------");
        dao2.Personquery();
    }
}

console:

  

before
queryindex
--------------------------
queryperson
--------------------------
query
--------------------------
query

结论:

  仅增强了indexDao中query(String str)方法

(5)target----指向接口和子类

 this----JDK代理时,指向接口和代理类proxy,cglib代理时 指向接口和子类(不使用proxy)

/**
 * 此处需要注意的是,如果配置设置proxyTargetClass=false,或默认为false,则是用JDK代理,否则使用的是CGLIB代理
 * JDK代理的实现方式是基于接口实现,代理类继承Proxy,实现接口。
 * 而CGLIB继承被代理的类来实现。
后续的随笔会讲到
* 所以使用target会保证目标不变,关联对象不会受到这个设置的影响。 * 但是使用this对象时,会根据该选项的设置,判断是否能找到对象。
*/ @Pointcut("target(com.chenss.dao.IndexDaoImpl)")//目标对象,也就是被代理的对象。限制目标对象为com.chenss.dao.IndexDaoImpl类 @Pointcut("this(com.chenss.dao.IndexDaoImpl)")//当前对象,也就是代理对象,代理对象时通过代理目标对象的方式获取新的对象,与原值并非一个 @Pointcut("@target(com.chenss.anno.Chenss)")//具有@Chenss的目标对象中的任意方法 @Pointcut("@within(com.chenss.anno.Chenss)")//等同于@targ

 案例:

  添加切点

@Pointcut("this(cn.cg.target.IndexDao)")
    public void pointCut5(){

    }

    @Before("pointCut5()")
    public void before( ){
        //具体的逻辑代码
        System.out.println("before");
    }

  创建接口

public interface Dao {
    public void query();
    public void query(String str);
}

  IndexDao实现该接口

@Component
public class IndexDao implements Dao {
    public void query(){
        System.out.println("query");
    }
    public void query(String str){
        System.out.println("query"+str);
    }
}

  测试:

public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        IndexDao dao = (IndexDao) ac.getBean("indexDao");

    }
}

  console:

Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy18 cannot be cast to cn.cg.target.IndexDao
    at cn.cg.MainClass.main(MainClass.java:18)

为什么呢?

上述已经提到因为IndexDao实现了Dao接口--->所以从容器中获取的对象为-->代理对象,他extends Proxy implements Dao

所以修改测试方法

public class MainClass {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
        Dao dao = (Dao) ac.getBean("indexDao");
        dao.query();
    }
}

  console:

query

为什么目标方法没有被增强呢?

与上述问题的原因一致,因为容器中获取的代理对象并不是IndexDao的实现类

解决方案:使用cglib代理

@Configuration
@ComponentScan("cn.cg.*")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class AppConfig { }

console:

before
query

原因:因为cglib代理是通过继承IndexDao实现的

  

猜你喜欢

转载自www.cnblogs.com/cg961107/p/11247931.html