设计模式之(动态)代理模式

OOP和AOP的关系!!!

OOP   面向对象编程           重要的一点是继承复用(有了继承才可以复用)

AOP   面向切面的编程        也是为了复用(当没有继承关系时候,但是有一些共同的方法需要实现,此时通过代理复用)

 

 

静态代理的缺点

  • 必须先有真实角色才有代理;

  • 一有需求就写代理,累计多了代理就多了,就会造成类的膨胀

 

所以就有了动态代理的出现(动态代理:可以在不同角色间转换,一会为卖车中介一会为卖方中介)

 

动态代理

<--组件扫描器   扫描com.spring下的所有类-->
<--这是IOC部分-->
<context:component-scan base-package="com.spring"></context:component-scan>

<--AOP部分(aop:aspectj-autproxy意为自动代理)-->
<--注解AOP驱动-->
</aop:aspectj-autproxy>
//spring进行代理类的实现,就不需要Proxy这个类了,需要一个切面类
public interface StudentDao {

    public void insert(Student student);

    public void delete(int id);

}
@Repository
public class StudentDaoImpl implements StudentDao {
    public void insert(Student student) {
        System.out.println("student insert");

    }

    public void delete(int id) {
        System.out.println("student delete ");
    }
}
//  把类注入到spring容器中(IOC方面的功能)
@Component
//此注解为了告知spring此类为切面(进行前后置等方法的设定)
@Aspect
//当有多个@Before时候,可以通过Order注解来设置每个@Before的方法执行的顺序( 设置优先级,值越低优先级越高)
@Order(1)
public class CheckSercunityAop {

    //前置通知(前置在哪里?在此包下的此方法,同时需要写参数是什么,“..”为任意参数。“*”为任意返回值)
    //如果想要insert方法也加前置,有俩种方式:
    //     1、@Before("exection(* com.spdb.dynamictwentyfive.StudentDaoImpl.*(..))")
    //     2、@Before("exection(* com.spdb.dynamictwentyfive.StudentDaoImpl.delete(..)) 
    //              || exection(* com.spdb.dynamictwentyfive.StudentDaoImpl.insert(..))")
    @Before("exection(* com.spdb.dynamictwentyfive.StudentDaoImpl.delete(..))")
    //如果需要安全认证,就可以在代理中进行安全认证方法的添加(代理角色可以有附加的功能,比如房产中介可以涨房租)
    public void checkSecurity(){
        System.out.println("check name and pasword");
    }

}
public class Client {


        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            //此处为获取bean中的对象,第一个参数为bean中的id,第二个为对象类型
            //当不写id的时候,默认的类名为类名首字母小写
            StudentDao student = context.getBean("studentDaoImpl",StudentDao.class);
            student.delete(1);
            

        }
}

 运行结果为

student delete
check name and pasword

 

重点:  

@Before前置通知

           执行此方法之前执行。(用@Before标识的方法为前置方法,在目标方法的执行之前执行,即在连接点之前进行执行。)

@After后置通知

           执行此方法之后执行。(后置方法在连接点方法完成之后执行,无论连接点方法执行成功还是出现异常,都将执行后置方法。)

@AfterRunning返回通知

          当当前方法(连接点方法)成功执行后,返回调用此方法的方法(通知方法)时候才会执行。(当连接点方法成功执行后,返回通知方法才会执行,如果连接点方法出现异常,则返回通知方法不执行。返回通知方法在目标方法执行成功后才会执行,所以,返回通知方法可以拿到目标方法(连接点方法)执行后的结果。)

@AfterThrowing异常通知

           发生异常时候执行。(异常通知方法只在连接点方法出现异常后才会执行,否则不执行。在异常通知方法中可以获取连接点方法出现的异常。)

/*通过throwing属性指定连接点方法出现异常信息存储在ex变量中,在异常通知方法中就可以从ex变量中获取异常信息了*/ @AfterThrowing(value="execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))", throwing="ex")

@Around环绕通知    此为包含楼上四个通知

 

   try{

       @Before
               delete();
        @After   正常运行结束后才会到这个

    }catch(Exception e){

        @AfterThrowing


    }finally{

       @AfterRunning
    }

 

详细解析环绕通知(?):

       环绕通知方法可以包含上面四种通知方法,环绕通知的功能最全面。环绕通知需要携带 ProceedingJoinPoint 类型的参数,且环绕通知必须有返回值, 返回值即为目标方法的返回值。在切面类中创建环绕通知方法,示例如下:

@Around("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))")
    public Object aroundMethod(ProceedingJoinPoint pdj){
        /*result为连接点的放回结果*/
        Object result = null;
        String methodName = pdj.getSignature().getName();

        /*前置通知方法*/
        System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()));

        /*执行目标方法*/
        try {
            result = pdj.proceed();

            /*返回通知方法*/
            System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result);
        } catch (Throwable e) {
            /*异常通知方法*/
            System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e);
        }

        /*后置通知*/
        System.out.println("后置通知方法>目标方法名" + methodName);

        return result;
    }
}

测试方法为:

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml");
        ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
        arithmetic.add(3, 2);
        arithmetic.div(4, 0);       
    }
}

运行测试方法:

public class Main {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("bean-aop.xml");
        ArithmeticCalculator arithmetic = (ArithmeticCalculator) ctx.getBean("arithmeticCalculator");
        arithmetic.add(3, 2);
        arithmetic.div(4, 0);       
    }
}

运行测试方法,输出结果:

前置通知方法>目标方法名:add,参数为:[3, 2]
add->result:5
返回通知方法>目标方法名add,返回结果为:5
后置通知方法>目标方法名add
前置通知方法>目标方法名:div,参数为:[4, 0]
异常通知方法>目标方法名div,异常为:java.lang.ArithmeticException: / by zero
后置通知方法>目标方法名div
Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int lzj.com.spring.aop.ArithmeticCalculator.div(int,int)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219)
    at com.sun.proxy.$Proxy7.div(Unknown Source)
    at lzj.com.spring.aop.Main.main(Main.java:12)

从输出结果中可以看出,环绕通知实现了上面几种通知的结合。
当div目标方法出现异常时,在环绕通知方法中已经用try…catch方法进行捕捉了,为什么最后输出结果中还出现了一个返回类型不匹配的错误:

Exception in thread "main" org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public abstract int lzj.com.spring.aop.ArithmeticCalculator.div(int,int)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:219)
    at com.sun.proxy.$Proxy7.div(Unknown Source)
    at lzj.com.spring.aop.Main.main(Main.java:12)

那是因为在环绕通知方法中开始就定义了目标方法的返回结果
Object result = null。当目标方法出现异常时,result = pdj.proceed();执行时出现异常,此时result中还是null,所以在环绕通知方法最后return result;时,返回的result就是null,但是环绕通知的返回类型我们定义的是Object类型的,null不能转化为Object类型,所以抛出了个类型转换的错误。我们可以在环绕通知方法中把异常抛出去,即为:

@Around("execution(public int lzj.com.spring.aop.ArithmeticCalculator.*(int, int))")
    public Object aroundMethod(ProceedingJoinPoint pdj){
        /*result为连接点的放回结果*/
        Object result = null;
        String methodName = pdj.getSignature().getName();

        /*前置通知方法*/
        System.out.println("前置通知方法>目标方法名:" + methodName + ",参数为:" + Arrays.asList(pdj.getArgs()));

        /*执行目标方法*/
        try {
            result = pdj.proceed();

            /*返回通知方法*/
            System.out.println("返回通知方法>目标方法名" + methodName + ",返回结果为:" + result);
        } catch (Throwable e) {
            /*异常通知方法*/
            System.out.println("异常通知方法>目标方法名" + methodName + ",异常为:" + e);
            /*当环绕通知方法本身还有其它异常时,非连接点方法出现的异常,此时抛出来*/
            throw new RuntimeException();
        }

        /*后置通知*/
        System.out.println("后置通知方法>目标方法名" + methodName);

        return result;
    }
}

在输出结果中会抛出一个运行时异常java.lang.RuntimeException

插曲:不可以在执行目标方法时在定义result变量:

        ……
        /*执行目标方法*/
        try {
            Object result = pdj.proceed();
            ……
        } catch (Throwable e) {
            ……
        }
        ……
        return result;

这种方法是行不通的,在Object result = pdj.proceed();中,如果pdj.proceed()执行失败,就会被try …catch捕获到异常,而不会就不会执行定义result变量那一步了,即Object result不会执行,所以在return result;就会出现错误。

 

 

个性签名:一个人在年轻的时候浪费自己的才华与天赋是一件非常可惜的事情

        如果觉得这篇文章对你有小小的帮助的话,记得在左下角点个“👍”哦,博主在此感谢!

 

万水千山总是情,打赏5毛买辣条行不行,所以如果你心情还比较高兴,也是可以扫码打赏博主,哈哈哈(っ•̀ω•́)っ✎⁾⁾! 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值