JavaEE基础02-spring4(AOP+web应用)

AOP的功能先从一个例子开始。

 加减乘除的一个功能,需要在每个方法开始和结束时打印东西。(就是需求一)

 方法一(非常不推荐):

package com.guigu.spring2;

/**
 * Created by Zhuxiang on 2020/5/18.
 */
public class ArithmeticCalculatorImpl implements ArithmeticCalculator{

    @Override
    public int add(int a, int b) {
        System.out.println("日志开始了:"+a+"加"+b);
        int add=a+b ;
        System.out.println(add);
        return add;
    }

    @Override
    public int sub(int a, int b) {
        System.out.println("日志开始了:"+a+"减"+b);
        int sub=a-b;
        System.out.println(sub);
        return sub ;
    }
}
ArithmeticCalculatorImpl

 方法二:使用静态代理(不推荐)(但是需要复习一下代理的思想)

下图中被代理类是接口的实现类,代理类也需要实现接口,这样才能提供方法。

package com.guigu.spring2;

/**
 * Created by Zhuxiang on 2020/5/18.
 */
public class ArithmeticCalculatorImpl implements ArithmeticCalculator{

    @Override
    public int add(int a, int b) {
        int add=a+b ;
        System.out.println(add);
        return add;
    }

    @Override
    public int sub(int a, int b) {
        int sub=a-b;
        System.out.println(sub);
        return sub ;
    }
}
ArithmeticCalculatorImpl
package com.guigu.spring2;

/**
 * Created by Zhuxiang on 2020/5/18.
 */
public class StaticProxy implements ArithmeticCalculator{
    private ArithmeticCalculator ar;
    public StaticProxy(ArithmeticCalculator ar) {
        this.ar=ar;
    }
    @Override
    public int add(int a, int b) {
        String s="加";
        noQueue(a,b,s);
        int add = ar.add(a, b);
        return add;
    }

    @Override
    public int sub(int a, int b) {
        String s="减";
        noQueue(a,b,s);
        int sub = ar.sub(a, b);
        return sub;
    }
    // 代理类本身自带功能;
    public void noQueue(int a,int b,String s) {
        System.out.println("日志开始了:"+a+s+b);
    }
}
StaticProxy
package com.guigu.spring2;

/**
 * Created by Zhuxiang on 2020/5/18.
 */
public class main {
    public static void main(String[] args) {
        ArithmeticCalculator a2 = new StaticProxy(new ArithmeticCalculatorImpl());
        a2.add(2,1);
        a2.sub(2,1);
    }
}
//日志开始了:2加1
//3
//日志开始了:2减1
//1
main

方法三:使用动态代理(详细内容去java基础反射复习)(可以用,但是和aop比起来麻烦得多)

package com.guigu.spring2;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * Created by Zhuxiang on 2020/5/18.
 */
public class LoggingProxy {
    //要代理的对象
    private  ArithmeticCalculator target;

    public LoggingProxy(ArithmeticCalculator target) {
        this.target = target;
    }
    public ArithmeticCalculator getLoggingProxy(){
        //代理对象由哪一个类加载器进行加载
        ClassLoader loader=target.getClass().getClassLoader();
        //代理对象的类型,即其中有哪些方法
        Class<?>[] interfaces=new Class[]{ArithmeticCalculator.class};
        //当调用代理对象其中的方法时,会执行的代码
        InvocationHandler h=new InvocationHandler() {
            /**
             * proxy: 正在返回的那个代理对象,一般情况下,invoke方法里不使用(调用里面的方法会导致死循环)
             * method: 正在被调用的方法
             * args: 传入的参数
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName=method.getName();
                System.out.println("日志:"+methodName+"begins with"+ Arrays.asList(args));
                //执行方法
                Object result = method.invoke(target, args);
                System.out.println("日志:"+methodName+"ends with"+ result);
                return result;
            }
        };
        ArithmeticCalculator proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
        return proxy;
    }
}
LoggingProxy
public class main {
    public static void main(String[] args) {
        ArithmeticCalculator a1 = new ArithmeticCalculatorImpl();
        ArithmeticCalculator a2 = new LoggingProxy(a1).getLoggingProxy();
        a2.add(2,1);
        a2.sub(2,1);
    }
}
//日志:addbegins with[2, 1]
//3
//日志:addends with3
//日志:subbegins with[2, 1]
//1
//日志:subends with1
main

上面是自己写的,下面是ppt里的。对比着看吧,反正效果一样的,log先忽视掉。

 

 

 

 

 

 了解了上面的内容,springAop正式开始。(重点)

1,加入jar包

 

 2,在配置文件中加入aop的命名空间

3,基于注解的方式

①在配置文件中加入

<!--使Aspect注解起作用,自动匹配的类生成代理对象-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

②把横切关注点的代码抽象到切面的类中。

  切面首先是一个ioc中的bean,即加入@component 注解

  切面还需要加入@Aspect 注解

③在类中声明各种通知

  声明一个方法

  在方法前加上@......注解,在注解后加上具体的类(具体看下面代码)

④可以在通知方法中声明一个类型为joinpoint的参数。然后就能访问链接细节。如方法名称和参数值

------------------------------具体代码-------------------------------

package com.guigu.spring2aop;

import org.springframework.stereotype.Component;

/**
 * Created by Zhuxiang on 2020/5/18.
 */
@Component
public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

    @Override
    public int add(int a, int b) {
        int add=a+b ;
        System.out.println(add);
        return add;
    }

    @Override
    public int sub(int a, int b) {
        int sub=a-b;
        System.out.println(sub);
        return sub ;
    }
}
ArithmeticCalculatorImpl
package com.guigu.spring2aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * Created by Zhuxiang on 2020/5/18.
 */
//把类声明为切面    1,放入ioc容器 2,声明为切面
@Aspect
@Component
public class LoggingAspect {
    //声明该方法是一个前置
    @Before("execution(public int com.guigu.spring2aop.ArithmeticCalculator.*(int,int))")
    public void beforeMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("日志开始了 "+name+" 参数 "+args);
    }
    @After("execution(* com.guigu.spring2aop.*.*(int,int))")
    public void afterMethod(){
        System.out.println("日志结束了");
    }
}
LoggingAspect
package com.guigu.spring2aop;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by Zhuxiang on 2020/5/18.
 */
public class Main {
    public static void main(String[] args) {
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        ArithmeticCalculator ari=(ArithmeticCalculator)c.getBean("arithmeticCalculatorImpl");
        ari.add(3,1);
    }
}
main
<?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"
       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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan
            base-package="com.guigu.spring2aop">
    </context:component-scan>
    <!--使Aspect注解起作用,自动匹配的类生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
applicationContext.xml

 -----------------------五种通知---------------------------

环绕通知不常用看看就行。

以动态代理里的方法为例,看懂各个通知所在的位置。

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName=method.getName();
                System.out.println("日志:"+methodName+"begins with"+ Arrays.asList(args));
                Object result = null;
                try {
                    //前置通知的位置
                    //执行方法
                    result = method.invoke(target, args);
                    //返回通知的位置
                } catch (Exception e) {
                    e.printStackTrace();
                    //异常通知的位置
                }
                //后置通知的位置,因为可能会出异常,所以不能访问到方法的返回值。
                System.out.println("日志:"+methodName+"ends with"+ result);
                return result;
            }

回到aspect介绍五种通知。

@Aspect
@Component
public class LoggingAspect {
    @Before("execution(* com.guigu.spring2aop.*.*(..))")
    public void beforeMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("日志开始了 "+name+" 参数 "+args);
    }
    //后置通知:在目标方法执行后(无论是否发生异常),都会执行
    //不能访问目标方法执行的结果。结果在返回通知里。
    @After("execution(* com.guigu.spring2aop.*.*(..))")
    public void afterMethod(){
        System.out.println("日志结束了");
    }
    //返回通知:方法正常结束受执行的代码。可以访问到方法的返回值。
    @AfterReturning(value = "execution(* com.guigu.spring2aop.*.*(..))",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
        String name = joinPoint.getSignature().getName();
        System.out.println("The method "+ name + " ends with "+result);
    }
    //异常通知:方法出现异常时会执行的代码。可以访问到异常对象,可以指定特定异常出现时再执行。
    @AfterThrowing(value = "execution(* com.guigu.spring2aop.*.*(..))",throwing = "ex")
    public void afterThrowing(JoinPoint joinPoint,Exception ex){
        String name = joinPoint.getSignature().getName();
        System.out.println("The method "+ name + " occurs exception: with "+ex);
    }
    /**(不常用,看看就行)
     * 环绕通知,需要携带ProceedingJoinPoint类型的参数。
     * 类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法。
     * 且环绕通知必须有返回值,返回值即为目标方法的返回值。
     */
//    @Around("execution(* com.guigu.spring2aop.*.*(..))")
//    public Object aroundMethod(ProceedingJoinPoint pjd){
//        Object result =null;
//        String name = pjd.getSignature().getName();
//        //执行目标方法
//        try {
//            System.out.println("前置通知 "+name+" begins with "+Arrays.asList(pjd.getArgs()));
//            result = pjd.proceed();
//            System.out.println("返回通知 "+name+" result "+result);
//        } catch (Throwable throwable) {
//            throwable.printStackTrace();
//            System.out.println("异常通知 "+name+" occurs exception: "+ throwable);
//        }
//        System.out.println("后置通知 "+name+" ends with "+Arrays.asList(pjd.getArgs()));
//        return result;
//    }
}
LoggingAspect

 --------------1优先级,2重用切点表达式------------

//1.优先级。用@order注解来表明优先级,数字越低,优先级越高,不声明就是任意优先级(最低)
@Order(2)
@Aspect
@Component
public class ValidationAspect {
    @Before("LoggingAspect.declareJoinPointExpression()")
    public void validationMethod(){
        System.out.println("--->验证");
    }
}
//---------上面验证类,下面日志类---------
@Aspect
@Component
public class LoggingAspect {
    //2.定义一个方法,用于声明切入点表达式。一般该方法中不添加其他代码。
    //在本类中调用看下面,在其他类中调用的例子看验证类(ValidationAspect)
    @Pointcut("execution(* com.guigu.spring2aop.*.*(..))")
    public void declareJoinPointExpression(){}
    @Before("declareJoinPointExpression()")
    public void beforeMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        List<Object> args= Arrays.asList(joinPoint.getArgs());
        System.out.println("日志开始了 "+name+" 参数 "+args);
    }

 -----------------以上是基于注解,下面是基于xml文件------------------(看一看)

实现过程就是,把类的注解删了,然后加入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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="arithmeticCalculator" class="com.guigu.spring2aop.ArithmeticCalculatorImpl">
    </bean>
    <!--配置切面的bean-->
    <bean id="loggingAspect" class="com.guigu.spring2aop.LoggingAspect">
    </bean>

    <bean id="validationAspect" class="com.guigu.spring2aop.ValidationAspect">
    </bean>
    <!--配置aop-->
    <aop:config>
        <!--配置切点表达式-->
        <aop:pointcut expression="execution(public int com.guigu.spring2aop.*.*(..))"
                      id="pointcut"></aop:pointcut>
        <!--配置切面及通知-->
        <aop:aspect ref="loggingAspect" order="2">
            <aop:before method="beforeMethod" pointcut-ref="pointcut"></aop:before>
            <aop:after method="afterMethod" pointcut-ref="pointcut"></aop:after>
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"></aop:after-returning>
            <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"></aop:after-throwing>
        </aop:aspect>
        <aop:aspect ref="validationAspect">
            <aop:before method="validationMethod" pointcut-ref="pointcut"></aop:before>
        </aop:aspect>
    </aop:config>
</beans>
applicationContext-xml.xml

经过配置,效果就一样了。

-----------------spring对jdbc的支持------------------(看一看就行)

一,JdbcTemplate插件

 

//看的顺序是从最下面的test往上看。
public class JdbcTest {
    private ApplicationContext c = null;
    JdbcTemplate jdbcTemplate;

    {
        c = new ClassPathXmlApplicationContext("applicationContext.xml");
        jdbcTemplate = (JdbcTemplate) c.getBean("jdbcTemplate");
    }

    //获取单个列的值,或做统计查询
    @Test
    public void testQueryForObject2() {
        String sql="select count(id) from admin";
        long count=jdbcTemplate.queryForObject(sql,long.class);
        System.out.println(count);
    }

    //查到实体类的集合
    //调用query方法
    @Test
    public void testQueryForList() {
        String sql = "select * from admin where id>?";
        RowMapper<Admin> rowmapper = new BeanPropertyRowMapper<>(Admin.class);
        List<Admin> admins = jdbcTemplate.query(sql, rowmapper, 1);
        System.out.println(admins);
    }

    /**
     * 查到实体类
     * 不是调用queryForObject(String var1, Class<T> var2, object...args)
     * 而是调用queryForObject(String var1, RowMapper<> rowMapper, object...args)
     * 1.其中RowMapper 指定如何去映射结果集的行,常用的实现类为BeanPropertyRowMapper
     * 2.使用sql中的别名完成类名(表里的列名)和类的属性名(bean里的)的映射。
     * 3.不支持级联属性,JdbcTemplate只是jdbc工具,而不是orm框架。
     */
    @Test
    public void testQueryForObject() {
        String sql = "select * from admin where id=?";
        RowMapper<Admin> rowMapper = new BeanPropertyRowMapper<>(Admin.class);
        Admin admin = jdbcTemplate.queryForObject(sql, rowMapper, 1);
    }

    //批量更新:批量地inset,update,delete
    //最后一个参数是object[] 的list类型:因为修改一条需要一个object的数组,那么多条就要多个object的数组。
    @Test
    public void testBatchUpdate() {
        String sql = "insert into admin(username,password) values(?,?)";
        List<Object[]> batchArgs = new ArrayList<>();
        batchArgs.add(new Object[]{"AA", "123"});
        batchArgs.add(new Object[]{"BB", "123"});
        batchArgs.add(new Object[]{"CC", "123"});
        jdbcTemplate.batchUpdate(sql, batchArgs);
    }

    @Test
    public void testUpdate() {
        String sql = "update admin set username=? where id=?";
        jdbcTemplate.update(sql, "锤子", 1);
    }

    //连接数据库
    @Test
    public void test() throws Exception {
        DataSource dataSource = (DataSource) c.getBean("dataSource");
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }
}
JdbcTest
url=jdbc:mysql://localhost:3306/girls
username=root
password=
driverClassName=com.mysql.jdbc.Driver
dd.properties
<?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.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    
<!--    <context:property-placeholder location="classpath:dd.properties"/>-->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:dd.properties"></property>
    </bean>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="url" value="${url}"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
    <constructor-arg ref="dataSource"></constructor-arg>
    </bean>

</beans>
applicationContext.xml

 

在开发中怎么用?(使用了注解记得去xml文件里进行配置)

@Component
public class AdminDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    //主要说明的是开发中实现增删改查,所以简化了连接池部分,
    // 不要这样搞连接。开发中写一个专门的连接类。
    {
        ApplicationContext c = new ClassPathXmlApplicationContext("applicationContext.xml");
        jdbcTemplate = (JdbcTemplate) c.getBean("jdbcTemplate");
    }
    public Admin testQueryForObject(int id) {
        String sql = "select * from admin where id=?";
        RowMapper<Admin> rowMapper = new BeanPropertyRowMapper<>(Admin.class);
        Admin admin = jdbcTemplate.queryForObject(sql, rowMapper, id);
        return admin;
    }
}

二,NamedParameterJdbcTemplate具名参数(看一看)

其他步骤(xml文件连接数据库,得到NamedParameterJdbcTemplate类)的编写同上。

    <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg ref="dataSource"></constructor-arg>
    </bean>

第一种方法:

好处是:若有多个参数,则不用再去对应位置,直接对应参数名,便于维护

缺点是:较为麻烦

@Test
public void testNamedParameterJdbcTemplate(){
String sql =" insert into admin(username,password) values(:un,:pw)";
Map<String,Object> paramMap=new HashMap<>();
paramMap.put("un","小米");
paramMap.put("pw","123");
namedParameterJdbcTemplate.update(sql,paramMap);
}

第二种方法:

    @Test
    public void testNamedParameterJdbcTemplateUpdate(){
        String sql="insert into admin(username,password) values(:username,:password)";
        Admin admin = new Admin();
        admin.setUsername("xiaomi");
        admin.setPassword("123");
        BeanPropertySqlParameterSource b = new BeanPropertySqlParameterSource(admin);
        n.update(sql, b);
    }

 ------------spring中的事务管理------------(重点看)

复习一下,四个关键属性(ACID):原子性(atomicity),一致性(consistency),隔离性(isolation),持久性(durability)。

以前学过jdbc的事物管理,重复代码很多,不易于维护。

现在aop来进行事物管理,将事务管理代码从业务方法中分离出来, 以声明的方式来实现事务管理.

以用户买书为例:用户买一本书,账户金额减掉书的价格,书的库存减少1。作为一个事务。

准备工作:数据库里3个表,用户账户表,书表,书的库存表。

第一步:分别写出,1)用户买的书的价格。2)用户账户金额减书的价格。3)书的库存减少1

 

package com.guigu.spring4.shiwu;

/**
 * Created by Zhuxiang on 2020/5/21.
 */
public interface BookShopDao {
    //根据书号获取书的单价
    public int findBookPriceByIsbn(int isbn);

    //更新库存,书号对应的库存减一
    public void updateBookStock(int isbn);

    //更新用户的账户余额:使 username 的 balance-price
    public void updateUserAccount(String username ,int price);
}
BookShopDao
package com.guigu.spring4.shiwu;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

/**
 * Created by Zhuxiang on 2020/5/21.
 */
@Repository("bookShop")
public class BookShopImpl implements BookShopDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int findBookPriceByIsbn(int isbn) {
        String sql = "select price from book where isbn=?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(int isbn) {
        //检查书的库存是否够,不够就抛出异常。
        String sql2 = "select stock from book_stock where isbn=?";
        int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if (stock <= 0) throw new RuntimeException("库存不足");
        String sql = "update book_stock set stock = stock-1 where isbn=?";
        jdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price) {
        //检查用户余额是否够,不够就抛出异常。
        String sql2 = "select balance from account where username=?";
        int stock = jdbcTemplate.queryForObject(sql2, Integer.class, username);
        if (stock < price) throw new RuntimeException("余额不足");
        String sql = "update account set balance = balance-? where username=?";
        jdbcTemplate.update(sql, price, username);
    }
}
BookShopImpl

第二步:使用注解的方法加上事务。

package com.guigu.spring4.shiwu;

/**
 * Created by Zhuxiang on 2020/5/21.
 */
public interface BookShopService {
    public void purchase (String username,int isbn);
}
BookShopService
package com.guigu.spring4.shiwu;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * Created by Zhuxiang on 2020/5/21.
 */
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
    @Autowired
    private BookShopDao bookShopDao;
    //添加事务注解.
    @Transactional
    @Override
    public void purchase(String username, int isbn) {
        //获取书的单价
        int bp = bookShopDao.findBookPriceByIsbn(isbn);
        //更新书的库存
        bookShopDao.updateBookStock(isbn);
        //更新用户余额
        bookShopDao.updateUserAccount(username,bp);
    }
}
BookShopServiceImpl
<?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" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.alibaba.com/schema/stat http://www.alibaba.com/schema/stat.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

<!--    <context:property-placeholder location="classpath:dd.properties"/>-->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:dd.properties"></property>
    </bean>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="url" value="${url}"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <context:component-scan base-package="com.guigu.spring4.shiwu">
    </context:component-scan>
    <!--配置事务管理器-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--启动事务注解-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>

</beans>
applicationContext.xml
package com.guigu.spring4.shiwu;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by Zhuxiang on 2020/5/21.
 */
public class STTest {
    private ApplicationContext a=null;
    private BookShopDao bookShopDao =null;
    private BookShopService bookShopService=null;
    {
        a=new ClassPathXmlApplicationContext("applicationContext.xml");
        bookShopDao =(BookShopDao)a.getBean("bookShop");
        bookShopService=(BookShopService)a.getBean("bookShopService");
    }
    @Test
    public void test04(){
        bookShopService.purchase("小明",1002);
    }
    @Test
    public void test01(){
        System.out.println(bookShopDao.findBookPriceByIsbn(1001));
    }
    @Test
    public void test02(){
        bookShopDao.updateBookStock(1001);
    }
    @Test
    public void test03(){
        bookShopDao.updateUserAccount("小明",100);
    }
}
STTest

第三步:事务里的事务如何处理。

package com.guigu.spring4.shiwu;

import java.util.List;
/**
 * Created by Zhuxiang on 2020/5/21.
 */
public interface Cashier {
    //一个人买多本书
    public void checkout(String username, List<Integer> isbns);
}
Cashier
package com.guigu.spring4.shiwu;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * Created by Zhuxiang on 2020/5/21.
 */
public class CashierImpl implements Cashier {
    @Autowired
    private BookShopService bookShopService;
    @Transactional
    @Override
    public void checkout(String username, List<Integer> isbns) {
        for (int isbn : isbns) {
            bookShopService.purchase(username,isbn);
        }
    }
}
CashierImpl
package com.guigu.spring4.shiwu;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * Created by Zhuxiang on 2020/5/21.
 */
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService{
    @Autowired
    private BookShopDao bookShopDao;
    //添加事务注解,使用propagation指定事务的传播行为,即当前的事务方法被另一个事务方法调用时,
    //如何使用事务,一,默认值为required,即使用调用方法的事务。(看图理解)
    //二,使用自己的事务REQUIRES_NEW,调用的事务方法的事务被挂起。(看图理解)
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void purchase(String username, int isbn) {
        //获取书的单价
        int bp = bookShopDao.findBookPriceByIsbn(isbn);
        //更新书的库存
        bookShopDao.updateBookStock(isbn);
        //更新用户余额
        bookShopDao.updateUserAccount(username,bp);
    }
}
BookShopServiceImpl

默认情况图:CashierImpl里的事务是Tx1,BookShopServiceImpl里的事务是图中的purchase().

 

新建情况图:

 

如何使用事务,事务的其他属性(和上面的一样重要)

//添加事务注解,使用propagation指定事务的传播行为,即当前的事务方法被另一个事务方法调用时,
    //如何使用事务,一,默认值为required,即使用调用方法的事务。(看图理解)
    //二,使用自己的事务REQUIRES_NEW,调用的事务方法的事务被挂起。(看图理解)
    //三,指定隔离级别,isolation = Isolation.READ_COMMITTED(读已提交)
    //四,虽然默认异常后全回滚,但是也可以自己设置(通常情况不会去设置)。
    // 比如设置在账户余额不回滚 : noRollbackFor=UserAccountException.class,
    // 结果就是继续执行下面的方法,把库存减1。
    //五,使用readOnly=true指定事务为只读。只能查。
    //六,使用timeout 指定强制回滚之前事务可以占用的时间。如timeout = 3就是
    // 给事务3秒时间,如果在时间内没有完成就强制回滚。可以让程序睡4秒测试一下:Thread.sleep(4000)。
    @Transactional(propagation = Propagation.REQUIRED,noRollbackFor=UserAccountException.class)

为了测试4,我们多加一个异常类 UserAccountException ,如果余额不足就进入这个异常类,它继承了runtimeException类。

 最后,不使用注解作配置,而使用xml文件配置,达到同样的效果。具体操作时记得把注解删了,还要加上引用类的set方法。

<?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" 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 https://www.springframework.org/schema/context/spring-context.xsd http://www.alibaba.com/schema/stat http://www.alibaba.com/schema/stat.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:dd.properties"/>-->
    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:dd.properties"></property>
    </bean>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="url" value="${url}"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置bean-->
    <bean id="bookShopImpl" class="com.guigu.spring4.shiwu.BookShopImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    <bean id="bookShopServiceImpl" class="com.guigu.spring4.shiwu.BookShopServiceImpl">
        <property name="bookShopImpl" ref="bookShopImpl"></property>
    </bean>
    <bean id="cashierImpl" class="com.guigu.spring4.shiwu.CashierImpl">
        <property name="bookShopServiceImpl" ref="bookShopServiceImpl"></property>
    </bean>
    <!--1.配置事务管理器-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--2.配置事务属性-->
    <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
        <tx:attributes>
            <tx:method name="purchase" propagation="REQUIRED" timeout="5"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!--3.配置事务切入点,以及把事务切入点和事务属性关联起来-->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.guigu.spring4.shiwu.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"></aop:advisor>
    </aop:config>
</beans>
applicationContext-tx.xml

 ------------------spring在web应用的使用------------------(重要)

一,第一种方式:核心步骤,自己用代码把web应用和spring关联起来。

1. Spring 如何在 WEB 应用中使用 ?

1). 需要额外加入的 jar 包:

spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar

2). Spring 的配置文件, 没有什么不同

3). 如何创建 IOC 容器 ?

①. 非 WEB 应用在 main 方法中直接创建
②. 应该在 WEB 应用被服务器加载时就创建 IOC 容器:

在 ServletContextListener#contextInitialized(ServletContextEvent sce) 方法中创建 IOC 容器.

③. 在 WEB 应用的其他组件中如何来访问 IOC 容器呢 ?

在 ServletContextListener#contextInitialized(ServletContextEvent sce) 方法中创建 IOC 容器后, 可以把其放在
ServletContext(即 application 域)的一个属性中.

④. 实际上, Spring 配置文件的名字和位置应该也是可配置的! 将其配置到当前 WEB 应用的初始化参数中较为合适.

二,第二种方式:核心步骤,调用spring里已经为开发者准备好的监听器与web应用关联。

4). 在 WEB 环境下使用 Spring

①. 需要额外加入的 jar 包:

spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar

②. Spring 的配置文件, 和非 WEB 环境没有什么不同

③. 需要在 web.xml 文件中加入如下配置:

<!-- 配置 Spring 配置文件的名称和位置 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>

<!-- 启动 IOC 容器的 ServletContextListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

---------------------------代码------------------------------

 第一种的代码,主要看思想。

记得导服务器里的servlet包

 

public class Listener implements ServletContextListener,
        HttpSessionListener, HttpSessionAttributeListener {

    // Public constructor is required by servlet spec
    public Listener() {
    }

    // -------------------------------------------------------
    // ServletContextListener implementation
    // -------------------------------------------------------
    public void contextInitialized(ServletContextEvent sce) {
      /* This method is called when the servlet context is
         initialized(when the Web application is deployed). 
         You can initialize servlet context related data here.
      */
        //获取spring配置文件的名称。
        ServletContext servletContext = sce.getServletContext();
        String initParameter = servletContext.getInitParameter("contextConfigLocation");
        //创建ioc容器
        ApplicationContext ac=new ClassPathXmlApplicationContext(initParameter);
        //把ioc容器放在servletContext的一个属性里。
        servletContext.setAttribute("ac",ac);
    }
public class Person {
    String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
public class Servlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext =getServletContext();
        ApplicationContext a=(ApplicationContext) servletContext.getAttribute("ac");
        Person person =(Person) a.getBean("person");
        System.out.println(person);
    }
}
    <bean id="person" class="com.guigu.spring6.web.Person">
        <property name="name" value="zx"></property>
    </bean>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--加入监听器-->
    <listener>
        <listener-class>com.guigu.spring6.web.Listener</listener-class>
    </listener>
    <!--和spring配置文件建立联系-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <!--需要一个servlet做测试-->
    <servlet>
        <servlet-name>Servlet</servlet-name>
        <servlet-class>com.guigu.spring6.web.Servlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Servlet</servlet-name>
        <url-pattern>/servlet</url-pattern>
    </servlet-mapping>

</web-app>
web

 

index.jsp  
<a href="servlet">test</a>

 

第二种的代码,进行了简化(不用自己写监听器了)。

    <!--加入spring包准备好的监听器-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--和spring配置文件建立联系-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
web.xml

spring的配置文件applicationcontext.xml  不变。

person类 不变。

public class Servlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        ServletContext servletContext =getServletContext();
//        ApplicationContext a=(ApplicationContext) servletContext.getAttribute(WebApplicationContext.class.getName() + ".ROOT");
        //和上面写的效果一样,spring给了我们对应的工具类,叫WebApplicationContextUtils
        ApplicationContext a=WebApplicationContextUtils.getWebApplicationContext(servletContext);
        Person person =(Person) a.getBean("person");
        System.out.println(person);
    }
}
Servlet

index.jsp  不变

--------------------spring带的监听器ContextLoaderListener的源码理解------------------

在web里配的监听器ContextLoaderListener,进入初始化方法看一看。

 

 进入源码里,找到这个方法:

servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

 

 这个方法存储ioc容器的key为:

WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE

点进去看,具体叫什么?

所以在servlet里这样的行为得到ioc容器是可以的:

ApplicationContext a=(ApplicationContext) servletContext.getAttribute(WebApplicationContext.class.getName() + ".ROOT");

但是我们的spring已经封装了这个功能,和上面写的效果一样,spring给了我们对应的工具类,叫WebApplicationContextUtils

ApplicationContext a=WebApplicationContextUtils.getWebApplicationContext(servletContext);

走入这个工具类的getWebApplicationContext方法的源码。

 

进入源码。上面这个方法return了下面这个方法。

 

 

 

 本质上还是通过那个一大串字符串作为key,来获得ioc容器。spring通过封装使代码简洁。

posted @ 2020-05-18 22:26  天才淇露洛  阅读(238)  评论(0)    收藏  举报