美文网首页SSM
SSM框架系列学习总结2之Spring AOP

SSM框架系列学习总结2之Spring AOP

作者: 梦蓝樱飞2020 | 来源:发表于2018-01-20 23:04 被阅读5次

    先整理AOP之前, 我先把之前DI的内容整理完!

    DI

    Spring的依赖注入第二种方式:

    通过构造方法注入属性
    首先提供一个实体类, 不过这个类没有属性的get和set方法, 只有一个有参的构造方法和toString方法.

    public class Boy {
        private String id;
        private String name;
        private Integer age;
        private Double salary;
    
        public Boy(String id, String name, Integer age, Double salary) {
            System.out.println("IOC容器创建对象");
            this.id = id;
            this.name = name;
            this.age = age;
            this.salary = salary;
        }
    
        @Override
        public String toString() {
            return "Boy{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    ", age=" + age +
                    ", salary=" + salary +
                    '}';
        }
    }
    

    Spring配置

        <!--
            通过constructor实现依赖注入
            constructor-arg: 通过构造方法传过去的值
            type: 用来限制传递的参数的类型
        -->
        <bean id="boyId" class="com.wtu.spring.di.constructor.Boy">
            <!--
            <constructor-arg type="java.lang.String">
                <value>001</value>
            </constructor-arg>
            <constructor-arg type="java.lang.String">
                <value>小阳</value>
            </constructor-arg>
            <constructor-arg type="java.lang.Integer">
                <value>16</value>
            </constructor-arg>
            <constructor-arg type="java.lang.Double">
                <value>1000</value>
            </constructor-arg>
            -->
    
            <!-- index: 参数在构造方法中的位置, 第一个参数从0开始 -->
            <constructor-arg index="0">
                <value>001</value>
            </constructor-arg>
            <constructor-arg index="1">
                <value>小阳</value>
            </constructor-arg>
            <constructor-arg index="2">
                <value>16</value>
            </constructor-arg>
            <constructor-arg index="3">
                <value>1000</value>
            </constructor-arg>
        </bean>
    

    测试类:

            // 启动IOC容器
            ApplicationContext ac = new ClassPathXmlApplicationContext(
                    new String[]{"com/wtu/spring/di/constructor/spring3.0.xml"}
            );
    
            Boy boy = (Boy) ac.getBean("boyId");
            System.out.println(boy);
            //Boy{id='001', name='小阳', age=16, salary=1000.0}
    

    注入Date类型:
    在依赖注入中 如何将一个字符串转换成Date对象, 注入到目标对象

    public class Boy {
        private String id;
        private String name;
        private Integer age;
        private Date birthday;
        private Double salary;
    
        public Boy() {
            System.out.println("Ioc容器创建对象");
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Date getBirthday() {
            return birthday;
        }
    
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
    
        public Double getSalary() {
            return salary;
        }
    
        public void setSalary(Double salary) {
            this.salary = salary;
        }
    
        @Override
        public String toString() {
            return "Boy{" +
                    "id='" + id + '\'' +
                    ", name='" + name + '\'' +
                    ", birthday=" + birthday +
                    ", age=" + age +
                    ", salary=" + salary +
                    '}';
        }
    }
    

    Spring配置

        <!-- 注册时间格式转换器
            -->
        <bean id="format" class="java.text.SimpleDateFormat">
            <constructor-arg>
                <!-- 时间的格式样式 -->
                <value>yyyy-MM-dd</value>
            </constructor-arg>
        </bean>
    
        <!-- 注册bean对象
            new SimpleDateFormat("yyyy-MM-dd").parse("2017-12-12"); 
        -->
        <bean id="boy" class="com.wtu.spring.di.set.Boy">
            <property name="birthday">
                <bean factory-bean="format" factory-method="parse">
                    <constructor-arg>
                        <value>2017-12-12</value>
                    </constructor-arg>
                </bean>
            </property>
        </bean>
    

    测试类:

            // 启动IOC容器
            ApplicationContext ac = new ClassPathXmlApplicationContext(
                    new String[]{"com/wtu/spring/di/set/spring3.0.xml"}
            );
    
            Boy boy = (Boy) ac.getBean("boy");
            System.out.println(new SimpleDateFormat("yyyy-MM-dd").parse("2017-12-12"));
            //Tue Dec 12 00:00:00 CST 2017
            System.out.println(boy);
            //Boy{id='null', name='null', birthday=Tue Dec 12 00:00:00 CST 2017, age=null, salary=null}
    
    DI注解形式
    结构.png

    UserDaoImpl.java:

    public class UserDaoImpl implements UserDao {
        public UserDaoImpl() {
            System.out.println("IOC容器创建对象");
        }
    
        @Override
        public void addUser() {
            System.out.println("添加用户");
        }
    }
    

    UserServiceImpl.java:

    public class UserServiceImpl implements UserService {
        private UserDao userDao;
    
        public UserServiceImpl() {
            System.out.println("IOC容器创建UserServiceImpl对象");
        }
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void addUser() {
            System.out.println("service中addUser方法被调用了");
        }
    }
    

    Spring配置:

        <!--注册UserDao对象 -->
        <bean id="userDao" class="com.wtu.spring.di.annotation.UserDaoImpl"/>
    
        <!--注册UserService对象 -->
        <bean id="userService" class="com.wtu.spring.di.annotation.UserServiceImpl">
            <property name="userDao" ref="userDao"/>
        </bean>
    

    测试类:

            // 启动IOC容器
            ApplicationContext ac = new ClassPathXmlApplicationContext(
                    new String[]{"com/wtu/spring/di/annotation/spring3.0.xml"}
            );
    
            UserService us = (UserService) ac.getBean("userService");
    

    运行结果:


    结果.png

    注解改写:
    后来的Spring配置文件:

    <?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:context="http://www.springframework.org/schema/context"
    
           xsi:schemaLocation="http://www.springframework.org/schema/beans
               http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 开启Spring注解的功能 -->
        <context:annotation-config/>
        <!-- 让springIOC容器自动去扫描某一个包, 创建该包下的bean对象 -->
        <context:component-scan base-package="com.wtu.spring.di.annotation2"/>
    
    </beans>
    

    UserDaoImpl.java:

    /**
     * @Component(value = "userDao")
     *  告诉SpringIOC容器创建一个id为userDao的bean对象
     *  value可以省略, 但是此时生成的id为类名, 并且首字母小写
     */
    //@Component(value = "userDao") // userDaoImpl
    @Repository(value = "userDao")
    public class UserDaoImpl implements UserDao {
        public UserDaoImpl() {
            System.out.println("IOC容器创建对象");
        }
    
        @Override
        public void addUser() {
            System.out.println("添加用户");
        }
    }
    

    UserServiceImpl.java:

    @Service(value = "userServiceImpl")
    public class UserServiceImpl implements UserService {
        @Resource(name = "userDao")
    //    @Autowired
        private UserDao userDao;
    
        public UserServiceImpl() {
            System.out.println("IOC容器创建UserServiceImpl对象");
        }
    
        /*
            spring会自动查找id为userDao的bean对象, 如果找到就通过下面的set方法注入进来
         */
    //    @Resource(name = "userDao")
    //    public void setUserDao(UserDao userDao) {
    //        this.userDao = userDao;
    //    }
    
        @Override
        public void addUser() {
            System.out.println("service中addUser方法被调用了");
        }
    }
    

    测试类:

            // 启动IOC容器
            ApplicationContext ac = new ClassPathXmlApplicationContext(
                    new String[]{"com/wtu/spring/di/annotation2/spring3.0.xml"}
            );
    
            UserService us = (UserService) ac.getBean("userServiceImpl");
            us.addUser();
    

    运行结果:

    结果.png
    注意: @Component现在不提倡使用
    对于注册bean对象的注解现在提倡使用如下三个:
    @Controller 一般作用在springMVC中
    @Service 作用在业务层
    @Repository 作用在持久层
    但是不是绝对的, 任何一个在任何一层都可以用

    AOP

    AOP.jpeg AOP.jpeg AOP.jpeg

    如图上述的解决方案不好:
    在javaweb三层框架中,dao层是数据访问层,它的核心业务就是操作数据库,然而像输出时间,开启事务, 日志输出等等。这些事情都不是核心业务,我们可以称之为服务代码,我们不能将执行服务代码这样的事情交给dao层来做。但是我们又需要这样的服务代码。所以我们可以找该类的一个代理对象来实现。

    动态代理图解:


    动态代理图解.png
    动态代理核心代码模拟实现

    日志类, 切面:

    public class MyLog {
        @SuppressWarnings("deprecation")
        public void writeConsole() {
            System.out.println(new Date().toLocaleString());
        }
    }
    

    目标对象所在类, 执行核心代码:

    public class UserDaoImpl implements UserDao {
    
        @Override
        public void addUser() {
            System.out.println("添加用户...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        @Override
        public void deleteUser() {
            System.out.println("删除用户");
        }
    }
    

    模拟JDK动态代理的Proxy类:

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 中间类, 相当于JDK动态代理中的Proxy
     * @Author menglanyingfei
     * @Created on 2018.01.17 16:51
     */
    public class Middle {
        private UserDao userDao = new UserDaoImpl();
        // 定义切面
        private MyLog myLog = new MyLog();
    
        public Object getObject() {
            /*
            第一个参数: 当前类的类加载器
            第二个参数: 代理对象实现的接口类型
            第三个参数: 是个接口, 在这个接口中具体去处理如何调用切面以及目标对象
                匿名内部类
             */
            return Proxy.newProxyInstance(
                    this.getClass().getClassLoader(),
                    userDao.getClass().getInterfaces(),
                    new InvocationHandler() {
                        /**
                         *
                         * @param proxy 目标对象
                         * @param method 需要执行目标对象的方法对应的Method对象
                         * @param args 执行目标对象业务方法时需要的参数
                         * @return
                         * @throws Throwable
                         */
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            // 定义返回值, 目标对象方法执行以后的返回值
                            Object result = "";
                            // 获取目标对象的方法名
                            String methodName = method.getName();
                            if ("addUser".equals(methodName)) {
                                myLog.writeConsole();
                                result = method.invoke(userDao, args);
                                myLog.writeConsole();
                            } else {
                                result = method.invoke(userDao, args);
                            }
                            return result;
                        }
                    }
            );
        }
    
    }
    

    测试类:

            // 通过一个中间类来获取代理对象
            Middle middle = new Middle();
            UserDao userDao = (UserDao) middle.getObject();
            userDao.addUser();
    
    
            System.out.println(userDao);
            //com.wtu.spring.aop.base.UserDaoImpl@5305068a
            // 红色标识显示
            System.err.println(userDao.getClass());
            // 使用JDK动态代理产生的代理对象
            //class com.sun.proxy.$Proxy0
    

    运行结果:


    运行结果.png

    导入aop的所需jar包:


    jar.png
    SpringAOP的五种通知方式

    通知就是告诉代理在什么时候去执行服务代码。
    1.前置通知: 执行目标对象业务方法之前执行服务代码(切面)
    2.后置通知: 执行目标对象业务方法之后执行服务代码
    3.方法正常返回通知:
    4.方法抛出异常通知:
    5.环绕通知:

    由于晚上太想睡觉了, 所以只先总结到前置通知, 把剩下的内容留到下次总结吧!
    先弄一下前置通知吧!
    先定义一个切面: 输出日志时间类

    package com.wtu.spring.aop.before;
    
    import java.util.Date;
    
    /**
     * 切面
     * @Author menglanyingfei
     * @Created on 2018.01.17 17:06
     */
    public class MyLog {
        @SuppressWarnings("deprecation")
        public void writeConsole() {
            System.out.println(new Date().toLocaleString());
        }
    }
    

    目标对象所在类的核心业务代码:

    public class UserDaoImpl implements UserDao {
    
        @Override
        public void addUser() {
    
    //        if (true) {
    //            throw new RuntimeException("测试一下遇到异常执行情况");
    //        }
    
            System.out.println("添加用户...");
        }
    
        @Override
        public void deleteUser() {
            System.out.println("删除用户");
        }
    }
    

    Spring配置:

    <?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-3.0.xsd
    
               http://www.springframework.org/schema/context
               http://www.springframework.org/schema/context/spring-context-3.0.xsd
    
               http://www.springframework.org/schema/aop
               http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
               ">
    
        <!-- 注册UserDao对象 -->
        <bean id="userDao" class="com.wtu.spring.aop.before.UserDaoImpl">
        </bean>
    
        <!-- 配置切面 -->
        <bean id="myLog" class="com.wtu.spring.aop.before.MyLog"/>
    
        <!--
            aop:config: 配置代理对象
            proxy-target-class: 指底层的动态代理使用的是JDK, 还是CGLIB代理;
                true表示CGLIB代理, CGLIB代理不是说没有接口, spring会根据目标对象自动生成一个接口
            aop:pointcut: 配置切入点
            execution: 确定切入点的方法
            aop:aspect: 配置切面如何去切切入点
            pointcut-ref: 切面中的方法去切哪个目标对象中的切入点
        -->
        <aop:config proxy-target-class="true">
            <aop:pointcut expression="execution(* com.wtu.spring.aop.before.UserDaoImpl.a*(..))" id="xxx"/>
    
            <aop:aspect ref="myLog">
                <aop:before method="writeConsole" pointcut-ref="xxx"/>
            </aop:aspect>
        </aop:config>
    
    </beans>
    

    测试类:

    package com.wtu.spring.aop.before;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * @Author menglanyingfei
     * @Created on 2018.01.17 14:22
     */
    public class TestSpringIoc {
        public static void main(String[] args) {
            // 启动Ioc容器
            ApplicationContext ac = new ClassPathXmlApplicationContext(
                    new String[]{"com/wtu/spring/aop/before/spring3.0.xml"}
            );
    
            UserDao userDao = (UserDao) ac.getBean("userDao");
            userDao.addUser();
    
            System.out.println(userDao.getClass());
            //class com.wtu.spring.aop.before.UserDaoImpl$$EnhancerByCGLIB$$aa48c241
        }
    }
    

    运行结果:


    运行结果.png

    完整代码地址见Github

    https://github.com/menglanyingfei/SSMLearning/tree/master/spring_day02

    相关文章

      网友评论

        本文标题:SSM框架系列学习总结2之Spring AOP

        本文链接:https://www.haomeiwen.com/subject/vfcxaxtx.html