Spring-10-AOP

Spring-10-AOP

代理模式

代理模式是Spring,AOP的底层

静态代理

真实角色

代理角色

真实角色和代理角色要实现同一个接口

代理角色要持有真实角色的引用。

代理角色可以实现额外的功能

示例

接口

public interface UserService {
    public void add();
    public void delete();
    public void select();
    public void update();
}

实现

//真实对象
public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void select() {
        System.out.println("查找了一个用户");
    }

    public void update() {
        System.out.println("修改了一个用户");
    }
}

用户调用

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        userService.add();
    }
}

现在,用户提出新的需求,需要输出日志,如果手动在UserServiceImpl类进行修改是违背了开闭原则的

好的做法是添加静态代理,去补充原来的实现类做不到的事情

public class UserServiceProxy implements UserService{

    //保留真实对象的引用,并用set注入
    private UserServiceImpl userService;
    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    public void add() {
        log("add");
        userService.add();
    }

    public void delete() {
        log("delete");
        userService.delete();
    }

    public void select() {
        log("select");
        userService.select();
    }

    public void update() {
        log("update");
        userService.update();
    }

    //日志方法
    public void log(String msg){
        System.out.println("[DEBUG]:使用了"+msg+"方法");
    }

}

利用代理测试

public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);
        proxy.add();
    }
}
//[DEBUG]:使用了add方法
//增加了一个用户

看起来似乎变得更加麻烦,明明在源码处只需要改4行,愣是加了一个代理类

但事实上开闭原则的存在并不是毫无根据的,在本例小规模的情况下,可能没有很深的体会

但是代码量很大的时候,修改一定没有扩展可靠,扩展的容错率比修改源码高的多

所以这种思想是重要的,能扩展尽量扩展,而不是去源码中修改

而AOP之所以称为面向切面编程正是由于代理模式,使得横向扩展成为可能

image-20210324220008682

动态代理

动态代理和静态代理的角色是一样的

动态代理的代理类是动态生成的,不是我们直接写好的

动态代理分为两大类:

  • 基于接口的动态代理
  • 基于类的动态代理

基于接口------JDK动态代理

基于类------cglib

Java字节码实现------javasist

需要了解两个类

  • Proxy:提供了创建动态代理类和实例的静态方法
  • InvocationHandler是由代理实例的调用处理程序实现的接口

在上例的静态代理示例中,代理类是由我们自己创建的

虽然也实现了扩展,但是每有一个真实角色就要对应一个代理,代码量是翻倍的

而且代理类中的代码写的比较"死",没有灵活性

而动态代理对其进行了改进,通过反射机制可以灵活的获取类的结构,方法等

可以说动态代理不仅保留了静态代理的优点,而且具有比起更强的灵活性和可复用性

//使用该类,动态生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口,这里使用Object 实现动态可复用
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }
    //生成得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }
    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = method.invoke(target,args);
        return result;
    }
}

测试

public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();
        //代理角色,使用ProxyInvocationHandler动态创建
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(userService);//设置要代理的对象

        //动态生成代理类,本质通过反射得到
        UserService proxy = (UserService) pih.getProxy();
    }
}

同样现在要添加日志功能

//使用该类,动态生成代理类
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口,这里使用Object 实现动态可复用
    private Object target;
    public void setTarget(Object target) {
        this.target = target;
    }
    //生成得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }
    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());
        Object result = method.invoke(target,args);
        return result;
    }

    //添加日志功能
    public void log(String msg){
        System.out.println("[DEBUG]:"+msg+"方法被执行了");
    }
}

这通过反射机制,动态获取了方法的名字,就不用像在静态代理中那样,在每个方法下加对应的msg,无疑灵活性和复用性大大提高了!

测试

public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();
        //代理角色,使用ProxyInvocationHandler动态创建
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(userService);//设置要代理的对象

        //动态生成代理类,本质通过反射得到
        UserService proxy = (UserService) pih.getProxy();

        proxy.add();
    }
}
//[DEBUG]:add方法被执行了
//增加了一个用户

AOP(Aspect Oriented Programming)

面向切面编程,通过预编译和运行期间动态代理实现程序功能的统一维护的一种技术,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高开发的效率

使用Spring实现AOP

使用AOP织入,需要导入一个织入包

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
方式一:使用Spring的API接口

接口

public interface UserService {
    public void add();
    public void delete();
    public void select();
    public void update();
}

真实对象

public class UserServiceImpl implements UserService{
    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void select() {
        System.out.println("查找了一个用户");
    }

    public void update() {
        System.out.println("修改了一个用户");
    }
}

要添加的日志功能类

//真实业务前
public class BeforeLog implements MethodBeforeAdvice {
    //method: 要执行的目标对象的方法
    //orgs:参数
    //target:目标对象
    public void before(Method method, Object[] orgs, Object target) throws Throwable {
        System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了!");
    }
}
//真实业务后
public class AfterLog implements AfterReturningAdvice {
    //returnValue返回值
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法,反回结果为:"+returnValue);
    }
}

applicationContext.xml

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--注册bean-->
    <bean id="userService" class="com.cmy.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.cmy.log.BeforeLog"/>
    <bean id="afterLog" class="com.cmy.log.AfterLog"/>

    <!--方式一:使用原生的Spring API接口-->
    <!--配置aop:需要导入aop的约束-->
    <aop:config>
        <!--切入点 expression:表达式,execution(要执行的位置,修饰词 返回值 列名 方法名 参数)-->
        <!--com.cmy.service.UserServiceImpl的所有方法,是切入点-->
        <aop:pointcut id="pointcut" expression="execution(* com.cmy.service.UserServiceImpl.*(..))"/>

        <!--执行环绕增强-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>

    </aop:config>


</beans>

补充execution表达式

(* com.cmy.service..*.*(..))
整个表达式可以分为五个部分:
1、execution(): 表达式主体。
2、第一个*号:表示返回类型, *号表示所有的类型。
3、包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包
4、第二个*号:表示类名,*号表示所有的类。
5、*(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
即com.cmy.service包下的所有类的所有方法
(* com.cmy.service.UserServiceImpl.*(..))
特指com.cmy.service.UserServiceImpl类下的所有方法

测试

public class MyTeat {
    public static void main(String[] args) {
       ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
       //动态代理代理的是接口
       UserService userService = (UserService) context.getBean("userService");
       userService.add();
    }
}
//com.cmy.service.UserServiceImpl的add被执行了!
//增加了一个用户
//执行了add方法,反回结果为:null
方式二:使用自定义类实现AOP
public class DiyPointCut {
    public void before(){
        System.out.println("方法执行前");
    }
    public void after(){
        System.out.println("方法执行后");
    }
}

applicationContext.xml

 <!--方式二:自定义类-->
<bean id="diy" class="com.cmy.diy.DiyPointCut"/>

<aop:config>
    <!--自定义切面,ref要引用的类-->
    <aop:aspect ref="diy">
        <!--切入点-->
        <aop:pointcut id="pointcut" expression="execution(* com.cmy.service.UserServiceImpl.*(..))"/>
        <!--通知-->
        <aop:before method="before" pointcut-ref="pointcut"/>
        <aop:after method="after" pointcut-ref="pointcut"/>
    </aop:aspect>
</aop:config>

诚然方式二比方式一简单,但是方式一更加强大,可以拦截参数,比如获取方法名…

方式三:使用注解实现AOP
//使用注解方式实现AOP
@Aspect//标注改类是一个切面
public class AnnocationPointCut {

    @Before("execution(* com.cmy.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("-----方法执行前-----");
    }
    @After("execution(* com.cmy.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("-----方法执行后-----");
    }

    //在环绕增强中,可以给定一个参数,代表要获取的切入点
    @Around("execution(* com.cmy.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        //执行方法
        Object proceed = jp.proceed();
        System.out.println("环绕后");


        /*
        *   可以用于获取日志信息
        *   Signature signature = jp.getSignature();//获得签名
            System.out.println("signature:"+signature);
            System.out.println(proceed);
        * */
    }

}
<!--方式三:-->
<bean id="annocationPointCut" class="com.cmy.diy.AnnocationPointCut"/>
<!--开启注解支持 默认基于JDk实现(proxy-target-class="false")  基于cglib(proxy-target-class="true") -->
<aop:aspectj-autoproxy/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值