web学习笔记19-静态代理,动态代理,spring AOP

1、AOP的原理就是使用动态代理:

首先我们看下为什么要使用动态代理,先看看静态代理有什么问题。
演示一个静态代理的例子:(jdk的代理是基于接口的)
功能就是,保存数据的时候添加事务处理
    //定义接口 PersonDao.java
        public interface PersonDao {
            public void savePerson();
        }
    //定义个实现类 PersonDaoImpl.java
        public class PersonDaoImpl implements PersonDao{
            public void savePerson() {
                System.out.println("save person");
            }
        }
    //定义一个事务类 Transaction.java
        public class Transaction {
            public void beginTransaction(){
                System.out.println("begin transaction");//开启事务
            }

            public void commit(){
                System.out.println("commit");//提交事务
            }
        }
    //定义个代理类 PersonDaoProxy.java
        public class PersonDaoProxy implements PersonDao{ //实现相同的接口
            private PersonDao personDao;
            private Transaction transaction;
            public PersonDaoProxy(PersonDao personDao,Transaction transaction) {
                super();
                this.personDao = personDao;//通过构造传进来
                this.transaction = transaction;
            }

            public void savePerson() {
                this.transaction.beginTransaction();
                this.personDao.savePerson();
                this.transaction.commit();
            }
        }
    //测试 ProxyTest.java
        public class ProxyTest {
            @Test
            public void testProxy(){
                PersonDao personDao = new PersonDaoImpl();
                Transaction transaction = new Transaction();
                PersonDaoProxy proxy = new PersonDaoProxy(personDao, transaction);
                proxy.savePerson();
            }
        }
看看静态代理有什么问题:
a.静态代理没有事务重用,我们还是要在每个方法里面调用this.transaction.beginTransaction();
b.如果我们dao层有100个方法,我们就需要写100个proxy类,变得更麻烦了。接口中定义了多少个方法,
    proxy也要实现多少个方法。
c.如果一个proxy实现了多个接口,如果其中的一个接口发生变化(添加了一个方法),那么proxy也要做相应的改变。
所以静态代理我们维护起来会更加的麻烦。

2、动态代理:

既然静态代理这么多的问题,那么我们看看动态代理。
顾名思义,动态代理就是动态生成Proxy类,不需要我们自己创建Proxy了。
演示下:
    public interface PersonDao {
        public void savePerson();
        public void updatePerson();
    }
    public class PersonDaoImpl implements PersonDao{
        public void savePerson() {
            System.out.println("save person");
        }

        public void updatePerson() {
            System.out.println("update person");
        }
    }
    public class Transaction {
        public void beginTransaction(){
            System.out.println("begin transaction");
        }
        public void commit(){
            System.out.println("commit");
        }
    }

    //最主要的设置拦截器
    public class MyInterceptor implements InvocationHandler{
        private Object target;//目标类
        private Transaction transaction;
        public MyInterceptor(Object target, Transaction transaction) {
            super();
            this.target = target;
            this.transaction = transaction;
        }
        //proxy这个是代表的我们MyInterceptor这个类,千万别写成method.invoke(proxy);
        //否则就死循环,jvm就挂了
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            String methodName = method.getName();
            if("savePerson".equals(methodName)||"updatePerson".equals(methodName)
                    ||"deletePerson".equals(methodName)){
                this.transaction.beginTransaction();//开启事务
                method.invoke(target);//调用目标方法
                this.transaction.commit();//事务的提交
            }else{
                method.invoke(target);
            }
            return null;
        }
    }

    //使用
    public class JDKProxyTest {
        @Test
        public void testJDKProxy(){
            Object target = new PersonDaoImpl();
            Transaction transaction = new Transaction();
            MyInterceptor interceptor = new MyInterceptor(target, transaction);
            /**
             * 参数说明:
             * 1、目标类的类加载器
             * 2、目标类实现的所有的接口
             * 3、拦截器
             */
            PersonDao personDao = (PersonDao)Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                    target.getClass().getInterfaces(), interceptor);
            //personDao.savePerson();
            personDao.updatePerson();
        }
    }
注意几个问题:
a、拦截器的invoke方法是在时候执行的?
    当在客户端,代理对象调用方法的时候,进入到了拦截器的invoke方法。
b、代理对象的方法体的内容是什么?
    拦截器的invoke方法的内容就是代理对象的方法的内容
c、拦截器中的invoke方法中的参数method是谁在什么时候传递过来的?
    代理对象调用方法的时候,进入了拦截器中的invoke方法,所以invoke方法中的参数method就是
    代理对象调用的方法。

我们看看动态代理有什么问题:
a.在拦截器中除了能调用目标对象的目标方法以外,功能是比较单一的,在这个例子中只能处理事务。
    这里可以有个解决方案。比如我们有 Transaction(事务),Log(打印),Access(权限控制)
    我们可以让这几个类同时实现一个接口,我们传一个接口的List过来,然后for循环处理。
    这也是种解决方案。
b.拦截器中的invoke方法的if判断方法名称在真实的开发环境下是不靠谱的,
    因为一旦方法很多if语句需要写很多。
    我有100个方法就要判断100次,而且名称不能写错。
    这个暂时没有好的解决方法,在spring中可以解决。
c.调用时机的问题,我们是在invoke方法之前调用还是在invoke方法之后调用,或者是出异常在调用。

3、基于动态代理中的第一个问题,解决方案:

    //给事务、日志等做了一个抽象,而这个抽象就是Interceptor
    public interface Interceptor {
        public void interceptor();
    }
    //事务实现了接口,其他的也同样的做法
    public class Transaction implements Interceptor{
        public void interceptor() {
            System.out.println("begin transaction");
            System.out.println("commit");
        }
    }
    //拦截器传的是一个 List<Interceptor>
    public class MyInterceptor implements InvocationHandler{
        private Object target;//目标类
        //除了目标类以外的所有的功能都抽象为Interceptor
        private List<Interceptor> interceptors;

        public MyInterceptor(Object target, List<Interceptor> interceptors) {
            super();
            this.target = target;
            this.interceptors = interceptors;
        }

        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            for (Interceptor interceptor : interceptors) {//这样调用了多个功能方法
                interceptor.interceptor();
            }
            method.invoke(target);
            return null;
        }
    }
    //使用
    @Test
    public void testJDKProxy(){
        /**
         * 1、创建一个目标对象
         * 2、创建一个事务
         * 3、创建一个拦截器
         * 4、动态产生一个代理对象
         */
        Object target = new PersonDaoImpl();
        Transaction transaction = new Transaction();
        List<Interceptor> interceptors = new ArrayList<Interceptor>();
        interceptors.add(transaction);//我们可以添加不同的interceptor
        MyInterceptor interceptor = new MyInterceptor(target, interceptors);

        PersonDao personDao = (PersonDao)Proxy.newProxyInstance(target.getClass().getClassLoader(), 
                target.getClass().getInterfaces(), interceptor);
        personDao.updatePerson();
    }

4、cglib动态代理:

我们上面说的是jdk的动态代理,是基于接口的,我们的目标类需要实现接口,代理类就实现相同的接口。
cglib代理是基于子类的。
演示下:
    //首先需要导入cglib的包
    cglib-nodep-2.1_3.jar

    //我们现在就没有接口了
    public class PersonDaoImpl{
        public void savePerson() {
            System.out.println("save person");
        }
        public void updatePerson() {
            System.out.println("update person");
        }
    }
    public class Transaction {
        public void beginTransaction(){
            System.out.println("begin transaction");
        }

        public void commit(){
            System.out.println("commit");
        }
    }

    //我们的拦截器
    public class MyInterceptor implements MethodInterceptor{
        private Object target;//目标类
        private Transaction transaction;

        public MyInterceptor(Object target, Transaction transaction) {
            super();
            this.target = target;
            this.transaction = transaction;
        }

        public Object createProxy(){
            //代码增强类
            Enhancer enhancer = new Enhancer();
            enhancer.setCallback(this);//参数为拦截器
            enhancer.setSuperclass(target.getClass());//生成的代理类的父类是目标类
            return enhancer.create();
        }

        public Object intercept(Object arg0, Method method, Object[] arg2,
                MethodProxy arg3) throws Throwable {
            this.transaction.beginTransaction();
            method.invoke(target);
            this.transaction.commit();
            return null;
        }
    }
    //使用
    //通过cglib产生的代理对象,代理类是目标类的子类
    public class CGLibProxyTest {
        @Test
        public void testCGlib(){
            Object target = new PersonDaoImpl();
            Transaction transaction = new Transaction();
            MyInterceptor interceptor = new MyInterceptor(target, transaction);
            //使用拦截器创建一个代理的对象
            PersonDaoImpl personDaoImpl = (PersonDaoImpl)interceptor.createProxy();
            personDaoImpl.savePerson();
        }
    }

使用cglib的好处是我们目标类不需要实现接口就可以完成动态代理功能了。

讲了这么多,还是没解决多if判断等问题。

5、AOP的一些基本概念:

这里写图片描述

这里写图片描述

6、spring AOP的简单例子:

    public interface PersonDao {
        public void savePerson();
    }
    public class PersonDaoImpl implements PersonDao{
        public void savePerson() {
            System.out.println("save person");
        }
    }
    //切面
    public class Transaction {
        public void beginTransaction(){
            System.out.println("begin transaction");
        }

        public void commit(){
            System.out.println("commit");
        }
    }
    public class TransactionTest {
        @Test
        public void testTransaction(){
            ApplicationContext context = 
                    new ClassPathXmlApplicationContext("applicationContext.xml");
            PersonDao personDao = (PersonDao)context.getBean("personDao");
            personDao.savePerson();
        }
    }
    //xml配置
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/aop 
               http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
        <bean id="personDao" class="com.example.spring.aop.xml.transaction.PersonDaoImpl"></bean>
        <bean id="transaction" class="com.example.spring.aop.xml.transaction.Transaction"></bean>

        <aop:config>
            <!-- 
                切入点表达式  确定目标类
             -->
            <aop:pointcut 
                expression="execution(* com.example.spring.aop.xml.transaction.PersonDaoImpl.*(..))" 
                id="perform"/>
            <!-- 
                ref指向的对象就是切面
             -->
            <aop:aspect ref="transaction">
                <aop:before method="beginTransaction" pointcut-ref="perform"/>
                <aop:after-returning method="commit" pointcut-ref="perform"/>
            </aop:aspect>
        </aop:config>
    </beans>
    //测试
    public class TransactionTest {
        @Test
        public void testTransaction(){
            ApplicationContext context = 
                    new ClassPathXmlApplicationContext("applicationContext.xml");
            PersonDao personDao = (PersonDao)context.getBean("personDao");
            personDao.savePerson();
        }
    }

7、spring AOP的原理:

    a、当spring容器启动的时候,加载两个bean,对两个bean进行实例化。
    b、当spring容器对配置文件解析到<aop:config>的时候,
        把切入点表达式解析出来,按照切入点表达式匹配spring容器内容的bean
    c、如果匹配成功,则为该bean创建代理对象
    d、当客户端利用context.getBean获取一个对象时,如果该对象有代理对象,则返回代理对象
        如果没有代理对象,则返回对象本身

8、spring AOP的各种通知:

前置通知:在目标方法执行之前
后置通知:在目标方法执行之后,方法抛出异常以后是不会执行的。
最终通知:类似finally功能,无论目标方法是否抛出异常都将执行。
异常通知:接受目标方法抛出的异常
环绕通知:joinPoint.proceed();这个代码如果在环绕通知中不写,则目标方法不再执行。
    可以控制目标方法的执行。

简单的演示下:
    public interface PersonDao {
        public String savePerson();
    }
    public class PersonDaoImpl implements PersonDao{
        public String savePerson() {
            int a = 1/0;//构造一个异常
            System.out.println("save person");
            return "aaa";
        }
    }

    //切面
    public class Transaction {
        //前置通知
        public void beginTransaction(JoinPoint joinPoint){
            String methodName = joinPoint.getSignature().getName();
            System.out.println("连接点的名称:"+methodName);
            System.out.println("目标类:"+joinPoint.getTarget().getClass());
            System.out.println("begin transaction");
        }

        //后置通知,可以使用xml定义的val接受目标方法的返回值
        public void commit(JoinPoint joinPoint,Object val){
            System.out.println("目标方法的返回值:"+val);
            System.out.println("commit");
        }

        //最终通知
        public void finallyMethod(){
            System.out.println("finally method");
        }

        //异常通知,注意 ex 是在xml文件中定义的
        public void throwingMethod(JoinPoint joinPoint,Throwable ex){
            System.out.println(ex.getMessage());
        }

        //环绕通知
        public void aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable{
            System.out.println("aaaa");
            joinPoint.proceed();//调用目标方法
        }
    }
xml的配置:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans 
               http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
               http://www.springframework.org/schema/aop 
               http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">
        <bean id="personDao" class="com.example.spring.aop.xml.transaction.PersonDaoImpl"></bean>
        <bean id="transaction" class="com.example.spring.aop.xml.transaction.Transaction"></bean>

        <aop:config>
            <aop:pointcut //切入点
                expression="execution(* com.example.spring.aop.xml.transaction.PersonDaoImpl.*(..))" 
                id="perform"/>
            <!-- 
                ref指向的对象就是切面
             -->
            <aop:aspect ref="transaction">
                <aop:before method="beginTransaction" pointcut-ref="perform"/>
                <aop:after-returning method="commit" pointcut-ref="perform" returning="val"/>
                <aop:after method="finallyMethod" pointcut-ref="perform"/>
                <aop:after-throwing method="throwingMethod" throwing="ex" pointcut-ref="perform"/>//定义了一个ex的异常
                <aop:around method="aroundMethod" pointcut-ref="perform"/>
            </aop:aspect>
        </aop:config>
    </beans>

注意:前置通知和后置通知能在目标方法的前面和后面加一些代码,但是不能控制目标方法的执行,环绕通知可以控制。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值