Spring AOP

匆匆过客 提交于 2019-12-01 10:22:07

案例下载:
https://files.cnblogs.com/files/macht/Spring1904AOP.zip

注意:

在xml中扫描组件的时候,<context:component-scan base-package="aop"/>

总是报错,原因是没有加载命名空间。只要正确的输出就会自动的加载命名空间了,不要紧张、气愤,正确的输入一遍就加载出来了。

回顾

IOC和DI

IOC 控制反转
DI 依赖注入

目的:

代码的解耦(例子:自己做饭、点外卖)扩展、修改、维护方便

IOC的基本实现:

反射技术——>解耦
1)定义注解
2)把注解配置到属性上
3)通过反射读取属性上的注解
4)通过反射创建对象
5)设置对象到属性上

Spring的特点

轻量级(简单、侵入性低)
IOC
AOP
容器(帮助创建、保存、管理Java对象)
框架
一站式(Spring全家桶)

Spring的模块

core 核心
context 上下文
aop 面向切面
webmvc MVC框架
jdbc
orm

IOC的Spring实现:

XML

bean    在容器定义JavaBean
    属性
    id      名称,唯一
    name    名称,可以重复(重复找最后的)
    class   类型
    scope   bean的创建模式
        prototype       原型(多例)
        singleton       单例(默认)
    autowire    自动装配,必须有set方法
        byName  通过id或name查找和属性名相同的对象
        byType  通过类型查找和属性类型相同对象         
        
    标签
    property    属性
        name    属性名称
        value   基本和String类型
        ref     引用类型
    constructor-arg 构造方法的参数
        类似property  

注解

@Component
类,容器扫描该类创建对象加入到容器中
@Autowired
属性(set方法)
让容器注入对象到属性上
默认是按类型查找,类型找不到再按名字找
@ComponentScan
配置类,扫描某个包
@Configuration
定义配置类
@Qualifier
标识,解决多个相同类型注入出现冲突的问题
@Resource
作用类似Autowired
默认是按名字找,名字找不到再按类型找
@Repository
DAO实现上
@Service
Service实现上
@Controller
Controller上

AOP

代理模式

GOF23模式之一

目的:

客户不方便或不能访问某一个对象,通过代理对对象进行访问,代理起到中介的作用。

优点:

1)职责清晰
2)符合开闭原则,利于扩展

代理模式的实现

静态代理
动态代理
    JDK动态代理
    CGLib动态代理

静态代理

  1. 分为代理者和被代理者
  2. 代理者和被代理者具有相同的功能(实现相同的接口)
  3. 代理者能访问被代理者(代理者包含被代理者对象)
  4. 代理者执行功能时,会调用被代理者的功能,同时会进行扩展
    缺点:每个代理类只能代理一个业务,如果要代理更多的业务,需要定义大量的新的代理类。

    动态代理

    可以动态生成代理类和代理对象,不要手动编写大量的代理类

    JDK动态代理

InvocationHandler接口

Object invoke(Object proxy, Method method, Object[] args)       
    指定代理者如何执行代理工作(功能扩展)
    proxy   代理者对象
    method  被代理者需要调用的方法
    args        方法调用需要的参数

Proxy 代理类

Object newProxyInstance(ClassLoader cl,Interfaces[] fcs,InvocationHandler h)
    创建代理者对象并返回该对象
    cl  类加载器,帮助对类的字节码进行解析
    fcs 对象的接口,被代理者必须实现接口
    h   InvocationHandler的实现

newProxyInstance的实现过程

1)通过被代理对象的类加载器,加载类的字节码
2)将字节码反编译成Java被代理类
3)在被代理类的源码中添加invoke方法代码,创建新的代理类
4)创建代理类的对象,返回
/**
 * 动态代理实现的代理商类
 */
public class DynamicProxy implements InvocationHandler {

    //被代理者对象
    private Object factory;

    /**
     * 创建代理者对象
     * @param factory 被代理者对象
     * @return 代理者对象
     */
    public Object createProxy(Object factory){
        //传入被代理者对象,方便在invoke方法中调用
        this.factory = factory;
        //动态生成代理者对象 参数1 是类加载器,为了加载被代理对象的类;
        // 参数2 是对象实现的接口数组;参数3 是InvocationHandler的实现对象
        return Proxy.newProxyInstance(factory.getClass().getClassLoader(),
                factory.getClass().getInterfaces(),
                this);
    }

    /**
     * 实现动态代理的功能扩展
     * @param proxy     代理对象
     * @param method    被代理者需要调用的方法
     * @param args      调用方法需要的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("给产品打广告~~~~~");
        //调用被代理对象的方法
        method.invoke(factory,args);
        System.out.println("给产品做售后~~~~~");
        return null;
    }
}

CGLib动态代理

特点:基于继承,通过生成被代理类的子类的方式来创建代理类

1)实现MethodInterceptor接口
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("使用CGLib代理,推销产品!!");
        //使用方法的代理,调用父类方法
        methodProxy.invokeSuper(o,objects);
        System.out.println("使用CGLib代理,进行售后!!");
        return null;
    }
2)使用Enhancer创建代理对象
//创建Enhancer对象
        Enhancer hander = new Enhancer();
        //设置父类类型
        hander.setSuperclass(obj.getClass());
        //设置方法的回调,调用代理者对象方法时,会调用当前对象的intercept
        hander.setCallback(this);
        //创建代理对象返回
        return hander.create();

AOP的概念

AOP(Aspect Oriented Programming) 面向切面编程
是OOP(面向对象编程)的一个重要的补充,OOP以对象为核心,以封装、继承、多态建立类之间的关系,OOP能够实现的是纵向关系。
AOP实现的横向的关系,能将多个相互没有关系的类,又具有相同的业务需求,整合起来,为这些类提供通用的服务。例如项目中的:日志输出、事务处理、缓存操作、异常处理、权限控制。
总结:AOP能够将于类核心业务无关,又都需要的功能封装起来,实现自动调用,每个类只需要关注自己的核心业务,降低了代码的耦合性,提高了代码扩展性和维护性。

AOP的术语

1、横切关注点(Aspect)
对哪些方法进行拦截,拦截后怎么处理,这些关注点称之为横切关注点
类是对物体特征的抽象,切面就是对横切关注点的抽象
3、连接点(joinpoint)
被拦截到的点,因为Spring只支持方法类型的连接点,所以在Spring中连接点指的就是被拦 截到的方法,实际上连接点还可以是字段或者构造器
4、切入点(pointcut)
对连接点进行拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、目标对象
代理的目标对象
将切面应用到目标对象并导致代理对象创建的过程
8、引入\织入(introduction、weave)
在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段

AOP的动态代理

将通知方法织入到被代理类方法的过程,使用动态代理实现

先判断被代理类是否实现过接口,如果实现了就使用JDK动态代理

如果没有实现接口,就使用CGLib实现代理

AOP的实现

1)导入依赖
spring-aop
aspectjrt
aspectjweaver
2)编写需要拦截的类和方法
3)编写日志通知类

/**
 * 日志通知类
 */
public class LogAdvise {

    /**
     * 前置日志通知
     */
    public void beforeLog(){
        System.out.println("方法开始执行了~!!");
    }

    /**
     * 后置日志通知
     */
    public void afterLog(){
        System.out.println("方法执行结束");
    }

    /**
     * 环绕通知
     * @param point 连接点
     * @return
     */
    public Object aroundLog(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕通知前置执行~~~~~");
        //执行原来的方法
        Object result = point.proceed();
        System.out.println("环绕通知后置执行~~~~~");
        return result;
    }

    /**
     * 抛出异常后通知
     */
    public void afterThrowLog(){
        System.out.println("方法抛出了异常~~");
    }

    /**
     * 返回后通知
     */
    public void afterReturnLog(){
        System.out.println("方法返回了!~~");
    }
}

4) AOP的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置扫描包-->
    <context:component-scan base-package="com.qianfeng.aop.demo4"/>
    <!--配置通知对象-->
    <bean id="log" class="com.qianfeng.aop.demo4.LogAdvise"/>
    <!--配置AOP-->
    <aop:config>
        <!--配置连接点,expression用于指定哪些类的哪些方法需要被拦截 -->
        <!--第一个*代表任意的返回值类型-->
        <!--第二个*ServiceImpl代表匹配ServiceImpl结尾的所有类-->
        <!--第三个*代表类中所有的方法-->
        <!--(..)代表方法任意的参数-->
        <aop:pointcut id="pointcut"
                      expression="execution(* com.qianfeng.aop.demo4.*ServiceImpl.*(..))"/>
        <!--配置切面 ref引用通知对象-->
        <aop:aspect id="aspect" ref="log">
            <!--配置前置通知,method是通知类中的方法名 pointcut-ref是连接点-->
            <aop:before method="beforeLog" pointcut-ref="pointcut"/>
            <aop:after method="afterLog" pointcut-ref="pointcut"/>
            <aop:around method="aroundLog" pointcut-ref="pointcut"/>
            <aop:after-throwing method="afterThrowLog" pointcut-ref="pointcut"/>
            <aop:after-returning method="afterReturnLog" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>

Log4J日志框架

Apache的开源日志框架
可以将日志信息输出到控制台、文件或网络服务器上

使用方法:

1)导入依赖
2)配置文件 log4j.properties
3) 使用Log4J
创建Logger对象:
Logger logger = Logger.getLogger(当前类.class);
输出日志:
logger.info("日志内容");
logger.debug("日志内容");
logger.error("日志内容");
logger.fatal("日志内容");
logger.warn("日志内容");

设置日志级别和目的地

log4j.rootCategory=日志级别,目的地1,目的地2...
日志级别有(从低到高)OFF、FATAL、ERROR、WARN、INFO、DEBUG、ALL
高级别的日志包含低级别的日志

配置目的地

配置目的地的类型Appender
log4j.appender.目的地名=目的地类型

目的地类型有:

org.apache.log4j.ConsoleAppender 控制台
org.apache.log4j.FileAppender 单一文件
org.apache.log4j.DailyRollingFileAppender 每天产生新文件
org.apache.log4j.RollingFileAppender 每超过一定大小产生新文件
org.apache.log4j.WriterAppender 输出流(网络)

配置日志的格式Layout

log4j.appender.目的地名.layout=格式类型

格式类型有:

org.apache.log4j.HTMLLayout(以HTML表格形式布局),
org.apache.log4j.PatternLayout(可以灵活地指定布局模式),
org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串),
org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等等信息)

配置具体的格式ConversionPattern

log4j.appender.目的地名.layout.ConversionPattern=具体格式
%m 输出代码中指定的消息;
%M 输出打印该条日志的方法名;
%p 输出优先级,即DEBUG,INFO,WARN,ERROR,FATAL;
%r 输出自应用启动到输出该log信息耗费的毫秒数;
%c 输出所属的类目,通常就是所在类的全名;
%t 输出产生该日志事件的线程名;
%n 输出一个回车换行符,Windows平台为"rn”,Unix平台为"n”;
%d 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyyy-MM-dd HH:mm:ss,SSS},输出类似:2002-10-18 22:10:28,921;
%l 输出日志事件的发生位置,及在代码中的行数;

配置目的地为文件的文件路径

log4j.appender.file.File=C:\xpp\java_code\mylog.log

上机任务:

1)使用AOP来配置实现Service层所有方法的日志(前置、后置)输出
2)使用Log4j实现日志输出

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!