SpringBoot入门5——AOP

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34515002/article/details/82588700

参考:探秘Spring AOP

参考代码

面向切面编程(AOP)

概览、AOP使用、AOP原理、AOP开源运用。

编程范式

  1. 面向过程编程(OOP)
  2. 面向对象编程(OOB)
  3. 函数式编程
  4. 事件驱动编程
  5. 面向切面编程

面向切面编程(AOP)

是一种编程范式,不是编程语言

解决特定问题

是OOP的补充

1、AOP的作用:

DRY:Don't Repeat Yourself。提取公共的代码

SoC: Separation of Concerns

  • 水平分离:展示层->服务层->持久层
  • 垂直分离:模块划分(订单、库存等)
  • 切面分离:分离功能性需求与非功能性需求。

2、AOP的优点:

集中处理某一关注点/横切逻辑

可以很方便的添加/删除关注点

入侵性少,增强代码可读性及可维护性

3、AOP的应用场景:

权限控制——经常使用

缓存控制——

事务控制——

审计日志——

性能监控——

分布式追踪——

异常处理——可以用:@ControllerAdvice+@ExceptionHandler

4、支持AOP的编程语言:几乎所有

权限控制案例

要求:

1、产品管理的服务

2、产品添加/删除的操作只能管理员才能进行

3、普通实现 VS AOP实现

@Pointcut("@annotation(AdminOnly)")
public void adminOnly(){

}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface AdminOnly{
}

AOP使用方式

XML配置->Pointcut expression

注解方式->Pointcut expression

1、主要注解

@Aspect

@Pointcut

@Advice

2、切面表达式组成(Pointcut expression)——3个部分:

within表达式

匹配包/类型

//匹配ProductService类里头的所有方法
@Pointcut("within(com.imooc.service.ProductService)")
public void matchType(){}

//匹配com.imooc包及子包下所有类的方法
@Pointcut("within(com.imooc..*)")
public void matchPackage(){}

对象匹配

    //匹配AOP对象的目标对象为指定类型的方法,即DemoDao的aop代理对象的方法
    @Pointcut("this(com.imooc.DemoDao)")
    pubic void thisDemo(){}
    
    //匹配实现IDao接口的目标对象(而不是aop代理后的对象)的方法
    @Pointcut("target(com.imooc.IDao)")
    public void targetDemo(){}
    
    //this 可以拦截 DeclareParents(Introduction)
    //target 不拦截 DeclareParents(Introduction)
    
    //匹配所有以Service结尾的bean里头的方法
    @Pointcut("bean(*Service)")
    public void beanDemo(){}

参数匹配

    //匹配任何以find开头而且只有一个Long参数的方法
    @Pointcut("execution(* *..find*(Long))")
    public void argsDemo1(){}

    //匹配任何以find开头的而且第一个参数为Long型的方法
    @Pointcut("execution(* *..find*(Long,..))")
    public void argsDemo2(){}
    
    //匹配任何只有一个Long参数的方法
    @Pointcut("within(com.imooc..*) && args(Long)")
    public void argsDemo3(){}
    
    //匹配第一个参数为Long型的方法
    @Pointcut("within(com.imooc..*) && args(Long,..)")
    public void argsDemo4(){}

注解匹配

//匹配方法标注有AdminOnly的注解的方法
@Pointcut("@annotation(com.imooc.anno.AdminOnly) && within(com.imooc..*)")
public void annoDemo(){}

//匹配标注有NeedSecured的类底下的方法 //class级别
@Pointcut("@within(com.imooc.anno.NeedSecured) && within(com.imooc..*)")
public void annoWithinDemo(){}


//匹配标注有NeedSecured的类及其子类的方法 //runtime级别在spring context的环境下,二者没有区别
@Pointcut("@target(com.imooc.anno.NeedSecured) && within(com.imooc..*)")
public void annoTargetDemo(){}


//匹配传入的参数类标注有Repository注解的方法
@Pointcut("@args(com.imooc.anno.NeedSecured) && within(com.imooc..*)")
public void annoArgsDemo(){}

execution()表达式

格式:execution(

                 modifier-pattern?

                 ret-type-pattern

      declaring-type-pattern?

      name-pattern(param-pattern)

     throws-pattern?

)

//匹配任何公共方法
@Pointcut("execution(publiccom.imooc.service.*.*(..))")
public void matchCondition1(){}

//匹配com.imooc包及子包下Service类中无参方法
@Pointcut("execution(* com.imooc..*Service.*())")
public void matchCondition2(){}

//匹配com.imooc包及子包下Service类中的任何只有一个参数的方法
@Pointcut("execution(* com.imooc..*Service.*(*))")
public void matchCondition3(){}


//匹配com.imooc包及子包下任何类的任何方法
@Pointcut("execution(* com.imooc..*.*(..))")
public void matchCondition4(){}

//匹配com.imooc包及子包下返回值为String的任何方法
@Pointcut("execution(String com.imooc..*.*(..))")
public void matchCondition5(){}

//匹配异常
@Pointcut("execution(publiccom.imooc.service.*.*(..) throws java.lang.IllegalAccessException)")
public void matchCondition6(){}

5种Advice注解

@Before,前置通知

@After(finally),后置通知,方法执行完之后

@AfterReturning,返回结果通知,成功执行后

@AfterThrowing,异常通知,抛出异常之后(如果有@Around注解时使用try-catch,@AfterThrowing无效)

@Around,环绕通知。

    
@Before("matchLongArg() && args(productId)")
public void before(Long productId){
    this.logger.info("###before,get args:"+productId+"======================ADVICE");
}

@After("matchLongArg()  && within(com.imooc..*)")
public void afterMatch(){
    this.logger.info("###after =========================================ADVICE");
    this.logger.info("");
}

@AfterReturning(returning = "object",pointcut = "matchReturn()  && within(com.imooc..*)")
public void afterReturningMatch(Object object){
    this.logger.info("###afterReturning,result={} ================ =ADVICE",object);
}

@AfterThrowing("matchException()")
public void afterThrowing(){
    this.logger.info("###afterThrowing========================= =ADVICE-afterThrowing");
}

@Around("matchException()")
public java.lang.Object after(ProceedingJoinPoint joinPoint){
    this.logger.info("###before===================================ADVICE-Around");
    java.lang.Object result = null;
    try{
        result = joinPoint.proceed(joinPoint.getArgs());
        this.logger.info("###after returning===================================ADVICE-Around");
    }catch (Throwable e){
        this.logger.info("###ex===================================ADVICE-Around");
        //throw
        e.printStackTrace();
    }finally {
        this.logger.info("###finally===================================ADVICE-Around");
    }
    return result;
}

AOP的实现原理

代理模式

JDK静态代理

1、接口:Subject.java

public interface Subject {
    void request();
}

2、具体实现类:RealSubject.java

public class RealSubject implements Subject {
    private final Logger logger = LoggerFactory.getLogger(RealSubject.class);

    @Override
    public void request() {
        this.logger.info("real subject execute request Method");
    }
}

3、代理类:Proxy.java

public class Proxy implements Subject{
    private final Logger logger = LoggerFactory.getLogger(Proxy.class);

    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        this.logger.info("before");

        try {
            realSubject.request();
        } catch (Exception e) {
//            e.printStackTrace();
            this.logger.info("ex:"+e.getMessage());
        }finally {
            this.logger.info("after");
        }
    }
}

 4、客户端(测试类)

	@Test
	public void contextLoads() {
		Subject subject = new Proxy(new RealSubject());
		subject.request();
	}  

静态代理与动态代理

1、静态代理的缺点

如果有100个类,就需要有100个代理,即使这些代理实现一样的动作。这时就需要动态代理。

2、动态代理的两类实现:基于接口代理、基于继承代理

3、2类实现的代表:JDK代理与Cglib代理。

JDK动态代理

基于接口代理

1、接口:Subject.java

2、具体实现类:RealSubject.java

3、代理类:JdkProxySubject.java

public class JdkProxySubject implements InvocationHandler{
    private final Logger logger = LoggerFactory.getLogger(JdkProxySubject.class);

    private RealSubject realSubject;

    public JdkProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        this.logger.info("before");
        Object result = null;
        try {
            result = method.invoke(realSubject,args);
        } catch (Exception e) {
            this.logger.info("ex:"+e.getMessage());
            throw e;
        } finally {
            this.logger.info("after");
        }
        return result;
    }
}

4、客户端/测试类:

@Test
public void testJdkProxySubject(){
	Subject subject = (Subject) java.lang.reflect.Proxy.newProxyInstance(
                        ProxyDemoApplicationTests.class.getClassLoader()
			,new Class[]{Subject.class}
			,new JdkProxySubject(new RealSubject()));
	subject.request();
}

5、动态代理与静态代理相比:

如果接口Subject.java添加一个抽象方法,则具体实现类RealSubject.java必须要实现。

同时静态代理类也必须要实现,然后才能在客户端使用。

但是动态代理不需要实现,就可以在客户端使用。

6、JDK代理源码解析

Proxy.newProxyInstance

getProxyClass0,ProxyClassFactory, ProxyGenerator

newInstance

1)Client.java

public class Client {
    public static void main(String[] args){
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");


        Subject subject = (Subject) java.lang.reflect.Proxy.newProxyInstance(Client.class.getClassLoader()
                ,new Class[]{Subject.class}
                ,new JdkProxySubject(new RealSubject()));
        subject.request();

        subject.hello();
    }
}

运行,出现文件:com.sun.proxy.$Proxy0.class

Cglib动态代理

基于继承代理

1、接口:Subject.java

2、具体实现类:DemoMethodInterceptor.java

3、代理类:JdkProxySubject.java

public class DemoMethodInterceptor implements MethodInterceptor{
    private final Logger logger = LoggerFactory.getLogger(DemoMethodInterceptor.class);

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        this.logger.info("before=======================in cglib");
        Object result = null;
        try {
            result = proxy.invokeSuper(obj, args);
        } catch (Exception e) {
            this.logger.info("get ex:"+e.getMessage());
            throw e;
        } finally {
            this.logger.info("after====================in cglib");
        }
        return result;
    }
}

4、客户端/测试类

	@Test
	public void testCglibProxy(){
		Enhancer enhancer = new Enhancer();
		//生成指定类对象的子类,也就是重写类中的业务函数。
		enhancer.setSuperclass(RealSubject.class);
		//这里是回调函数,加入intercept()函数
		enhancer.setCallback(new DemoMethodInterceptor());
		//创建这个子类对象
		Subject subject = (Subject) enhancer.create();

		subject.request();
		subject.hello();
	}

5、JDK与Cglib代理对比

JDK Cglib
只能针对有接口的类的接口方法进行动态代理 基于继承来实现代理
  无法对static、final类进行代理
  无法对private,staitc方法进行代理

Spring对两种实现的选择

1、Spring如何创建代理bean

2、DefaultSopProxyFactory.java

public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
    public DefaultAopProxyFactory() {
    }

    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
        if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
            return new JdkDynamicAopProxy(config);
        } else {
            Class<?> targetClass = config.getTargetClass();
            if (targetClass == null) {
                throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
            } else {
                return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
            }
        }
    }

    private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
        Class<?>[] ifcs = config.getProxiedInterfaces();
        return ifcs.length == 0 || ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0]);
    }
}

3、实现的情况:

如果目标对象实现了接口,则默认采用JDK动态代理

如果目标对象没有实现接口,则采用Cglib进行动态代理

如果目标对象实现了接口,且强制cglib代理,则使用cglib代理。

4、强制cglib代理

@SpringBootApplication
@EnableAspectJAutoProxy(proxyTargetClass = true) /*强制使用cglib代理*/
public class ProxyDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(ProxyDemoApplication.class, args);
	}
}

AOP如何链式调用

1、责任链模式

2、Handler.java

public abstract class Handler {
    private Handler sucessor;

    public Handler getSucessor() {
        return sucessor;
    }

    public void setSucessor(Handler sucessor) {
        this.sucessor = sucessor;
    }

    public void execute(){
        handleProcess();
        if(sucessor!=null){
            sucessor.execute();
        }
    }

    protected abstract void handleProcess();
}

客户端/测试类

public class Client {
    private final Logger logger = LoggerFactory.getLogger(Client.class);

    static class HandlerA extends Handler{
        private final Logger logger = LoggerFactory.getLogger(HandlerA.class);
        @Override
        protected void handleProcess() {
            logger.info("handle by A");
        }
    }

    static class HandlerB extends Handler{
        private final Logger logger = LoggerFactory.getLogger(HandlerB.class);
        @Override
        protected void handleProcess() {
            logger.info("handle by B");
        }
    }

    static class HandlerC extends Handler{
        private final Logger logger = LoggerFactory.getLogger(HandlerC.class);
        @Override
        protected void handleProcess() {
            logger.info("handle by C");
        }
    }

    public static void main(String[] args){
        Handler handlerA = new HandlerA();
        Handler handlerB = new HandlerB();
        Handler handlerC = new HandlerC();

        handlerA.setSucessor(handlerB);
        handlerB.setSucessor(handlerC);

        handlerA.execute();

    }
}

3、改进版责任链模式

Chain.java,构造器的参数为列表,有游标。

public class Chain {

    private List<ChainHandler> handlers;

    private int index = 0;

    public Chain(List<ChainHandler> handlers) {
        this.handlers = handlers;
    }

    public void proceed(){
        if(index>=handlers.size()){
            return;
        }
        handlers.get(index++).execute(this);
    }
}

ChainHandler.java

public abstract class ChainHandler {
    public void execute(Chain chain){
        handlerProcess();
        chain.proceed();
    }

    protected abstract void handlerProcess();

}

Client.java

public class Client {
    static class ChainHandlerA extends ChainHandler{
        private final Logger logger = LoggerFactory.getLogger(ChainHandlerA.class);
        @Override
        protected void handlerProcess() {
            logger.info("handle by A");
        }
    }
    static class ChainHandlerB extends ChainHandler{
        private final Logger logger = LoggerFactory.getLogger(ChainHandlerB.class);
        @Override
        protected void handlerProcess() {
            logger.info("handle by B");
        }
    }
    static class ChainHandlerC extends ChainHandler{
        private final Logger logger = LoggerFactory.getLogger(ChainHandlerC.class);
        @Override
        protected void handlerProcess() {
            logger.info("handle by C");
        }
    }
    public static void main(String[] args){
        List<ChainHandler> handlers = Arrays.asList(
          new ChainHandlerA(),
          new ChainHandlerB(),
          new ChainHandlerC()
        );
        Chain chain = new Chain(handlers);
        chain.proceed();
    }
}

4、Spring中使用的代码 ReflectiveMethodInvocation.java 中的方法proceed()

Spring AOP经典代码解读

@Transactional事务

@PreAuthorize安全

@Cacheable缓存

1、@Transactional事务

@Column(unique = true)
private String name;

这个注解确实不启作用,运行测试代码时没有报错。

2、安全校验@PreAuthorize

MethodSecurityInterceptor

PreInvocationAuthorizationAdviceVoter

ExpressionBasedPreInvocationAdvice

3、缓存@Cacheable

AnnotationCacheAspect

CacheInterceptor

CacheAspectSupport

案例——商家产品管理系统

记录产品修改的操作记录

什么人在什么时间修改了哪些产品的哪些字段修改为什么值。

1、实现思路(分析)

利用aspect去拦截增删改方法

利用反射获取对象的新旧值

利用@Around的advice去记录操作记录

2、领域模型

小结:

反射获取新旧值

@Around的advice去记录修改记录

利用注解去增加中文字段名。

保存修改记录到mongoDB

注意

1、AOP无法拦截内部调用。

MenuService.java

@Component
public class MenuService {

    @Cacheable(cacheNames = {"menu"})
    public List<String> getMenuList(){
        System.out.println("");
        System.out.println("mock:get from db");
        return Arrays.asList("article","comment","admin");
    }

    public List<String> getRecommends(){
        return this.getMenuList();
//        MenuService proxy = ApplicationContextHolder.getContext().getBean(MenuService.class);
//        return proxy.getMenuList();
    }
}

测试类

	@Test
	public void testInnerCall(){
		System.out.println("call:"+menuService.getRecommends());
		System.out.println("call:"+menuService.getRecommends());
	}

执行结果,发现并没有受到缓存的AOP拦截,第2次获取数据没有从缓存中获取。

15:13:20.499 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved ProfileValueSource type [class org.springframework.test.annotation.SystemProfileValueSource] for class [com.imooc.CacheDemoApplicationTests]
mock:get from db
call:[article, comment, admin]

mock:get from db
call:[article, comment, admin]
15:13:20.523 [main] DEBUG org.springframework.test.annotation.ProfileValueUtils - Retrieved @ProfileValueSourceConfiguration [null] for test class [com.imooc.CacheDemoApplicationTests]

2、解决方法。

ApplicationContextHolder.java
@Component
public class ApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext ctx;

    public static ApplicationContext getContext() {
        return ctx;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        ctx = applicationContext;
    }
}

MenuService.java

    public List<String> getRecommends(){
        MenuService proxy = ApplicationContextHolder.getContext().getBean(MenuService.class);
        return proxy.getMenuList();
    }

 

猜你喜欢

转载自blog.csdn.net/qq_34515002/article/details/82588700