SSM---Spring

本文深入探讨Spring框架的核心特性,包括IoC(控制反转)的详细概念和实现,如Bean的创建、初始化、属性注入,以及自动化配置。接着,介绍了Aop(面向切面编程),讲解动态代理、五种通知类型和XML配置Aop。此外,还涉及了JdbcTemplate的使用,展示了如何在Java和XML配置中操作。最后,概述了Spring事务管理的XML和Java配置方式。
摘要由CSDN通过智能技术生成

说明:Spring的学习是参考江南一点雨的教程(安利这个公众号),教程原文


Spring

一、Spring简介

Spring是为了解决企业级应用开发的复杂性而创建的。在Spring之前,有一个重量级工具叫做EJB,使用Spring可以让JavaBean之间进行有效的解耦,而这个操作之前只有EJB才能完成,EJB过于臃肿,使用很少。Spring不仅仅局限于服务端的开发,在测试性和松耦合方面都有很好的表现。

二、Ioc

1、Ioc

(1)Ioc概念

Ioc(Inversion of Control),中文叫做控制反转,即对一个对象的控制权的反转。


public class Book{

    private Integer id;

    private String name;

    private Doule price;

    ...

}

public class User{

    private Integer id;

    private   String name;

    private Integer age;

    

    pubic void doSth(){

        Book book  = new Book();

        book.setId(1);

        book.setName("故事新编");

        book.setPrice((double)20);

    }

}

在这种情况下,Book对象的控制权在User对象里面,即Book和User高度耦合,如果在其他对象中需要使用Book对象,得重新创建。也就是说,对象的创建、初始化、销毁等操作,得开发者自己开完成,如果能够将这些操作交给容器来管理,开发者就可以极大的从对象的创建中解脱出来。

使用Spring之后,我们可以将对象的创建、初始化、销毁等操作交给Spring容器来管理。即,在项目启动时,所有的Bean都将自己注册到Spring容器中(如果有必要),然后如果其他Bean需要使用到这个Bean,则不需要自己去new,而是直接去Spring容器要。

(2)实例

在pom.xml中添加spring的依赖:


<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
</dependencies>

定义Book和User类:


package org.luyangsiyi.test02.bean;

/**
 * Created by luyangsiyi on 2020/2/14
 */
public class Book {

    private Integer id;
    private String name;
    private Double price;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

package org.luyangsiyi.test02.bean;

/**
 * Created by luyangsiyi on 2020/2/14
 */
public class User {

    private int id;
    private String name;
    private String age;

    public void doSth(){
        Book book = new Book();
        book.setId((int)1);
        book.setName("故事新编");
        book.setPrice((double)20);
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }


    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}

配置applicationContext.xml文件(spring相关配置),配置需要注册到Spring容器的Bean,class属性表示需要注册的bean的全路径,id/name则表示bean的唯一标记:


<?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 http://www.springframework.org/schema/context/spring-context-4.0.xsd">

    <!--配置所有需要注册到spring容器的bean-->
    <bean class="org.luyangsiyi.test02.bean.Book" id="book"/>
</beans>

测试结果:


import org.luyangsiyi.test02.bean.Book;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created by luyangsiyi on 2020/2/14
 */
public class test {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        Book book = (Book)ctx.getBean("book");
        System.out.println(book);
    }
}

2、Bean的获取

一般通过ctx.getBean(Bean的name或id属性)去获取Bean。

**(不建议)**也可以使用ctx.getBean(Bean.class)去获取,但是如果存在多个实例,可能会报错。

3、属性的注入

Spring中,如果要将一个Bean注册到Spring容器中,有三种不同的方式:

  • xml注入

  • Java配置

  • 自动化扫描

(1)构造方法注入

给Bean添加相应的构造方法:


public class Book{

    private Integer id;

    private String name;

    private Double price;

    

    public Book(iInteger id, String name, Double price){

        this.id = id;

        this.name = name;

        this.price = price;

    }

}

在xml中注入Bean,方式一:index:


<bean class="org.luyangsiyi.test02.Book" id="book1">

    <constructor-arg index="0" value="1"/>

    <constructor-arg index="1" value="三国演义"/>

    <constructor-arg index="2" value="30"/>

</bean>

方法二:name:


<bean class="org.luyangsiyi.test02.Book" id="book2">

    <constructor-arg name="id" value="2"/>

    <constructor-arg name="name" value="红楼梦"/>

    <constructor-arg name="price" value="30"/>

</bean>

(2)set方法注入

<bean class="org.luyangsiyi.test02.Book" id="book3">

    <property name="id" value="3"/>

    <property name="name" value="水浒传"/>

    <property name="price" value="3"/>

</bean>

set方法的注入需要注意属性名并不是你定义的属性名,而是通过Java中的内省机制分析出来的属性名,即get/set方法分析出来的属性名。

(3)p名称空间注入

使用的较少。


<bean class="org.luyangsiyi.test02.Book" id="book4" p:id="4" p:bookName="西游记" p:price="30"></bean>

(4)外部Bean的注入

① 静态工厂方法


public class BookFactory{

    public static Book buyBook(){

        Book book = new Book();

        book.setName("java");

        return book;

    }

}


<bean id="book" class="...BookFactory" factory-method="buyBook"></bean>

② 非静态工厂方法

必须实例化工厂类之后才能调用工厂方法。


public class BookFactory{

    public Book buyBook(){

        Book book = new Book();

        book.setName("java");

        return book;

    }

}


<bean id="bookFactory" class="...bean.BookFactory"></bean>

<bean id="book" factory-bean="bookFactory" factory-method="buyBook"></bean>

因为bookFactory的工厂方法不是静态的,因此需要定义一个工厂类的bean,然后通过factory-bean属性来引用工厂bean实例,通过factory-method属性来指定对应的工厂方法。

4、复杂属性的注入

(1)对象注入

<bean class="org.luyangsiyi.test02.User" id="user">

    <property name="cat" ref="cat"/>

</bean>

<bean class="org.luyangsiyi.test02.Cat" id="cat">

    <property name="name" value="小白"/>

    <property name="color" value="白色"/>

</bean>

可以通过xml注入对象,通过ref来引用一个对象。

(2)数组/集合注入

<bean class="org.luyangsiyi.test02.User" id="user">

    <property name="cat" ref="cat"/>

    <property name="favorites">

        <array>

            <value>足球</value>

            <value>篮球</value>

            <value>乒乓球</value>

        </array>

    </property>

</bean>

<bean class="org.luyangsiyi.test02.Cat" id="cat">

    <property name="name" value="小白"/>

    <property name="color" value="白色"/>

</bean>

注意,array节点也可以被list节点替代。array/list节点中也可以是对象,即可以通过ref使用外部定义好的Bean,也可以直接在其中定义。


<bean class="org.luyangsiyi.test02.User" id="user">

    <property name="cat" ref="cat"/>

    <property name="favorites">

        <list>

            <value>足球</value>

            <value>篮球</value>

            <value>乒乓球</value>

        </list>

    </property>

    <property name="cats">

        <list>

            <ref bean="cat"/>

            <bean class="org.luyangsiyi.test02.Cat" id="cat1">

                <property name="name" value="小花"/>

                <property name="color" value="花色"/>

            </bean>

        </list>

    </property>

</bean>

<bean class="org.luyangsiyi.test02.Cat" id="cat">

    <property name="name" value="小白"/>

    <property name="color" value="白色"/>

</bean>

(3)Map注入

<property name="map">

    <map>

        <entry key="age" value="99"/>

        <entry key="name" value="alice"/>

    </map>

</property>

(4)Properties注入

<property name="info">

    <props>

        <prop key="age">99</prop>

        <prop key="name">alice</prop>

    </props>

</property>

5、Java配置

Java配置这种方式广泛用在Spring Boot中。

如果有一个Bean:


public class SayHello{

    public String sayHello(String name){

        return "hello"+name;

    }

}

使用Java配置类去代替之前的applicationContext.xml文件:


@Configuration //表示这是一个配置类,相当于applicationContext.xml。

public class JavaConfig{

    @Bean //表示将这个方法的返回值注入到Spring容器中,相当于bean节点。

    SayHello sayHello(){

        return new SayHello();

    }

}

在启动时,加载配置类:


public class Main{

    public static void main(String[] args){

        AnnotationConfigApplicationContext ctx = new  AnnotationConfigApplicationContext(JavaConfig.class);          

        SayHello hello = ctx.getBean(SayHello.class);   

        System.out.println(hello.sayHello("java"));

    }

}

说明:

  • 配置的加载使用 AnnotationConfigApplicationContext来实现。

  • Bean的默认名称是方法名。如果需要自定义名称可以在@Bean中配置,如修改名字为hello,@Bean(“hello”)。

6、自动化配置

实际发开中大量使用自动配置。

自动化配置可以通过Java配置来实现,也可以xml配置来实现。

(1)准备工作

如果有一个UserService希望在自动化扫描的时候,这个类能自动注册到Spring容器中去,那么可以给该类添加一个@Service作为一个标记。

和@Service功能类似的注解有四个,功能是一致的,只是为了在不同的类上添加方便:

  • Service层:@Service

  • Dao层:@Repository

  • Controller层:@Controller

  • 其他层:@Component


@Service

public class UserService{

    public List<String> getAllUser(){

        List<String> users = new ArrayList<>();

        for(int i = 0; i < 10; i++)

            users.add("java:"+i);

        return users;

    }

}

(2)Java代码配置自动扫描

@Configuration

@ComponentScan(basePackages="org.luyangsiyi.test02.service") //指定要扫描的包,默认是配置类所在的包下的Bean和配置类所在的包下的子包下的类

public class JavaConfig{

}

然后可以获取UserService的实例:


public class Main{

    public static void main(String[] args){

        AnnotationConfigApplicationContext ctx = new  AnnotationConfigApplicationContext(JavaConfig.class);         

        UserService userService = ctx.getBean(UserService.class);

        System.out.println(userService.getAllUser());

    }

}

注意:

  • Bean默认的名字是类名首字母小写,如果要自定义则添加到@Service中。

  • 上述方法是按照包的位置来扫描的,也可以使用注解来扫描:@ComponentScan(basePackages="org.luyangsiyi.test02", useDefaultFilters = true, excludeFilters = {@ComponentScan.Filter(type=FilterType.Annotation, classes=Controller.class)}),表示扫描所有org.luyangsiyi.test02下除了Controller以外的所有Bean。

(3)xml配置自动化扫描

在applicationContext.xml中配置:


<context:component-scan base-package="org.luyangsiyi.test02"/>

表明扫描org.luyangsiyi.test02下的所有Bean,也可以按照类来扫描。

也可以按照注解的类型进行扫描:


<context:component-scan base-package="org.luyangsiyi.test02" use-default-filters="true">

    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>

</context:component-scan>

(4)对象注入

自动扫描时的对象注入有三种方式:@Autowired、@Resources、@Injected

@Autowired是根据类型去寻找,然后赋值,要求这个类型只能有一个对象,否则会报错。

@Resources是根据名称去寻找,默认情况下定义的变量名就是查找的名字,也可以在注解中手动指定。如果一个类存在多个实例,需要使用@Resources去注入。

@Qualifier是根据变量名去寻找,结合@Autowired可以实现@Resources的功能。


@Service

public class UserService{

    @Autowired
    UserDao useDao;

    public String hello(){

        return userDao.hello();

    }

    

    public List<String> getAllUser(){

        List<String> users = new ArrayList<>();

        for(int i = 0; i < 10; i++)

            users.add("java:"+i);

        return users;

    }

}

7、条件注解

满足某一个条件下,生效的配置。

(1)条件注解

条件注解的典型使用场景,即多环境切换。

比如要实现展示在windows和linux系统下的显示文件夹命令,先定义接口:


public interfact ShowCmd{

    String showCmd();

}

然后实现两种环境下的实例:


public class WinShowCmd implements ShowCmd{

    @Override

     public String showCmd(){

        return "dir";

    }

}





public class LinuxShowCmd implements ShowCmd{

    @Override

     public String showCmd(){

        return "ls";

    }

}

接下来定义两个条件:


public class WindowsCondition implements Condition{

    @Override

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){

        return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");

    }

}



public class LinuxCondition implements Condition{

    @Override

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata){

        return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");

    }

}

然后在定义Bean的时候,配置条件注解:


@Configuration

public class JavaConfig{

    @Bean("showCmd")

    @Conditional(WindowsCondition.class)

    ShowCmd winCmd(){

        return new WinShowCmd();

    }



    @Bean("showCmd")

    @ConditionalLinuxCondition.class)

    ShowCmd linuxCmd(){

        return new LinuxShowCmd();

    }

}

一定要给两个Bean取相同的名字,这样在调用时才会自动匹配。然后给每个Bean加上条件注解,只要条件中的matches方法返回true时,这个Bean的定义就会生效。

(2)多环境切换

Spring中提供了Profile可以进行开发/生产/测试环境之间的快速切换。Profile的底层就是条件注解。

我们可以提供一个DataSource:


public class DataSource{

    private String  url;

    private String username;

    private Sring password;

    ......

}

在配置Bean的时候,通过@profile注解指定不同的环境:


@Bean("ds")

@Profile("dev")

DataSource devDataSource(){

    DataSource dataSource = new DataSource();

    dataSource.setUrl("...");

    dataSource.setUsername("...");

    dataSource.setPassword("...");

}



@Bean("ds")

@Profile("prod")

DataSource devDataSource(){

    DataSource dataSource = new DataSource();

    dataSource.setUrl("...");

    dataSource.setUsername("...");

    dataSource.setPassword("...");

}

最后,加载配置类,需要先设置当前环境,再去加载配置类。

Java配置:


public class JavaMain { 

    public static void main(String[] args) { 

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();        

        ctx.getEnvironment().setActiveProfiles("dev");

        ctx.register(JavaConfig.class);

        ctx.refresh();

        DataSource ds = (DataSource) ctx.getBean("ds");

        System.out.println(ds);

    }

}

xml配置,需要放在其他节点后面:


<beans profile="dev">

    <bean class="...DataSource" id="dataSource">

        <property name="url" value="..."/>

        <property name="username" value="..."/>

        <property name="password" value="..."/>

    </bean>

</beans>

<beans profile="prod">

    <bean class="...DataSource" id="dataSource">

        <property name="url" value="..."/>

        <property name="username" value="..."/>

        <property name="password" value="..."/>

    </bean>

</beans>

最后启动类中设置当前环境并加载配置:


public class Main { 

    public static void main(String[] args) { 

        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();    

        ctx.getEnvironment().setActiveProfiles("prod"); 

        ctx.setConfigLocation("applicationContext.xml"); 

        ctx.refresh(); 

        DataSource dataSource = (DataSource) ctx.getBean("dataSource"); 

        System.out.println(dataSource); 

    } 

}

8、其他

(1)Bean的作用域

在Spring容器中多次获取同一个Bean,默认情况下,获取到的实际上是同一个实例,但是可以手动配置:


<bean class="..." id="..." scope="prototype"/>

设置scope属性可以调整默认的实例个数,默认是singleton(单例),可以修改为prototype(多次获取到的是不同的实例)。在web环境下,还可以有取值request和session。

在java中配置为:


@Bean

@Scope("prototype")

...

(2)id和name的区别

在xml配置中,id和name都可以指定唯一标识符,大部分情况下作用一致。

name支持取多个,可以用","隔开:


<bean class="..." id="user1,user2,user3" scope="prototype"/>

而id不支持多个值,如果强行用","隔开,其实还是一个值,即user1,user2,user3表示一个Bean的名字。

(3)混合配置

在Java配置中引入xml配置:


@Configuration

@ImportResource("classpath:applicationContext.xml")

public class JavaConfig{}

三、Aop

1、Aop简介

Aop(Aspect Oriented Programming),面向切面编程,即在程序运行时,不改变程序源码的情况下,动态的增强方法的功能。常见的使用场景:日志、事务、数据库操作等。

常见概念:

  • 切点:要添加代码的地方。

  • 通知(增强):向切点动态添加的代码。

  • 切面:切点+通知。

  • 连接点:切点的定义。

Aop实际上基于Java动态代理来实现,Java中的动态代理实现方式:cglib、jdk。

2、动态代理

基于JDK的动态代理。

(1)定义一个计算器接口


public interface MyCalculator{

    int add(int a, int b);

}

(2)定义计算器接口的实现


public class MyCalculatorImpl implements MyCalculator{

    public int add(int a, int b){

        return a+b;

    }

}

(3)定义代理类


public class CalculatorProxy{

    public static Object getInstance(final MyCalculatorImpl myCalculator) {

        return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(), myCalculator.getClass().getInterfaces(),new InvocationHandler(){

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{   

                System.out.println(method.getName()+"方法开始执行");

                Object invoke = method.invoke(myCalculator, args);

                System.out.println(method.getName()+"方法执行结束");   

                return invoke;             

            }

        });

    }

}

3、五种通知

Spring中的Aop的通知类型有5种:

前置通知、后置通知、异常通知、返回通知、环绕通知。

(1)在pom.xml中引入Aop相关的依赖:


<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.9.2</version>
</dependency>

(2)定义切点—自定义注解(不推荐)

定义切点的两种方式:使用自定义注解、使用规则(推荐)。

先自定义一个注解:


@Target(ElementType.METHOD)

@Retention(RententionPolicy.RUNTIME)

public @interface Action{

}

然后在需要拦截的方法行,添加该注解,在add方法上添加了@Action注解,表示该方法会被Aop拦截,而其他未添加该注解的方法不受影响。


@Component

public class MyCalculatorImpl{

    @Action

    public int add(int a, int b){

        return a+b;

    }

    

    public void min(int a, int b){

        System.out.println(a + "-" + b + "=" + (a-b));

    }

}

接下来,定义增强(通知、Advice):


@Component

@Aspect //表示这是一个切面

public class LogAspect{

    //前置通知

    //jointPoint包含了目标方法的关键信息

    //@Before 注解表示这是一个前置通知,即在目标方法执行之前执行,注解中需要填入切点

    @Before(value = "@annotation(Action)")

    public void before(JoinPoint joinPoint){

        Signature signature = joinPoint.getSignature();

        String name = signature.getName();

        System.out.println(name + "方法开始执行");

    }

    

    //后置通知

    //joinPoint包含了目标方法的所有关键信息

    //@After表示这是一个后置通知,即在目标方法执行之后执行

    @After(value = "@annotation(Action)")

    public void after(JoinPoint joinPoint){

        Signature signature = joinPoint.getSignature();

        String name = signature.getName();

        System.out.println(name + "方法执行结束");

    }



    //返回通知

    //@AfterReturning返回通知,即目标方法有返回值时才触发,该注解中的returning属性表示目标方法返回值的变量名。目标方法的返回值类型和这里方法返回值参数的类型一致,否则拦截不到,如果想拦截所有(包括返回值为void),则方法返回值参数可以为Object

    @AfterReturning(value = "@annotation(Action)", returning = "r")

    public void returning(JoinPoint joinPoint, Integer r){

        Signature signature = joinPoint.getSignature();

        String name = signature.getName();

        System.out.println(name + "方法返回"+r);

    }


    //异常通知

    //e为目标方法所抛出的异常,这个参数必须是目标方法所抛出的异常或者所抛出的异常的父类,如果想拦截所有,参数类型声明为Exception

    @AfterThrowing(value = "@annotation(Action)", throwing = "e")

    public void before(JoinPoint joinPoint, Exception e){

        Signature signature = joinPoint.getSignature();

        String name = signature.getName();

        System.out.println(name + "方法抛异常了"+e);

    }



    //环绕通知

    //可以用环绕通知实现上面四个通知,方法的核心类似于这里通过反射执行方法

    //这里的返回值类型最好的Object,和拦截到的方法相匹配

    @Around(value = "@annotation(Action)")

    public Object around(ProceedingJoinPoint pjp){

        Object proceed = null;

        try{

            //这个相当于method.invoke方法,我们可以在这个方法的前后分别添加日志,相当于前置/后置通知

            proceed = pjp.proceed();

        } catch (Throwable e) {

            e.printStackTrace();

        }   

        return proceed;

    }
}

通知定义完成后,接下来在配置类中,开启包扫描和自动代理:


@Configuration

@ComponentScan

@EnableAspectAutoProxy //开启自动代理

public class JavaConfig{

}

然后,在main方法中,开启调用:


public class Main { 

    public static void main(String[] args) { 

        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);

        MyCalculatorImpl myCalculator = ctx.getBean(MyCalculatorImpl.class); 

        myCalculator.add(3, 4); 

        myCalculator.min(3, 4); 

    } 

}

可以将切点统一定义,方便修改:


@Component
@Aspect //表示这是一个切面
public class LogAspect{

    //统一定义切点
    @Pointcut("@annotation(Action)")
    public void pointcut(){

    }

    //前置通知
    //jointPoint包含了目标方法的关键信息
    //@Before 注解表示这是一个前置通知,即在目标方法执行之前执行,注解中需要填入切点
    @Before(value = "pointcut()")
    public void before(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行");
    }

    //后置通知
    //joinPoint包含了目标方法的所有关键信息
    //@After表示这是一个后置通知,即在目标方法执行之后执行
    @After(value = "pointcut()")
    public void after(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束");
    }

    //返回通知
    //@AfterReturning返回通知,即目标方法有返回值时才触发,该注解中的returning属性表示目标方法返回值的变量名。目标方法的返回值类型和这里方法返回值参数的类型一致,否则拦截不到,如果想拦截所有(包括返回值为void),则方法返回值参数可以为Object
    @AfterReturning(value = "pointcut()", returning = "r")
    public void returning(JoinPoint joinPoint, Integer r){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法返回"+r);
    }
    
    //异常通知
    //e为目标方法所抛出的异常,这个参数必须是目标方法所抛出的异常或者所抛出的异常的父类,如果想拦截所有,参数类型声明为Exception
    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void before(JoinPoint joinPoint, Exception e){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法抛异常了"+e);
    }

    //环绕通知
    //可以用环绕通知实现上面四个通知,方法的核心类似于这里通过反射执行方法
    //这里的返回值类型最好的Object,和拦截到的方法相匹配
    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint pjp){
        Object proceed = null;
        try{
            //这个相当于method.invoke方法,我们可以在这个方法的前后分别添加日志,相当于前置/后置通知
            proceed = pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return proceed;
    }
}

(3)定义切点—使用规则(推荐)

可以进一步优化为非侵入式的,不需要@Action注解:


@Component
@Aspect //表示这是一个切面
public class LogAspect{

    /*
     * 可以统一定义切点
     * 第一个 * 表示要拦截的目标方法返回值任意(可以明确指定返回值类型
     * 第二个 * 表示包中的任意类(可以明确指定类
     * 第三个 * 表示类中的任一方法
     * 最后面的两个点表示方法参数任意,个数任意,类型任意
     * */
    @Pointcut("execution(* org.luyangsiyi.aop.commons.*.*(..))")
    public void pointcut(){

    }
    
    //前置通知
    //jointPoint包含了目标方法的关键信息
    //@Before 注解表示这是一个前置通知,即在目标方法执行之前执行,注解中需要填入切点
    @Before(value = "pointcut()")
    public void before(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行");
    }

    //后置通知
    //joinPoint包含了目标方法的所有关键信息
    //@After表示这是一个后置通知,即在目标方法执行之后执行
    @After(value = "pointcut()")
    public void after(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束");
    }

    //返回通知
    //@AfterReturning返回通知,即目标方法有返回值时才触发,该注解中的returning属性表示目标方法返回值的变量名。目标方法的返回值类型和这里方法返回值参数的类型一致,否则拦截不到,如果想拦截所有(包括返回值为void),则方法返回值参数可以为Object
    @AfterReturning(value = "pointcut()", returning = "r")
    public void returning(JoinPoint joinPoint, Integer r){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法返回"+r);
    }

    //异常通知
    //e为目标方法所抛出的异常,这个参数必须是目标方法所抛出的异常或者所抛出的异常的父类,如果想拦截所有,参数类型声明为Exception
    @AfterThrowing(value = "pointcut()", throwing = "e")
    public void before(JoinPoint joinPoint, Exception e){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法抛异常了"+e);
    }

    //环绕通知
    //可以用环绕通知实现上面四个通知,方法的核心类似于这里通过反射执行方法
    //这里的返回值类型最好的Object,和拦截到的方法相匹配
    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint pjp){
        Object proceed = null;
        try{
            //这个相当于method.invoke方法,我们可以在这个方法的前后分别添加日志,相当于前置/后置通知
            proceed = pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return proceed;
    }
}

4、XML配置Aop

(1)在pom.xml中引入依赖


<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.2</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjrt</artifactId>
        <version>1.9.2</version>
    </dependency>
</dependencies>

(2)定义通知/增强


public class LogAspect{

    public void before(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法开始执行");
    }
    
    public void after(JoinPoint joinPoint){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法执行结束");
    }

    public void returning(JoinPoint joinPoint, Integer r){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法返回"+r);
    }
    
    public void before(JoinPoint joinPoint, Exception e){
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();
        System.out.println(name + "方法抛异常了"+e);
    }
    
    public Object around(ProceedingJoinPoint pjp){
        Object proceed = null;
        try{
            //这个相当于method.invoke方法,我们可以在这个方法的前后分别添加日志,相当于前置/后置通知
            proceed = pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return proceed;
    }
}

(3)在sping中配置Aop


<bean class="org....aop.LogAspect" id="logAspect"/>

<aop:config>

    <aop:pointcut id="pc1" expression="execution(* org....aop.commons.*.*(..))"/>

    <aop:aspect ref="logAspect">

        <aop:before method="before" pointcut-ref="pc1"/>

        <aop:after method="after" pointcut-ref="pc1"/>

        <aop:after-returning method="returning" pointcut-ref="pc1" returning="r"/>

        <aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>

        <aop:around method="around" pointcut-ref="pc1"/>

    </aop:aspect>

</aop:config>

(4)main方法中加载配置文件


public class Main { 

    public static void main(String[] args) { 

        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        MyCalculatorImpl myCalculator = ctx.getBean(MyCalculatorImpl.class); 

        myCalculator.add(3, 4); 

        myCalculator.min(5, 6); 

    } 

}

四、JdbcTemplate

JdbcTemplate是Spring利用Aop思想封装的JDBC操作工具。

1、准备

新建一个项目,在pom.xml中添加依赖:


<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.17</version>
    </dependency>
</dependencies>

准备一个数据库:


CREATE DATABASE test01;

USE `test01`;/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`username` varchar(255) DEFAULT NULL,

`address` varchar(255) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

准备一个实体类:


package org.luyangsiyi.test02.bean;

import java.awt.print.Book;

/**
 * Created by luyangsiyi on 2020/2/14
 */
public class User {

    private int id;
    private String username;
    private String address;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", usernname='" + username + '\'' +
                ", address='" + address + '\'' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String usernname) {
        this.username = usernname;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

2、Java配置

提供一个配置类,在配置类中配置JdbcTemplate:


package org.luyangsiyi.Config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

/**
 * Created by luyangsiyi on 2020/2/16
 */
@Configuration
public class JdbcConfig {
    @Bean
    DataSource dataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test01");
        return dataSource;
    }
    @Bean
    JdbcTemplate jdbcTemplate(){
        return new JdbcTemplate(dataSource());
    }
}

提供两个Bean,一个是DataSource的Bean,另一个是JdbcTemplate的Bean,JdbcTemplate的配置非常容易,只要new一个Bean出来,然后配置一下DataSource即可。


package org.luyangsiyi.main;

import org.junit.Before;
import org.junit.Test;
import org.luyangsiyi.Config.JdbcConfig;
import org.luyangsiyi.test02.bean.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * Created by luyangsiyi on 2020/2/16
 */
public class Main {
    private JdbcTemplate jdbcTemplate;

    @Before
    public void before(){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JdbcConfig.class);
        jdbcTemplate = ctx.getBean(JdbcTemplate.class);
    }

    @Test
    public void insert(){
        jdbcTemplate.update("insert into user (username,address) values (?,?);","java","beijing");
    }

    @Test
    public void update(){
        jdbcTemplate.update("update user set username=? where id=?","java1",1);
    }

    @Test
    public void delete(){
        jdbcTemplate.update("delete from user where id==?",2);
    }

    @Test
    public void select(){
        User user = jdbcTemplate.queryForObject("select * from user where id=?",new BeanPropertyRowMapper<User>(User.class),1);
        System.out.println(user);
    }
}

在查询时,如果使用了BeanPropertyRowMapper,要求查出来的字段必须和Bean的属性名一一对应。如果不一样,则不要使用BeanPropertyRowMapper,此时需要自定义RowMapper或者给查询的字段取别名。

(1)给查询出来的列取别名


@Test

public void select2(){

    User user = jdbcTemplate.queryForObject("select id,username, as name, address from user where id=?", new BeanPropertyMapper<User>(User.class), 1);

    System.out.println(user);

}

(2)自定义RowMapper


@Test

public void select3(){

    User user = jdbcTemplate.queryForObject("select * from user where id=?", new RowMapper<User>() {

        int id = resultSet.getInt("id");   

        String username = resultSet.getString("username");

        String address = resultSet.getString("address");

        User u = new User();   

        u.setId(id);

        u.setName(username);

        u.setAddress(address);

        return u;

    },1);

    System.out.println(user);

}

(3)查询多条记录


@Test

public void select4(){

    List<User> list = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));

    System.out.println(list);

}

3、xml配置

以上配置,可以通过xml文件来实现。通过xml文件实现只是提供JdbcTemplate实例,剩下的代码还是Java代码,就是JdbcConfig被XML文件代替而已。


<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">

    <property name="username" value="root"/>

    <property name="password" value="123456"/>

    <property name="url" value="jdbc:mysql://localhost:3306/test01"/>

</bean>

<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">

    <property name="dataSource" ref="dataSource"/>

</bean>

配置完成后,加载该配置文件,并启动:


package org.luyangsiyi.main;

import org.junit.Before;
import org.junit.Test;
import org.luyangsiyi.Config.JdbcConfig;
import org.luyangsiyi.test02.bean.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

/**
 * Created by luyangsiyi on 2020/2/16
 */
public class Main {
    private JdbcTemplate jdbcTemplate;

    @Before
    public void before(){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext("applicationContext.xml");
        jdbcTemplate = ctx.getBean(JdbcTemplate.class);
    }

    @Test
    public void insert(){
        jdbcTemplate.update("insert into user (username,address) values (?,?);","java","beijing");
    }

    @Test
    public void update(){
        jdbcTemplate.update("update user set username=? where id=?","java1",1);
    }

    @Test
    public void delete(){
        jdbcTemplate.update("delete from user where id==?",2);
    }

    @Test
    public void select(){
        User user = jdbcTemplate.queryForObject("select * from user where id=?",new BeanPropertyRowMapper<User>(User.class),1);
        System.out.println(user);
    }
}

六、事务

Spring中的事务主要是利用Aop思想,简化事务的配置,可以通过Java配置也可以通过XML配置。

准备一个数据库:


USE `test01`;/*Table structure for table `user` */

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (

`id` int(11) NOT NULL AUTO_INCREMENT,

`username` varchar(255) DEFAULT NULL,

`money` int(11) DEFAULT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB auto_increment=3 CHARSET=utf8;

insert into `account`(`id`,`username`,`money`) values (1,'zhangsan',1000),(2,'lisi',1000);

然后配置JdbcTemplate,按照5中的方法。

然后提供转账操作的方法:


@Repository

public class UserDao{

    @Autowired

    JdbcTemplate jdbcTemplate;



    public void addMoney(String username, Integer money) {

        jdbcTemplate.update("update account set money = money+? where username=?", money, username);     

    }



    public void minMoney(String username, Integer money){

         jdbcTemplate.update("update account set money = money-? where username=?", money, username);    

    }

}



@Service

public class UserService{

    @Autowired

    UserDao userDao;

    public void updateMoney(){

        userDao.addMoney("zhangsan",200);

        int i = 1/0;

        userDao.minMoney("lisi", 200);

    }

}

然后在xml文件中开启自动化扫描:


<context:component-scan base-package="org...."/>

<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">

    <property name="username" value="root"/>

    <property name="password" value="123456"/>

    <property name="url" value="jdbc:mysql://localhost:3306/test01"/>

</bean>

<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">

    <property name="dataSource" ref="dataSource"/>

</bean>

1、XML配置

xml中配置事务一共分为三个步骤:

(1)配置TransactionManager


<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManeger" id="transactionManager">

    <property name="dataSource" ref="dataSource"/>

</bean>

(2)配置事务要处理的方法


<tx:advice id="txAdvice" transaction-manager="transactionManager">

    <tx:attributes>

        <tx:method name=“update*"/>

        <tx:method name=“insert*"/>
        <tx:method name=“add*"/>

        <tx:method name=“delete*"/>
    </tx:attributes>

</tx:advice>

一旦配置了方法名称规则后,service中的方法一定要按照这里的命名规则来,否则事务配置不会生效。

(3)配置Aop


<aop:config>

    <aop:pointcut id="pc1" expression="execution(* org....service.*.*(..))"/>

    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>

</aop-config>

2、Java配置

如果要开启Java注解配置,在XML配置中添加如下配置:


<tx:annotation-driven transaction-manager="transactionManger"/>

这行配置,可以代替下面的两个配置:


<tx:advice id="txAdvice" transaction-manager="transactionManager">

    <tx:attributes>

        <tx:method name=“update*"/>

        <tx:method name=“insert*"/>
        <tx:method name=“add*"/>

        <tx:method name=“delete*"/>
    </tx:attributes>

</tx:advice>

<aop:config>

    <aop:pointcut id="pc1" expression="execution(* org....service.*.*(..))"/>

    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>

</aop-config>

在需要添加事务的方法上,添加@Transactional注解,表示该方法开启事务,当然这个注解也可以放在类上,表示这个类中的所有方法都开启事务。


@Service

public class UserService{

    @Autowired

    UserDao userDao;

    @Transactional 

    public void updateMoney(){

        userDao.addMoney("zhangsan",200);

        int i = 1/0;

        userDao.minMoney("lisi", 200);

    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值