二、Spring

Spring

Spring简介
  • 2003年兴起的一门轻量级的JavaEE企业级应用开发的框架技术,Spring框架致力于全面的简化Java企业级开发,Spring框架会贯穿我们整个Web应用开发的每一个层面,也可以整合和管理其他的框架产品,可以称之为管理框架的框架
  • MVC:
    • View:HTML、JSP
    • Controller:Servlet、Struts2、SpringMVC
    • Model:JDBC、Mybatis
  • Spring框架中使用了很多的设计模式(设计模式是代码在实际开发中经验性的总结):单例模式、工厂模式、代理模式、适配器模式、观察者模式、策略模式、装饰器模式等
    • 三大类型设计模式:创建性模式、行为性模式,结构性模式
    • 23种设计模式都遵循了设计模式等六大原则,六大原则中有一个核心的原则:开闭原则
      • 里氏替换原则、接口隔离原则、迪米特法则、依赖倒置原则、开闭原则、单一原则
      • 开闭原则:代码对扩展开放,对修改关闭
工厂设计模式小案例
  • 工厂设计模式专注于对象的创建,目的是简化对象的创建,让项目更方便的解耦合

  • 主要目的是通过工厂类生产对象

  • 工厂实践步骤

    • 编写工厂配置文件:配置需要工厂创建的对象的基本信息
    • 创建工厂,从工厂中创建对象
  • 优点

    • 将一些对象创建的过程封装在工厂类中,使用时只需要从工厂获取即可
    • 使对象的创建变得更加灵活
  • 代码实现

    //applicationContext.properties
    userDao=com.mo.factorys.UserDaoImpl
    userService=com.mo.factorys.UserServiceImpl
      
      
    //MyFactoryBean
    //提供调用者需要的对象
    public class MyFactoryBean {
        //加载流中的信息保存到prop中
        private Properties prop = new Properties();
        //利用构造方法初始化
        public MyFactoryBean(String path) {
            InputStream in = null;
            try {
                //用类加载时的流读取配置文件
                in = MyFactoryBean.class.getResourceAsStream(path);
                prop.load(in);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        
        public Object getBean(String beanId) throws Exception {
            //获取实现类全限定名
            String className = prop.getProperty(beanId);
            //用类的全限定名获取类对象
           Class<?> aClass= Class.forName(className);
            Object newInstance = aClass.newInstance();
            return newInstance;
        }
    }
    
    
    //UserServiceImpl
    public class UserServiceImpl implements UserService {
        private UserDao userDao;
        
        public UserServiceImpl() throws Exception {
            userDao = (UserDao) new MyFactoryBean("/com/mo/conf/applicationContext.properties").getBean("userDao");
        }
        
        @Override
        public void regist() {
            System.out.println("===regist===");
            userDao.insertUser();
        }
    }
    
    
    @Test
    public void test2() throws Exception {
            //初始化工厂
            MyFactoryBean factoryBean = new MyFactoryBean("/com/mo/conf/applicationContext.properties");
            //获取对象
            UserService userService = (UserService) factoryBean.getBean("userService");
            userService.regist();
    }
    
    
Spring框架小案例
  • 引入依赖

  • 引入配置文件:配置文件applicationContext.xml位置没有强制要求

  • 从工厂获取对象

    • 两个初始化工厂的API
      • Java环境:ClassPathXmlApplicationContext
      • JavaWeb环境:WebXmlApplicationContext
  • 代码实现

    //applicationContext.xml
    <beans ...
        <!--声明让spring工厂创建一个对象
        一个bean标签代码让工厂创建一个对象
        id:全局唯一,通过工厂获取对象的依据
        class:属性值指定类的全限定名-->
        <bean id="emp1" class="com.mo.entity.Emp"></bean>
    </beans>
    
    @Test
    public void test0(){
            //初始化工厂
            ClassPathXmlApplicationContext context =
                    new ClassPathXmlApplicationContext("applicationContext.xml");
            //获取对象
            // Emp emp1 = (Emp) context.getBean("emp1");
            //两种写法,第一个参数:配置文件中bean标签的id,第二个参数为目标获取对象的类型
            Emp emp1 = context.getBean("emp1",Emp.class);
            System.out.println(emp1);
    }
    

Spring注入 —> Injection
  • 指Spring工厂在创建对象的同时,可以给对象的属性注入值

  • set注入:本质是通过调用属性对应的set方法实现值的注入

    • 给八种基本数据类型和字符串注入

      <bean id="emp1" class="com.mo.entity.Emp">
              <!--注入
              name:对应类中属性名
              value:对应属性的赋值-->
              <property name="id" value="1"></property>
              <property name="name" value="mo"></property>
              <property name="age" value="18"></property>
              <property name="salary" value="20000"></property>
      </bean>
      
    • 自定义对象类型属性

      <!--自定义对象类型属性值注入-->
          <bean id="dept1" class="com.mo.entity.Dept">
              <property name="id" value="1"></property>
              <property name="deptName" value="药学"></property>
          </bean>
          <bean id="emp2" class="com.mo.entity.Emp">
              <property name="id" value="2"></property>
              <!--注入自定义对象类型属性值用ref-->
              <property name="dept" ref="dept1"></property>
          </bean>
      
    • List集合类型属性

      <!--list集合泛型为String-->
      <property name="strList">
              <list>
              <!--一个value标签代表添加一个元素到集合,仅限于八种基本数据类型和字符串-->
                      <value>mo</value>
                      <value>yuan</value>
              </list>
      </property>
      <!--list集合泛型为自定义类型-->
      <property name="deptList">
              <list>
                      <!--一个ref标签代表添加一个自定义对象元素到集合,bean属性值书写对应bean标签的id-->
                      <ref bean="dept1"></ref>
                      <ref bean="dept2"/>
              </list>
      </property>
      
    • Set集合类型属性

      <!--set集合注入,泛型为String-->
      <property name="strSet">
              <set>
                      <value>mo</value>
                      <value>yuan</value>
              </set>
      </property>
      <!--set集合注入,泛型为自定义对象-->
      <property name="deptSet">
              <set>
                      <ref bean="dept1"></ref>
                      <ref bean="dept2"></ref>
              </set>
      </property>
      
    • Map集合类型属性

      <!--map集合,键值对为String-->
      <property name="strMap">
              <map>
                      <!--一个entry代表Map里的一个键值对-->
                      <entry key="k1" value="mo"></entry>
                      <entry key="k2" value="yuan" />
              </map>
      </property>
      <!--map集合,值为自定义类型-->
      <property name="deptMap">
              <map>
                      <entry key="d1" value-ref="dept1"></entry>
                      <entry key="d2" value-ref="dept2"></entry>
              </map>
      </property>
      
    • Properties类型属性

      <!--properties类型属性-->
      <property name="prop">
              <props>
                      <!--一个prop标签代表添加一个键值对-->
                      <prop key="driver">oracle.jdbc.OracleDriver</prop>
                      <prop key="name">hr</prop>
              </props>
      </property>
      
  • 默认情况下Spring自动调用无参构造方法构造对象

  • 构造注入:通过调用指定的构造方法完成属性值注入

    <bean id="emp" class="com.mo.entity.Emp">
            <!--一个constructor-arg标签对应一个构造方法参数
            name:匹配构造方法的参数变量名
            value:对应参数的值-->
            <constructor-arg name="id" value="1"></constructor-arg>
            <constructor-arg name="name" value="mo"></constructor-arg>
            <constructor-arg name="age" value="18"></constructor-arg>
            <constructor-arg name="salary" value="20000"></constructor-arg>
    </bean>
    
    <bean id="emp1" class="com.mo.entity.Emp">
            <!--一个constructor-arg标签对应一个构造方法参数
            index:匹配构造方法的参数下标
            value:对应参数的值-->
            <constructor-arg index="0" value="1"></constructor-arg>
            <constructor-arg index="1" value="mo"></constructor-arg>
            <constructor-arg index="2" value="18"></constructor-arg>
            <constructor-arg index="3" value="20000"></constructor-arg>
    </bean>
    
    <!--当构造方法重载时,下标中容易出现歧义,可以通过type属性来指定想要调用的构造方法-->
    <constructor-arg index="0" type="java.lang.Integer" value="1"></constructor-arg>
    <constructor-arg index="1" type="java.lang.String" value="mo"></constructor-arg>
    
  • 自动注入:只适用于自定义对象类型的属性

    //class必须是实现类,不能为接口
    <bean id="orderDao" class="com.mo.dao.OrderDaoImpl"></bean>
        <!--autiwire:代表自动注入
        属性值:
            byName:指Spring到工厂中寻找与成员变量名匹配(相同)的beanId,拿到对应的对象注入
            byType:指Spring到工厂寻找与成员变量类型匹配的bean,完成注入-->
        <bean id="orderService" class="com.mo.service.OrderServiceImpl" autowire="byType">
            <!--<property name="orderDao" ref="orderDao"></property>-->
    </bean>
    
    //OrderServiceImpl
    public class OrderServiceImpl implements OrderService{
        private OrderDao orderDao;
        
        @Override
        public void createOrder() {
            System.out.println("service create order");
            orderDao.createOrder();
        }
        
        public void setOrderDao(OrderDao orderDao) {
            this.orderDao = orderDao;
        }
    }
    
IOC 和 DI
  • Inverse Of Control,控制反转:将创建对象的权利由程序员反转给Spring工厂容器管理
    • Servlet实现类的对象,是由tomcat容器管理创建
  • Dependency Injection,依赖注入:Spring工厂创建对象的同时,给对象依赖的属性注入值
  • 基于IOC和DI思想开发的优点
    • 使我们的对象之间的依赖关系做到了弱耦合
    • 利于项目的维护和升级
Spring工厂控制创建对象的次数
  • Spring工厂默认以单例方式创建(调用多次获取对象,一个类只在工厂创建一个对象)

    • 自定义Spring工厂创建对象的方式

      <!--scope属性控制对象创建的方式
          prototype代表多例方式创建,即每一次从工厂获取对象都会创建一个新的,如Struts2的action
          singleton代表单例方式创建,默认,无状态的类,只是提供一些方法,如Dao,Service通常为单例-->
      <bean id="orderService" class="com.mo.service.OrderServiceImpl"
                scope="prototype" autowire="byType">
      </bean>
      
  • 自定义一个类的初始化和销毁方法

    <--此时相当于给MyClass指定了一个初始化和一个销毁时调用的方法
        init-method:指定初始化方法,属性写对应方法名
        destroy-method:指定销毁方法,工厂关闭时才会调用,需要手动关闭工厂-->
    <bean id="orderService" class="com.mo.service.OrderServiceImpl"
              scope="singleton" init-method="initOrderService" destroy-method="destroyOrderService" autowire="byType">
    </bean>
      
      //方法名没有规定,初始化方法,对象初始化完毕自动调用
      public void initOrderService(){
              System.out.println("initOrderService");
      }
      //销毁方法,对象销毁前调用
      public void destroyOrderService(){
              System.out.println("destroyOrderService");
      }
    
  • Spring工厂创建对象的生命周期

    • 创建的时间
      • Spring工厂初始化的时候,会创建所有的单例对象
      • 对于多例的对象,在从工厂获取的时候才会创建
    • 销毁
      • 在Spring工厂关闭的时候销毁所有的单例对象
      • 对于多例对象的销毁Spring工厂不去管理
复杂对象的创建
  • 不能手动直接new出来的对象,如Connection和SqlSessionFactory,Spring工厂需要通过FactoryBean来进行管理创建

    • FactoryBean接口方法的作用

      //FactoryBean
      //需要工厂生产什么类型的对象,在实现这个接口的时候,指定什么类型的泛型即可
      public interface FactoryBean<T>{
      	String OBJECT_ATTRIBUTE = "factory";
      	
      	//书写创建复杂对象的逻辑,并返回
      	@Nullable
      	T getObject() throws Exception;
      	
      	//返回创建的复杂对象的类的对象
      	@Nullable
      	Class<?> getObjectType();
      	
      	//控制创建复杂对象的次数,默认true为单例,false为多例
      	default boolean isSingleton(){
      		return true;
      	}
      }
      
    • 创建Connection

    • Spring工厂在创建bean标签配置的对象时,会检查当前的类是否实现了FactoryBean接口,如果一个类实现了FactoryBean接口,则通过beanId获取对象时,工厂返回的是通过FactoryBean创建的复杂对象

      //实现FactoryBean接口
      public class ConnectionFactionBean implements FactoryBean<Connection> {
          private String driverClassName;
          private String url;
          private String username;
          private String password;
      	setter方法;
        
          //书写创建复杂对象的逻辑
          @Override
          public Connection getObject() throws Exception {
              Class.forName(driverClassName);
              Connection conn = DriverManager.getConnection(url, username, password);
      
              return conn;
          }
      
          @Override
          public Class<?> getObjectType() {
              return Connection.class;
          }
          //根据创建复杂对象的特点,指定是单例模式还是多例模式创建
          @Override
          public boolean isSingleton() {
              //connction需要每一个用户使用单独的,所以是多例
              return false;   //代表多例模式
          }
      }
      
      
      //Spring工厂在创建bean标签配置的对象时,会检查当前的类是否实现了FactoryBean接口,如果一个类实现了FactoryBean接口,则通过beanId获取对象时,工厂返回的是通过FactoryBean创建的复杂对象
      <!-- 配置FactoryBean的实现类 -->
      <bean id="connectionFactoryBean" class="com.yuan.factorybean.ConnectionFactionBean">
              <property name="driverClassName" value="oracle.jdbc.OracleDriver"></property>
              <property name="url" value="jdbc:oracle:thin:@10.211.55.3:1521:xe"></property>
              <property name="username" value="hr"></property>
              <property name="password" value="hr"></property>
      </bean>
      	
      public void test(){
              //初始化工厂
              ApplicationContext cxt = new ClassPathXmlApplicationContext("spring-core.xml");
              //获取对象
              Connection connection = (Connection) cxt.getBean("connectionFactoryBean");
              System.out.println(connection);
      }
      
  • Spring工厂配置文件参数化

    • 在核心配置文件中引入外部小配置文件

      //数据库连接
      jdbc.driverClassName=oracle.jdbc.OracleDriver
      jdbc.url=jdbc:oracle:thin:@10.211.55.3:1521:xe
      jdbc.username=hr
      jdbc.password=hr
      
      <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.xsd
              <!-- 手动引入的 -->
              http://www.springframework.org/schema/context
              http://www.springframework.org/schema/context/spring-context.xsd">
      
          <!--引入外部小配置文件
          location:用于指定小配置文件路径
          classpath指系统的环境变量,在这里指从项目根目录开始找-->
          <context:property-placeholder location="classpath:db.properties"/>
          
          <!-- 配置FactoryBean的实现类 -->
          <bean id="connectionFactory" class="com.mo.factorybean.ConnectionFactoryBean">
                  <property name="driverClassName" value="${jdbc.driverClassName}"></property>
                  <property name="url" value="${jdbc.url}"></property>
                  <property name="username" value="${jdbc.username}"></property>
                  <property name="password" value="${jdbc.password}"></property>
          </bean>
      
      </beans>
      
静态代理
  • 在项目上线之前,需要进行性能测试

    //XXXService
    
    public void ma(){
    	//记录执行的开始时间begin	额外功能
    	
    	//完成核心业务功能操作
    	
    	//记录执行完毕的时间		额外功能
    	
    	//记录执行完毕的时间		额外功能
    	time = end - begin;
    }
    
  • 上线项目时,需要将额外的功能代码去掉

  • 由于项目中会存在一些除核心业务功能之外的额外功能,这些额外功能在某些情况下是需要的,在有些情况下不需要,为了提高项目的可维护性,所有通过代理类原始类增加额外功能,可以让代码灵活使用额外功能

    • 原始类只负责核心业务功能
    • 代理类负责完成额外功能以及核心业务功能
    • 原始类和代理类要实现同一个接口
    • 代理类要保留对原始类的引用
  • 静态代理的缺陷

    • 代理类的数量会随着原始类的增加越来越多
    • 额外功能冗余
  • 静态代理的目的:通过代理类给原始类核心的业务功能加入额外功能操作

AOP开发
  • Aspect Oriented Programming,面向切面编程

    • 主要是用切面来解决开发中的一些问题(针对切面进行编程)
  • OOP,Object Oriented Programming,面向对象编程

    • 基于对象进行逻辑的组织(以多个对象之间的相互协作),解决实际的问题
  • Spring的AOP开发是基于Spring的动态代理实现的(本质为动态代理),动态代理是通过生成代理类给原始类增加额外功能

    • 动态代理是在程序运行的过程中生成的代理类的对象,目的也是通过代理类给原始类(目标类)增加额外功能
  • 所谓的面向切面编程其实是指额外功能与切入点组装到一起形成的一个切面

  • AOP编程优点:

    • 解耦合
    • 使额外功能与业务功能完全的从编码上剥离开,可以让程序员更加专注业务功能的实现
    • 额外功能的定义,被提取到一个地方,解决了横切入项目中的冗余代码,提高了代码的复用性
  • Spring动态代理开发的步骤

    • 编写原始类

      <!--编写原始类-->
      <bean id="orderService" class="com.mo.service.OrderServiceImpl"></bean>
      
    • 编写通知(额外功能)

      • MethodBeforeAdvice前置通知:指在原始方法执行前加入额外功能

        public class MyBeforeAdvice implements MethodBeforeAdvice {
            /**
             * @param method:调用的目标方法(原始方法)对象
             * @param objects:数组的地址,调用原始方法的传参
             * @param o:原始类的对象
             */
            @Override
            public void before(Method method, Object[] objects, Object o) throws Throwable {
                System.out.println("额外功能");
                System.out.println("method:"+method);
                System.out.println("objects:"+objects);
                System.out.println("o:"+o);
                //调用的方法名
                System.out.println(method.getName());
                //需要判断是否有参数
                if(objects!=null) {
                    //遍历参数数组
                    for (Object object : objects) {
                        System.out.println(object);
                    }
                }
            }
        }
        
        <!--创建额外功能实现类-->
        <bean id="advice" class="com.mo.advice.MyBeforeAdvice"></bean>
        
      • AfterReturningAdvice后置通知:在原始方法执行后加入额外功能

        public class MyAfterAdvice implements AfterReturningAdvice {
            /**
             * @param o:目标方法(原始方法)执行后的返回值
             * @param method:原始方法对象
             * @param objects:原始方法调用传入的参数
             * @param o1:目标类(原始类)的对象
             */
            @Override
            public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
                System.out.println("===后置通知===");
                System.out.println("o:"+o);
                System.out.println("method::"+method);
                System.out.println("objects:"+objects);
                System.out.println("o1:"+o1);
                
            }
        }
        
        <!--创建通知-->
        <bean id="advice" class="com.mo.advice.MyAfterAdvice"></bean>
        
      • MethodInterceptor环绕通知:在原始方法执行的前和后加入额外功能;事务管理是基于环绕通知实现的

        public class RoundAdvice implements MethodInterceptor {
            /**
             * @param methodInvocation:可以手动调用原始的方法
             * @return
             */
            @Override
            public Object invoke(MethodInvocation methodInvocation) throws Throwable {
                System.out.println("===原始方法执行前额外功能===");
                //手动调用原始方法,返回原始方法执行后的返回值
                Object result = methodInvocation.proceed();
                //原始方法的方法对象
                methodInvocation.getMethod();
                //原始方法的参数
                methodInvocation.getArguments();
                System.out.println("===原始方法执行后额外功能===");
                
                return result;
            }
        }
        
        <!--创建通知-->
        <bean id="advice" class="com.mo.advice.RoundAdvice"></bean>
        
      • ThrowsAdvice异常通知:当原始方法执行抛出异常时加入额外功能,可以做异常回滚和统一管理异常

        public class MyThrowAdvice implements ThrowsAdvice {
            /**
             * @param e:程序抛出的异常对象
             */
            public void afterThrowing(Exception e){
                System.out.println("===after throwing程序出现异常===:"+e);
            }
        }
        
        <!--创建通知-->
        <bean id="advice" class="com.mo.advice.MyThrowAdvice"></bean>
        
    • 定义切入点:用于指定额外功能加入的位置(加入到哪些包、哪些类、哪些方法上)

      • expression属性值是一个切入点表达式的定义,切入点表达式需要定义在一个切入点函数中

      • 切入点函数

        • execution(方法返回值和修饰符 包名…类名.方法名(参数列表))

        • 第一个 *:匹配方法的返回值类型和修饰符,通常不做变动

        • 第二个 *:匹配包结构

        • 第三个 *:匹配类名

        • 第四个 *:匹配方法名

        • (…):匹配参数列表

          execution(* *..*.*(..)):匹配所有的包中所有的类中的所有的方法
          execution(* *.*.*(..))	不包含子包
          execution(* com.mo.service..*.*(..))	匹配com.mo.service包下所有类中所有的方法
          execution(* *..OrderServiceImpl.*(..))	匹配所有包中,类名是OrderServiceImpl中的所有方法
          execution(* *..*.createOrder(..))	匹配所有包中,所有类中方法名叫cerateOrder的
          execution(* *..*.*(String))	匹配所有包中,所有类中,所有方法参数为一个字符串类型的
          execution(* *..*.*(Integer,..))		匹配第一个参数是Integer类型的方法,后面的可以为0个或多个
          
        • within(*…*):匹配所有包下所有的类,不关注参数,精度不如execution

        • 第一个 *:匹配包结构

        • 第二个 *:匹配类名

        • 中间两个点代表匹配子包

          within(com.mo.service..*)	匹配com.mo.service包以及其子包下的所有的类
          within(*..OrderServiceImpl)	匹配所有包下包含子包,所有类名叫OrderServiceImpl中的所有方法
          within(com.mo.service.*)	匹配con.mo.service包下不含子包的所有的类
          
        • args(…):匹配所有包中所有类中任意参数的方法,只关注参数列表,不在乎是哪个包哪个类

          arg(Integer,..)		匹配第一个参数是Integer类型的方法,不在乎后面参数的类型和个数
          arg(..,String)		匹配最后一个参数是String类型的方法,不在乎前面参数的类型和个数
          
        • @annotation(注解的类型):用于匹配使用了指定类型注解的方法

          @annotation(com.mo.annotation.Value)	匹配方法上使用了com.mo.anno.Value类型注解的加额外功能
          
      • 切入点函数运算符:切入点函数支持and | or逻辑运算

        //and		并且
        @annotation(com.mo.anno.Value) and args(Integer,..);	//匹配所有方法上使用了com.mo.anno.Value类型注解的并且第一个参数类型为整形的
        
        //or	或者
        @annotation(com.mo.anno.Value) or args(Integer,..)		//匹配所有方法上使用了com.mo.anno.Value类型注解的或者第一个参数类型为整形的
        
    • 编织 — wave:根据切入点指定的位置,将指定的额外功能加入到指定位置,最终产生代理类

      <beans xmlns:aop="http://www.springframework.org/schema/aop"
             xsi:schemaLocation="
                    http://www.springframework.org/schema/aop
                    http://www.springframework.org/schema/aop/spring-aop-3.2.xsd ">
      
      <aop:config>
              <!--定义切入点-->
              <!--id属性是引用这个切点的依据
              expression属性定义额外功能加入的位置-->
              <aop:pointcut id="pct" expression="execution(* *..*.*(..))" />
              <!--编织
              advice-ref:指定编织引用的额外功能
              pointcut-ref:指定使用的切点是哪个-->
              <aop:advisor advice-ref="advice" pointcut-ref="pct"></aop:advisor>
      </aop:config>
      
    • 测试

      public void test0(){
              ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop.xml");
              // 当加入AOP开发后,我们获取到的service是代理类的对象
              OrderService orderService = context.getBean("orderService", OrderService.class);
              System.out.println(orderService.getClass());
              orderService.createOrder();
      }
      
Spring动态代理的实现原理
  • 解释

    • JVM使用到一个类的时候,需要将对应的字节码文件加载到内存中
    • 虚拟机JVM会为我们的每一次new对象的操作分配一个类加载器(ClassLoader),完成类加载
    • 类加载是由类加载器负责
    • 类加载过程:加载、验证字节码文件是否合法、准备、解析、初始化(执行),此时才完成对象的创建
    • 完成一个对象的创建需要有类的字节码信息和使用一个类加载器
  • Java中创建一个对象需要的条件

    • 类的字节码文件(类的信息)
    • 由类加载器负责加载字节码文件到JVM中
  • JDK动态代理实现(Java原生)

    • 反编译中的h,为构建代理类的对象时传入的InvocationHandler

    • JDK最底层实现:要求原始类和代理类实现统一接口

      public void textJDKProxy() throws Exception {
              //生成代理类的字节码,返回代理类的字节码
              //第一个参数:生成代理类的类名
              //第二个参数:代理类实现的接口
              byte[] proxyClass = ProxyGenerator.generateProxyClass("OrderServiceProxy", new Class[]{OrderService.class});
              
              /*手动将代理类字节码写到本地磁盘
              OutputStream out = new FileOutputStream("/Users/hang/IdeaProjects/OrderServiceProxy.class");
              out.write(proxyClass);
              out.close();*/
              
              //根据字节码,创建代理类的对象
              //获取类加载器,借用TestAop类加载器
              //通过类的对象获取类加载器
              ClassLoader classLoader = TestAop.class.getClassLoader();
              System.out.println(classLoader);
              //类加载器的类对象,处理字节码,getDeclaredMethod获取到私有的,受保护的
              Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
              //开启受保护方法的访问权限
              defineClass.setAccessible(true);
              //根据代理类的字节码,使用指定的类加载器生成代理类的 【类对象】
              //参数:类加载器 ,字节数组,下标0,到最后
              Class aClass = (Class) defineClass.invoke(classLoader, proxyClass, 0, proxyClass.length);
          
              //创建代理类的 【类的对象】
              //默认调用的类的无参构造,所有需要先获取有参构造方法对象
              Constructor constructor = aClass.getConstructor(InvocationHandler.class);
              //参数,接口
              OrderService proxyObj = (OrderService) constructor.newInstance(new InvocationHandler() {
                  //原始类的对象
                  private OrderService o = new OrderServiceImpl();
                  /**
                   * @param proxy:代理类的对象
                   * @param method:原始类的方法对象
                   * @param args:原始类的方法参数
                   */
                  @Override
                  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                      System.out.println("===原始方法前的额外功能===");
                      //手动调用原始方法,调用哪个方法,参数就传哪个方法
                      Object result = method.invoke(o, args);
                      System.out.println("===原始方法后的额外功能===");
                      return result;
                  }
              });
              proxyObj.createOrder();
      }
      

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fWvWFic9-1611024524551)(/Users/hang/百知培训班/第三阶段/Spring动态代理实现.png)]

  • JDK动态代理实现:原始类和代理类实现同一个接口

    //JDK动态代理的封装实现
    @Test
    public void test2() {
            //生成一个代理类的对象
            OrderService orderService = (OrderService) Proxy.newProxyInstance(TestJDKProxy.class.getClassLoader(),
                    new Class[]{OrderService.class}, new MyInvocationHandler(new OrderServiceImpl()));
            orderService.addOrder();
    }
    
    
    public class MyInvocationHandler implements InvocationHandler {
        private Object obj;
        
        public MyInvocationHandler(Object obj){
            this.obj = obj;
        }
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("===原始方法前加入额外功能===");
            //执行原始类的方法
            Object result = method.invoke(obj, args);
            System.out.println("===原始方法前加入额外功能===");
            return result;
        }
         
        public void setObj(Object obj) {
            this.obj = obj;
        }
    }
    
  • cglib动态代理的实现:要求代理类去继承原始类

    public class TestCglibProxy {
        @Test
        public void test0(){
            Enhancer enhancer = new Enhancer();
            //设置父类
            enhancer.setSuperclass(ProductService.class);
            //定义额外功能
            enhancer.setCallback(new InvocationHandler(){
                private ProductService productService = new ProductService();
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("原始方法前的额外功能");
                    //执行原始方法
                    Object result = method.invoke(productService,args);
                    System.out.println("原始方法后的额外功能");
                    return null;
                }
            });
            //获取代理类的对象
            ProductService o = (ProductService)enhancer.create();
            System.out.println(o.getClass());
            o.addProduct();
        }
    }
    
  • Spring在生成代理类的对象的时候,会根据原始类是否实现接口来决定采用JDK的方式还是Cglib的方式生成代理类的对象

SM整合 —> Spring+Mybatis
  • Spring整合mybatis其实就是接管了mybatis-config.xml

    • 配置数据源的创建,可以使用第三方的连接池
    • 配置创建SqlSessionFactory
    • 配置创建DAO接口实现类的对象
    • 配置事务管理 – Spring的声明式事务管理机制
  • 连接池:避免频繁的创建和销毁连接,提高复用性,但连接池中的连接使用完要放回连接池

    • 常用的第三方连接池

      • c3p0

        public void testC3p0() throws PropertyVetoException, SQLException {
                //创建连接池对象
                //任何连接池都必须和父类DataSource建立关系
                ComboPooledDataSource dataSource = new ComboPooledDataSource();
                //设置连接数据库的参数
                dataSource.setDriverClass("oracle.jdbc.OracleDriver");
                dataSource.setJdbcUrl("jdbc:oracle:thin:@10.211.55.3:1521:xe");
                dataSource.setUser("hr");
                dataSource.setPassword("hr");
                
                //获取连接
                Connection connection = dataSource.getConnection();
                System.out.println(connection);
                //com.mchange.v2.c3p0.impl.NewProxyConnection@380fb434
        }
        
      • dbcp

        public void testDbcp() throws SQLException {
                //创建连接池对象
                BasicDataSource basicDataSource = new BasicDataSource();
                //设置连接数据库参数
                basicDataSource.setDriverClassName("oracle.jdbc.OracleDriver");
                basicDataSource.setUrl("jdbc:oracle:thin:@10.211.55.3:1521:xe");
                basicDataSource.setUsername("hr");
                basicDataSource.setPassword("hr");
                
                //获取连接
                Connection connection = basicDataSource.getConnection();
                System.out.println(connection);
                //jdbc:oracle:thin:@10.211.55.3:1521:xe, UserName=HR, Oracle JDBC driver
        }
        
      • druid(阿里巴巴研发):事务默认自动提交

        public void testDruid() throws SQLException {
                //创建连接池对象
                DruidDataSource druidDataSource = new DruidDataSource();
                //设置连接数据库参数
                druidDataSource.setDriverClassName("oracle.jdbc.OracleDriver");
                druidDataSource.setUrl("jdbc:oracle:thin:@10.211.55.3:1521:xe");
                druidDataSource.setUsername("hr");
                druidDataSource.setPassword("hr");
                
                //设置连接池优化配置参数
                druidDataSource.setInitialSize(3);  //设置初始化连接池创建的连接数,默认0
                druidDataSource.setMaxActive(5);    //设置最大活跃连接数,默认8
                druidDataSource.setMaxWait(2000);   //设置最大等待时间,单位毫秒,默认-1L,无限等
                druidDataSource.setMinIdle(3);   //设置最小空闲连接数,默认0
                
                //获取连接对象 DruidPooledConnection实现了Connection接口
                //DruidPooledConnection connection = druidDataSource.getConnection();
                Connection connection = druidDataSource.getConnection();
                System.out.println(connection);
                //oracle.jdbc.driver.T4CConnection@4c15e7fd
        }
        
  • Spring整合mybatis后,默认事务是自动提交的

  • 代码实现

    <!--db.properties-->
    jdbc.driverClassName=oracle.jdbc.OracleDriver
    jdbc.url=jdbc:oracle:thin:@10.211.55.3:1521:xe
    jdbc.username=hr
    jdbc.password=hr
    
    <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"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--引入外部小配置文件-->
        <context:property-placeholder location="classpath:db.properties"></context:property-placeholder>
    
        <!--创建连接池-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driverClassName}"></property>
            <property name="url" value="${jdbc.url}"></property>
            <property name="username" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
            <!--初始化连接数-->
            <property name="initialSize" value="2"></property>
            <!--最小空闲连接数-->
            <property name="minIdle" value="2"></property>
        </bean>
    
        <!--创建SqlSessionFactory-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--注入数据源
            第一个参数是规定好的
            第二个是之前写的连接池id-->
            <property name="dataSource" ref="dataSource"></property>
            <!--指定mapper文件的位置-->
            <property name="mapperLocations" value="classpath:com/mo/mapper/*Mapper.xml"></property>
            <!--给实体类起别名:指定实体类所在的包结构-->
            <property name="typeAliasesPackage" value="com.mo.entity"></property>
        </bean>
        <!--创建DAO接口实现类的对象 此标签id写了用处不大,可以不写-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!--注入SqlSessionFactory-->
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
            <!--指定DAO接口所在的包
            Spring工厂创建好DAO接口实现类的对象后,默认获取对象的beanId为接口类名首字母小写
            如UserDao接口实现类的对象,beanId是userDao-->
            <property name="basePackage" value="com.mo.dao"></property>
        </bean>
    
    	<!--创建service-->
        <bean id="userService" class="com.mo.service.UserServiceImpl" autowire="byName"></bean>
        
    </beans>
    
事务控制
  • 事务是用户操作数据库的基本单元,由一条到到多条SQL语句组成;也可以说一个业务操作对应数据库就是一个事务操作

  • 事务的ACID:原子性、一致性、隔离性、持久性

  • JDBC中的事务:

    • 一条SQL命令执行完毕自动提交事务
    • 管理事务的对象:Connection
    • 手动控制事务
      • conn.setAutoCommit(false)
      • conn.commit() / conn.rollback()
    • JDBC默认事务是自动提交的
  • Mybatis中事务控制

    • 事务管理对象:sqlSession(一个sqlSession中封装了一个connection对象)
    • 默认事务是手动提交,否则会进行自动回滚
    • 事务的提交回滚:sqlSession.commit() / sqlSession.rollback()
  • Spring中的事务

    • Spring采用声明式事务管理机制

    • 程序员使用Spring框架开发时,只需要按照Spring的规范和约定在配置文件中去声明配置自己管理事务的形式即可,Spring框架会根据配置自动完成事务的管理控制

    • 管理事务对象:DataSourceTransactionManager —>依赖于数据源对象dataSource

    • Spring事务管理开发步骤:

      • 创建原始类

        <!--创建service-->
        <bean id="orderService" class="com.mo.service.OrderServiceImpl"></bean>
        
      • 编写通知 —> 额外功能,事务管理的相关配置

        <!--编写额外功能,声明事务管理的配置-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                <!--注入数据源-->
                <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!--配置事务通知-->
        <tx:advice id="advice" transaction-manager="transactionManager">
              <!--配置事务属性-->
              <tx:attributes>
                    <!--配置指定的方法如何加事务
                    name:用于指定方法名,支持通配符
                    name="*"    代表所有的方法都加上当前的事务控制配置
                    name="add*" 代表给add开头的方法加上当前的事务控制配置 -->
                    <tx:method name="addOrder"/>
              </tx:attributes>
        </tx:advice>
        
      • 定义切入点

      • 编织

        <aop:config>
                <!--定义切入点-->
                <aop:pointcut id="pct" expression="execution(* com.mo.service..*.*(..))"/>
                <!--编织-->
                <aop:advisor advice-ref="advice" pointcut-ref="pct"></aop:advisor>
            </aop:config>
        
Spring中事务的属性
  • 传播性:主要是控制业务功能在相互调用的时候事务是如何进行控制

    • PROPAGATION_PEQUIRED默认,事务是必须的,一定有事务;如果外层有事务,则加入当前(外层)事务;如果外层没有事务,会创建一个新的事务;通常用于写操作(增删改)

    • PROPAGATION_SUPPORTS:支持事务;如果外层有事务,则加入当前事务;如果外层没有事务,不会创建事务;主要用于查询操作

    • PROPAGATION_REQUIRES_NEW:不管当前环境有没有事务,都会创建一个独立的新事务;如果当前环境没有事务,创建一个新事务;如果当前环境有事务,则会先挂起(Suspending current transaction暂停当前环境事务)当前环境的业务,然后创建一个新的事务执行,执行完毕后,将挂起的事务继续执行完毕; 通常在一个方法中记录操作日志、执行核心业务,此时出现异常日志不能回滚,需要把事务分开处理

    • PROPAGATION_NOT_SUPPORTED:不支持事务,外层有事务会先挂起外层事务,也不会加入,自己也不会创建事务;外层没有也不会创建

    • PROPAGATION_NEVER:一定不能运行在有事务的环境,有事务则抛出异常

    • PROPAGATION_MANDATORY:一定要运行在有事务的环境,没有事务则抛出异常

    • NESTED:嵌入式事务

      <tx:method name="addOrder" propagation="REQUIRED"/>
      <tx:method name="findProductNum" propagation="SUPPORTS"/>
      <tx:method name="XXX" propagation="REQUIRES_NEW"/>
      <!-- 主要前三种 -->
      <tx:method name="XXX" propagation="NOT_SUPPORTED"/>
      <tx:method name="XXX" propagation="NEVER"/>
      <tx:method name="XXX" propagation="MANDATORY"/>
      
  • 隔离性

    • 特殊情况

      • 脏读:在多事务同时操作的环境下,一个事务读取到了另一个事务还未提交的数据;有一定的风险是用户拿到的数据与数据库最终的数据不一致
      • 不可重复性:在一个事务中针对于一条数据进行多次读取,但是获取的结果不一致
      • 幻影读:在一个事务中针对于一张表进行多次统计类的操作,两次结果不一致
    • 事务隔离级别

      • Oracle数据库默认隔离级别为读提交
      • MySql数据库默认隔离级别为可重复读
      • Spring框架默认是采用当前数据库的隔离级别
      read uncommitted
      读未提交
      read committed
      读提交
      repeatable read
      可重复读
      serializable
      序列化读
      解释指多事务环境下,一个事务可以读取到其他事务未提交的数据指在多事务环境下,一个事务只能读取到其他事务提交的数据可以保证在一个事务操作中,针对一条数据的两次或多次数据结果一致又称为串行读
      原理当一个事务在操作某一条数据时,会将当前行数据加锁,事务结束后释放锁当一个事务对一张表操作时,会对当前表进行加锁,事务结束后释放锁
      问题脏读、不可重复读、幻影读不可重复读、幻影读幻影读
      解决的问题脏读脏读、不可重复读脏读、不可重复读、幻影读
      安全性极差一般较高最高
      性能最高较高较差极差
    <!--声明事务管理要求配置-->
    <tx:advice transaction-manager="transactionManager" id="advice">
          <tx:attributes>
                <!--配置事务控制针对哪些方法,具体做什么样的控制
                name:指定方法的名字
                propagation:配置事务传播属性
                isolation:配置事务隔离级别,采用默认DEFAULT即可,代表采用数据库的默认隔离级别-->
                <tx:method name="createOrder" propagation="REQUIRED" isolation="DEFAULT"/>
                <tx:method name="findProduct" propagation="SUPPORTS"/>
          </tx:attributes>
    </tx:advice>
    
  • 回滚性

    • Spring默认在程序抛出运行时异常后,事务做回滚处理;默认程序抛出非运行时异常事务做提交处理

    • 自定义事务的回滚性:对于事务的回滚性,我们采用Spring默认的设置即可

      <tx:method name="createOrder" propagation="REQUIRED" isolation="DEFAULT"
                      rollback-for="java.lang.Exception"	//指定当程序抛出什么异常的时候回滚,当程序抛出非运行时异常,事务做回滚处理,可以写多个子类异常
                      no-rollback-for="java.lang.RuntimeException"	//指定当程序抛出什么异常的时候不回滚,此时抛出运行时异常事务时做提交处理
                 />
      
  • 只读性:当事务设置为只读为true时,当时的数据库连接下只能执行读操作,但是这个属性受限于数据库(只读性oracle不支持,mysql支持),通常读操作设置为true,写操作设置为false

    <tx:method name="findProductNum" propagation="REQUIRES_NEW"
                read-only="true"//读操作设置为true,写操作设置为false/>
    
  • 超时性:指一个事务的执行时间,如果超过指定时间没有结束事务,则抛出异常,默认值为-1,代表用不超时(采用默认即可,默认Spring是依据数据库默认的事务超时时间)

    <tx:method name="findProductNum" propagation="REQUIRES_NEW"
                timeout="-1" />
    
  • Spring+Mybatis整合事务开发XML实例

    <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"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
             http://www.springframework.org/schema/aop
              https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--引入外部小配置文件-->
        <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
        <!--配置数据源-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driverClassName}"></property>
            <property name="url" value="${jdbc.url}"></property>
            <property name="username" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
            <property name="initialSize" value="${jdbc.initialSize}"></property>
            <property name="minIdle" value="${jdbc.minIdle}"></property>
        </bean>
        <!--创建SqlSessionFactory-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"></property>
            <property name="mapperLocations" value="classpath:com/mo/mapper/*Mapper.xml"></property>
            <property name="typeAliasesPackage" value="com.mo.entity"></property>
        </bean>
        <!--创建dao实现类的对象-->
        <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
            <property name="basePackage" value="com.mo.dao"></property>
        </bean>
        <!--创建service-->
        <bean id="userService" class="com.mo.service.UserServiceImpl" autowire="byName"></bean>
        <!--声明事务配置-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <tx:advice id="advice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="add*" propagation="REQUIRED" />
                <tx:method name="modify*" propagation="REQUIRED" />
                <tx:method name="remove" propagation="REQUIRED" />
                <tx:method name="find" propagation="SUPPORTS" read-only="true" />
            </tx:attributes>
        </tx:advice>
    
        <aop:config>
            <!--定义切入点-->
            <aop:pointcut id="pct" expression="execution(* com.mo.service..*.*(..))"></aop:pointcut>
            <!--编织-->
            <aop:advisor advice-ref="advice" pointcut-ref="pct"></aop:advisor>
        </aop:config>
    
    </beans>
    
SSM —> Spring+Struts2+Mybatis
  • 整合mybatis相关配置

    • 配置数据源
    • 创建SqlSessionFactory
    • 创建dao实现类的对象
    • 创建创建service
    • 做事务管理配置
  • 整合struts2

  • 在Web环境下,初始化Spring工厂的时机是在应用启动的时候。Spring采用监听器的机制,实现在启动Web应用时初始化Spring工厂:ContextLoaderListener(初始化工厂的监听器)

    • 只将Struts2的action实现类的对象交给了Spring工厂

      //struts.xml
      <package name="user" extends="struts-default" namespace="/user">
        <!--当整合spring后,可以从spring工厂获取action 实现类的对象
      		class:书写对于action在spring工厂里的beanId-->
      		<action name="findAll" class="userAction" method="findAll">
      			<result name="success" type="dispatcher">/list.jsp</result>
      		</action>
      </package>
      
    • web.xml

      <web-app>
        <display-name>Archetype Created Web Application</display-name>
      
        <!--指定工厂配置文件路径-->
        <context-param>
          <!--全局初始化参数,key名字必须是contextConfigLocation-->
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:spring-core.xml</param-value>
        </context-param>
      
        <!--配置核心过滤器-->
        <filter>
          <filter-name>struts2</filter-name>
          <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
        </filter>
        <filter-mapping>
          <filter-name>struts2</filter-name>
          <url-pattern>/*</url-pattern>
        </filter-mapping>
      
        <!--配置初始化spring工厂监听器-->
        <listener>
          <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
      
      </web-app>
      
    • spring.xml

      //Spring-core.xml
          <!--创建action实现类的对象,并指定为多例创建-->
          <bean id="userAction" class="com.mo.action.UserAction"
                scope="prototype" autowire="byName"></bean>
      
Spring事务传播属性失效
  • 问题场景:当在一个service中,调用本类的另一个方法,会导致事务失效

  • 原因:此时调用本类其他方法,相当于this.ma();而this对象不是工厂生产出来的,是原始对象,所以不会有额外的事务功能

  • 解决:需要从工厂中拿(工厂生产的代理)对象,然后去调用方法;注意不能getBean获取,通过实现接口ApplicationContextAware解决

    //this此时为原始类的对象,不具备额外的事务功能,所以在直接调用本类的另一个方法时,没有事务传播属性的功能
    public void addProduct() {
            System.out.println(this);
            //判断一下是否曾家添加过此商品
            this.findProduct("Mac");
            System.out.println("service add Product====");
    }
    
  • 为了保证事务传播属性的配置还可以使用,我们还需要从Spring工厂中获取代理类的对象

  • 实现接口ApplicationContextAware

    public class ProductServiceImpl implements ProductService, ApplicationContextAware {
        private ApplicationContext applicationContext;
        //程序运行时,spring会自动调用当前的set方法注入当前的工厂对象
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.applicationContext = applicationContext;
        }
      
        @Override
        public void addProduct() {
            ProductService productService = applicationContext.getBean("productService", ProductService.class);
            productService.findProduct("Mac");
            
            System.out.println("service add Product====");
        }
    }
    
注解式管理事务
  • 开启注解管理事务驱动

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    <!--事务-->
    <!--  开始注解管理事务的驱动  tx:annotation-driven
    此时可以在service类的方法中使用注解控制事务-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
    <!-- 用注解管理事务的方式替换 
    <tx:advice transaction-manager="transactionManager" id="advice">
          <tx:attributes>
                <tx:method name="add*" propagation="REQUIRED"/>
                <tx:method name="find" propagation="SUPPORTS" />
          </tx:attributes>
    </tx:advice>
    
     <aop:config>
          <aop:pointcut id="pct" expression="execution(* com.mo.service..*.*(..))"></aop:pointcut>
          <aop:advisor advice-ref="advice" pointcut-ref="pct"></aop:advisor>
    </aop:config>-->
    
  • 在service中使用注解@Transactional

    @Transactional(propagation = Propagation.SUPPORTS,
                isolation = Isolation.DEFAULT,
                rollbackFor = {RuntimeException.class},
                noRollbackFor = {Exception.class},
                readOnly = false, timeout = -1)
    
    • 用在方法上

      @Transactional(propagation = Propagation.SUPPORTS,
              isolation = Isolation.DEFAULT,readOnle = true)
      @Override
      public void removeProduct() {
        	System.out.println("service remove Product====");
      }
      
    • 用在类上:当前类中所有的方法都会加上指定的事务控制

      @Transactional(propagation = Propagation.REQUIRED)
      public class ProductServiceImpl implements ProductService, ApplicationContextAware {
          private ApplicationContext applicationContext;
      }
      
    • 当类和方法上同时使用事务注解时,以方法上的注解声明加事务控制(方法上优先)

注解式开发
  • 使用注解开发会简化配置文件,提高开发效率,注解也称为软接口

  • 注解式开发是一种约定大于配置的开发思想,来简化我们Web应用系统的开发。可以说注解是开发人员与框架之间的一种约定

  • 注解属于一种约定式的开发

  • Spring注解开发流程

    • 在工厂配置文件中开启注解开发

      <!--开启注解扫描:指定到哪些包中扫描注解
      base-package:指定在哪些包中使用注解开发,支持通配符--->
      <context:component-scan base-package="com.mo.entity"></context:component-scan>
      <!--开启注解扫描:可以指定多个包,中间用逗号隔开-->
      <context:component-scan base-package="com.mo.entity,com.mo.dao"></context:component-scan>
      <!--开启注解扫描:支持通配符-->
      <context:component-scan base-package="com.mo.*"></context:component-scan>
      
    • 在指定类上使用注解

      /**
       * @Component注解相当于工厂配置文件中到bean标签
       * 此时spring工厂会自动创建user类的对象,默认以类名首字母小写作为beanId
       * //value:相当于beanId,在注解中value可以省略不写,写多个属性的时候,value就不能省略;手动指定@Component("u")
       */
      @Component("u")
      public class User implements Serializable {
            @Value(value = "1")
            private Integer id;
      }
      
  • Spring开发中常用的注解

    • @Repository:用于dao上

      @Repository
      public class UserDaoImpl implements UserDao{
          
          @Override
          public void selectAll() {
              System.out.println("this is UserDao select all");
          }
      }
      
    • @Service:用于service上

      @Service
      public class UserServiceImpl implements UserService {
          //自动注入,仅限于自定义对象
          @Autowired
          private UserDao userDao;	//此时不会调用set方法注入值,底层直接反射赋值
          
      }
      
    • @Controller:用于控制器上,以类名首字母小写的方式指定beanId

      @Controller
      //指定为多例方式创建
      @Scope(value = "prototype")
      public class UserAction extends ActionSupport {
      }
      
      //struts.xml
      <package name="user" extends="struts-default" namespace="/user">
      	 <action name="addUser" class="userAction" method="addUser">
      			<result name="success" type="dispatcher">/list.jsp</result>
      	 </action>
      </package>
      
    • 自动注入:两个功能相同,还可以使用到set方法上,指定调用set方法完成属性的注入

      • @Autowired:由spring框架提供的

      • @Resource:由java原生提供

        //自动注入的注解
        //@Autowired
        @Resource
        private UserDao userDao;
        
        private UserDao userDao;
            @Autowired  //set注入	如果需要在给属性注入值的时候加入额外功能,就需要在set方法上使用注入的注解
            public void setUserDao(UserDao userDao) {
                System.out.println("调用了set方法注入");
                this.userDao = userDao;
            }
        
    • @Scope(“prototype”)用在struts2的action实现类上,指定为多例模式创建对象,singleton为单例,控制创建对象的次数

      @Scope("singleton") //控制对象读创建次数
      public class UserServiceImpl implements UserService {
      }
      
SSM注解式开发流程
  • 引入依赖
  • 引配置文件
  • 初始化配置 —> web.xml
    • 配置strut2核心过滤器
    • 初始化spring工厂的监听器
  • 编码
    • 实体类封装
    • 定义dao
    • 定义service
      • 使用注解@Service
      • 注入依赖对象@Autowired
      • 事务管理注解@Transactional
    • 定义action
      • 使用注解:@Controller @Scope(“prototype”) @Autowired
    • spring-core.xml
      • 开启注解扫描
      • 引入小配置文件
      • 配置数据源
      • 创建SqlSessionFactory
      • 创建dao实现类的对象的配置
      • 创建DataSourceTransactionManager
      • 开启事务注解驱动
Spring整合Junit测试的支持
  • 引入Spring的测试依赖jar

    <!--spring整合junit测试-->
    <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>${spring-version}</version>
          <scope>compile</scope>
    </dependency>
    
  • 注解@RunWith(SpringJUnit4ClassRunner.class)

    //将Spring与junit4整合到一起
    @RunWith(SpringJUnit4ClassRunner.class)
    //指定spring配置文件路径
    @ContextConfiguration(location = {"classpath:spring-anno.xml"})
    public class TestUserService{
    	@Autowired
    	private UserService userService;
    	
    	@Test
    	public void tes0(){
        	userService.finAll();	//此时不用再初始化工厂,从工厂中获取对象
    	}
    }
    
lombok使用
  • 简化实体类的编写,通过在实体类上声明lombok的注解,那么lombok会自动给类中声明getter、setter、有参构造、无参构造等

  • 使用

    • 引入依赖

      <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
      <dependency>
          <groupId>org.projectlombok</groupId>
          <artifactId>lombok</artifactId>
          <version>1.18.12</version>
          <scope>provided</scope>
      </dependency>
      
    • 安装lombok插件到idea中:由于lombok在编译阶段不会生成相关的方法,所有需要借助lombok的插件,让代码在编译阶段可以手动显示的调用实体类的方法

    • 常用注解

      /*@Getter     //自动生成getter
      @Setter     //自动生成setter
      @ToString   //自动生成toString
      @EqualsAndHashCode  //equals和hashCode*/
      
      @Data   //包括getter、setter、equals、hashCode、toString
      @NoArgsConstructor      //提供无参构造方法
      @AllArgsConstructor     //提供有参构造方法
      public class Book implements Serializable {
      
      }
      
    • 链式调用注解

      @Accessors(chain = true)    //生成可以链式调用的setter方法
      public class Book implements Serializable {
      
      }
      
      //底层实现
      public Book setId(Integer id){
      	this.id = id;
      	return this;
      }
      public Book setName(String name){
      	this.name = name;
      	return this;
      }
      
Java中的自定义注解
  • 定义一个注解类:@interface 类名

  • 元注解和注解中的属性的定义

    /**Java中的元注解:Jdk提供,用于定义注解的注解
    @Target:用于指定当前的注解可以在什么位置;如可以指定注解只能用在类上或方法上或同时可以在类上和方法上
    @Retention:指定当前注解的存活周期
    			存在于源码中的注解	RetentionRolicy.SOURCE
    			存在于.class文件中的注解	RetentionPolicy.CLASS
    			存在于程序运行时的注解	RetentionPolicy.RUNTIME
    @Documented:指定注解是否出现在javadoc编译生成的文档中
    @Inherited:指定注解是否可以被其他存在的类的自类继承
    */
    @Target({ElementType.TYPE})	//注解只能用在类上
    //@Target({ElementType.TYPE,ElementType.FIELD}) 可以用在类上或方法上
    @Retention(RetentionPolicy.CLASS)	//存在于字节码文件中
    
    @Documented
    @Inherited
    public @interface EntityValue{
      	//定义注解中的类型
      	//属性的类型只能是八种基本数据类型和String以及他们对应类型的数组
      	String value();
      	//String[] values();
    }
    
  • 注解中属性名为value的可以省略不写,一个自定义注解中只能有一个属性名为value

    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface EntityValue {
        //给属性指定默认值
        String value() default "mo";
        
        String type() default "String";
        
    }
    
  • 自定义注解的解析

    //EntityValue.java
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface EntityValue {
        String value() default "mo";
        
        String type() default "String";
    }
    
    //Person.java
    public class Person implements Serializable {
        @EntityValue("hang")
        private String name;
        @EntityValue("male")
        private String sex;
    }
    
    //注解解析
    public void test0() throws Exception {
            //获取类对象
            Class<?> c = Class.forName("com.mo.entity.Person");
            //创建类的对象
            Object o = c.newInstance();
            //获取到类中所有的属性对象
            Field[] fields = c.getDeclaredFields();
            for(Field field : fields){
                //判断属性上是否使用了指定的注解
                boolean flag = field.isAnnotationPresent(EntityValue.class);
                if(flag){
                    //获取到注解对象,并拿到注解中属性的值
                    EntityValue annotation = field.getAnnotation(EntityValue.class);
                    String value = annotation.value();
                    System.out.println(value);	//hang 、male
                    //设置私有属性可访问
                    field.setAccessible(true);
                    //给属性赋值
                    field.set(o,value);	//自定义类型等,都需要做转换、判断等
                }
            }
            System.out.println(o); Person{name='hang', sex='male'}
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值