Spring学习笔记

资源

  1. 下载地址:https://repo.spring.io/release/org/springframework/spring/
  2. docs文档、api文档地址:https://docs.spring.io/spring-framework/docs/
  3. 中文文档地址:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference
  4. github地址:https://github.com/spring-projects/spring-framework
  5. 笔记地址:https://github.com/userwusir/study-notes

控制反转(IOC容器)重点

一种通过描述(xml或注解)并通过第三方去生产或获取特定对象的方式

Spring实现控制反转的是IOC容器,实现方法:依赖注入

理解

程序从程序员控制(对象创建)--->用户控制(选择对象)

原理

从前

  1. UserDao

    public interface UserDao {
        /**
         * get user
         */
        void getUser();
    }
  2. UserDaoImpl

    public class UserDaoImpl implements UserDao {
    
        @Override
        public void getUser() {
            System.out.println("获取用户信息");
        }
    }
  3. UserService

    public interface UserService {
        /**
         * get user
         */
        void getUser();
    }
  4. UserServiceImpl

    public class UserServiceImpl implements UserService{
        private UserDao userDao = new UserDaoImpl();
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
  5. Test

    public void test(){
        UserService userService = new UserServiceImpl();
        userService.getUser();
    }
  6. 结果

    Spring学习笔记_Spring

    问题:此时,若是一个UserDaoImpl2继承UserDao,实现方法内容不同,若是想要在Service层调用UserDaoImpl2的方法,则需要修改Service层的代码,如下:

    UserDaoImpl2

    public class UserDaoImpl2 implements UserDao{
        @Override
        public void getUser() {
            System.out.println("获取用户二");
        }
    }

    UserServiceImpl

    public class UserServiceImpl implements UserService{
        //此处代码做了修改
        private UserDao userDao = new UserDaoImpl2();
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }

    结果:

    Spring学习笔记_Spring_02

    总结:这是由程序员控制对象的创建

现在

在原有UserDao、UserDaoImpl、UserService不变的情况下进行改进

  1. UserServiceImpl

    public class UserServiceImpl implements UserService {
        private UserDao userDao;
    
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
    
        @Override
        public void getUser() {
            userDao.getUser();
        }
    }
  2. Test

    public void test(){
        UserServiceImpl userService = new UserServiceImpl();
        userService.setUserDao(new UserDaoImpl());
        userService.getUser();
    }

    总结:UserServiceImpl实现了setter方法来获取用户选择创建的UserDao对象调用对应UserDaoImpl的方法,实现了由程序员控制对象创建到用户控制对象创建。

Spring配置

  1. alias

    给bean取别名


  2. bean

    对象,id:对象唯一标识符,class:对象地址,包名+类型,name:取别名,可以取多个(各种分隔符)


  3. import

    团队多人开发,多个配置文件导入到总配置文件

    example

    Spring学习笔记_Spring_03

    applicationContext.xml


HelloSpring

第一个Spring程序

public class HelloSpring {
    private String str;

    public String getStr() {
        return str;
    }

    //依赖注入的核心
    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "HelloSpring{" +
            "str='" + str + '\'' +
            '}';
    }
}

applicationContext.xml


       

Test

public void test(){
    //加载配置文件
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    //获取对象
    HelloSpring hello = (HelloSpring) applicationContext.getBean("hello");
    System.out.println(hello.toString());
}

结果:

Spring学习笔记_Spring_04

总结:对象的创建、管理、装配统一由Spring(applicationContext.xml)进行管理

通过Bean创建对象

在applicationContext.xml中配置Bean(相当于new对象),默认pojo层使用无参构造方法,有参构造方法有三种配置Bean方式,在加载applicationContext.xml文件的时候对象就已经创建好了。

  1. 无参构造

    Hello的无参构造

    public Hello() {
        System.out.println("Hello的无参构造");
    }

    applicationContext.xml


    Test

    public void test(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    }

    结果:

    Spring学习笔记_Spring_05

  2. 有参构造

    Hello的有参构造

    public Hello(String str) {
        this.str = str;
        System.out.println("Hello的有参构造");
    }
    1. 方式一,下标(index)

      applicationContext.xml


      Test

      public void test(){
          ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
          Hello hello = (Hello) applicationContext.getBean("hello");
          System.out.println(hello.toString());
      }

      结果:

      Spring学习笔记_Spring_06

    2. 方式二,类型(type),不建议使用


      结果:

      Spring学习笔记_Spring_07

    3. 方式三,参数名(name)


      结果:

      Spring学习笔记_Spring_08

依赖注入(DI)

程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入,对比着控制反转来理解。

1、构造器注入

查看通过Bean创建对象

2、Setter方法注入

  1. 测试环境

    pojo

    /**
     * @author wulele
     */
    public class Email {
        private String email;
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        @Override
        public String toString() {
            return "Email{" +
                "email='" + email + '\'' +
                '}';
        }
    }
    /**
     * @author wulele
     */
    @Data
    public class User {
        private String name;
        private Email email;
        private String[] products;
        private Listhobbies;
        private Mapgames;
        private Properties info;
        private String none;
    }
  2. applicationContext.xml

    手机电脑唱跳Rap男20

    Test

    public static void main(String[] args) {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user.toString());
    }

    结果:

    User(name=wll, 
    	email=Email{email='1415155099@qq.com'}, 
    	products=[手机, 电脑], hobbies=[唱, 跳, Rap], 
    	games={moba=英雄联盟}, 
    	info={性别=男, 年龄=20}, 
    	none=null)

3、拓展方式注入

p命名空间和c命名空间注入

pojo

/**
 * @author wulele
 */
@Data
public class Game {
    private String type;
    private String name;

    public Game() {
    }

    public Game(String type, String name) {
        this.type = type;
        this.name = name;
    }
}

applicationContext.xml


Test

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    Game game = applicationContext.getBean("game", Game.class);
    Game game2 = applicationContext.getBean("game2", Game.class);
    System.out.println(game);
    System.out.println(game2);
}

结果:

Spring学习笔记_Spring_09

注意:

配置文件插入

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

Bean作用域

ScopeDescription
singleton(默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。
prototype将单个 bean 定义的作用域限定为任意数量的对象实例。
request将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。
session将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。

example

Spring学习笔记_Spring_10

自动装配

xml实现自动装配

example

Spring学习笔记_Spring_11

byName:在容器上下文查找和自己set方法对象一致的类型,注意保证全局id唯一

byType:在容器上下文查找和自己对象属性一致的类型,可以不写id

注解实现自动装配

注意xml文件,需要增加注解支持

-->
  1. Spring提供的注解

        

    book和books类型相同,name不同

    @Autowired
    @Qualifier(value = "books")
    private Book book;

    Autowired通过byType装配,如果有相同类型,则通过byName,可以用在属性字段上,也可用于方法上

    Qualifier指定name去装配,两个搭配使用效果更佳

  2. Java自带的注解

    @Resource(name = "books")
    private Book book;

    类似于Autowired + Qualifier,开发常用Autowired

使用注解开发

applicationContext.xml

-->
    -->
    -->

pojo

/**
 * @author wulele
 */
//@Component
@Scope("singleton")
public class User {
    //@Value("wll")
    private String name;

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

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

Test

public static void main(String[] args) {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = applicationContext.getBean("user", User.class);
    System.out.println(user.toString());
}

结果:

Spring学习笔记_Spring_12

由@Component衍生出的几个注解,在MVC三层架构时使用

  • Dao  --->   @Repository
  • Service  --->   @Service
  • Controller  --->   @Controller

使用@Configuration配置开发

@Configuration用于定义配置类,代替xml文件配置,该类内部包含一个或多个@Bean注解的方法,此类通过AnnotationConfigApplicationContext类进行扫描,构建Bean,初始化Spring。

  1. 使用@Bean

    1. pojo

      /**
       * @author wulele
       */
      public class User {
          @Value("wll")
          private String name;
          //下面这两行可不写
          @Autowired
          private User user;
      
          public String getName() {
              return name;
          }
      }
    2. UserConfig

      /**
       * @author wulele
       */
      @Configuration
      public class UserConfig {
          //取别名,默认为getUser
          @Bean(name = "user")
          //设置作用域
          @Scope("singleton")
          //描述
          @Description("return user")
          public User getUser() {
              return new User();
          }
      }

      相当于


    3. Test

      public static void main(String[] args) {
          ApplicationContext applicationContext = new AnnotationConfigApplicationContext(UserConfig.class);
          User user = applicationContext.getBean("user", User.class);
          System.out.println(user.getName());
      }
    4. 结果

      Spring学习笔记_Spring_13

  2. 使用@Component和@ComponentScan(包扫描)

    1. pojo

      /**
       * @author wulele
       */
      //取别名,默认user
      @Component("getUser")
      public class User {
          @Value("wll")
          private String name;
      
          public String getName() {
              return name;
          }
      }
    2. UserConfig

      /**
       * @author wulele
       */
      @Configuration
      //包扫描,等价于@ComponentScan("com.wll.pojo")
      //导入配置文件
      //@Import(UserConfig2.class)
      public class UserConfig {
          public User getUser() {
              return new User();
          }
      }
  3. 总结

  • @Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean,搭配@ComponentScan注解使用
  • @Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。相较于@Component更加灵活

代理

静态代理

本人(RealSubject)和代理人(ProxySubject)都是对象,实现了同一个主题(Subject)。如果本人太忙,有些工作无法自己亲自完成,就将其交给代理人负责。

  1. Subject

    /**
     * 主题
     * @author wulele
     */
    public interface Subject {
        /**
         * 业务操作
         * @param str
         */
        public void doSomething(String str);
    }
  2. RealSubject

    /**
     * 真实对象(本人)
     * @author wulele
     */
    public class RealSubject implements Subject {
        @Override
        public void doSomething(String str) {
            System.out.println("something ---> " + str);
        }
    }
  3. ProxySubject

    /**
     * 代理对象(代理人)
     * @author wulele
     */
    public class ProxySubject implements Subject {
        private RealSubject realSubject = null;
    
        public ProxySubject() {
            this.realSubject = new RealSubject();
        }
    
        @Override
        public void doSomething(String str) {
            this.before("ProxySubject");
            realSubject.doSomething(str);
        }
    
        public void before(String str) {
            System.out.println("something ---> " + str);
        }
    }
  4. Client

    public class Client {
        public static void main(String[] args) {
            Subject subject = new ProxySubject();
            subject.doSomething("RealSubject");
        }
    }
  5. 结果

    Spring学习笔记_Spring_14

动态代理

参考《设计模式之禅》,资源地址:链接:https://pan.baidu.com/s/1Pot02W-5DpKZ5zeaHXS-Rg  提取码:86dz

反射学习地址:注解和反射学习笔记 - 芜湖男酮 - 博客园 (cnblogs.com)

动态代理调用过程示意图:

Spring学习笔记_Spring_15

实现动态代理有两个重要的类和接口InvocationHandler(接口)和Proxy(类),当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke()方法来调用

  1. Subject

    /**
     * 主题
     *
     * @author wulele
     */
    public interface Subject {
        /**
         * 业务操作
         *
         * @param str
         */
        public void doSomething(String str);
    }
  2. RealSubject

    /**
     * 真实对象(本人)
     *
     * @author wulele
     */
    public class RealSubject implements Subject {
        @Override
        public void doSomething(String str) {
            System.out.println("real subject ---> " + str);
        }
    }
  3. MyInvocationHandler

    /**
     * 动态代理的Handler类
     *
     * @author wulele
     */
    public class MyInvocationHandler implements InvocationHandler {
        /**
         * 接收任意对象
         */
        private Object target;
    
        public MyInvocationHandler(Object target) {
            this.target = target;
        }
    
        /**
         * 不是我们显式的调用这个方法
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            return method.invoke(this.target, args);
        }
    }
  4. DynamicProxy

    /**
     * 动态代理类
     * 泛型传入什么类型参数,就返回什么类型的参数
     *
     * @author wulele
     */
    public class DynamicProxy{
        public staticT newProxy(Subject subject) {
            //获取ClassLoader
            ClassLoader loader = subject.getClass().getClassLoader();
            //获取接口数组
            Class[] interfaces = subject.getClass().getInterfaces();
            //获取handler
            MyInvocationHandler handler = new MyInvocationHandler(subject);
            before("返回代理对象");
            return (T) Proxy.newProxyInstance(loader, interfaces, handler);
        }
    
        public static void before(String msg) {
            System.out.println("dynamic proxy ---> " + msg);
        }
    }
  5. Client

    /**
     * 场景设置
     *
     * @author wulele
     */
    public class Client {
        public static void main(String[] args) {
            //定义一个主题
            Subject subject = new RealSubject();
            //定义主题的代理
            Subject proxy = DynamicProxy.newProxy(subject);
            //代理的行为
            proxy.doSomething("doSomething");
        }
    }

面向切面编程(AOP)重点

AOP实现机制的核心就是动态代理,将核心类与非核心类进行分离,找到切入点横向扩展代码,不侵入原有代码。

方式一:Spring的API接口

  1. UserService

    /**
     * @author wulele
     */
    public interface UserService {
        /**
         * add
         */
        public void add();
    
        /**
         * delete
         */
        public void delete();
    }
  2. UserServiceImpl

    /**
     * @author wulele
     */
    public class UserServiceImpl implements UserService{
        @Override
        public void add() {
            System.out.println("增加用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除用户");
        }
    }
  3. BeforeLog

    /**
     * @author wulele
     */
    public class BeforeLog implements MethodBeforeAdvice {
        @Override
        public void before(Method method, Object[] objects, Object o) throws Throwable {
            System.out.println(o.getClass().getName() + "的" + method.getName()+"()");
        }
    }
  4. AfterLog

    /**
     * @author wulele
     */
    public class AfterLog implements AfterReturningAdvice {
        @Override
        public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
            System.out.println("返回值" + o + " " + o1.getClass().getName() + " " + method.getName() + "()");
        }
    }
  5. applicationContext.xml


  6. Test

    public class MyTest {
        public static void main(String[] args) {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            //代理的是接口
            UserService userService = applicationContext.getBean("userService", UserService.class);
            userService.add();
        }
    }
  7. 结果

    Spring学习笔记_Spring_16

方式二:自定义类

  1. MyLog

    /**
     * @author wulele
     */
    public class MyLog {
        public void before(){
            System.out.println("method before");
        }
        public void after(){
            System.out.println("method after");
        }
    }
  2. applicationContext.xml


方式三:注解

  1. AnnotationLog

    /**
     * Aspect 标记这个类是一个切面
     * @author wulele
     */
    @Aspect
    public class AnnotationLog {
        @Before("execution(* com.wll.service.UserServiceImpl.*(..))")
        public void before() {
            System.out.println("method before");
        }
    
        @After("execution(* com.wll.service.UserServiceImpl.*(..))")
        public void after() {
            System.out.println("method after");
        }
    
        @Around("execution(* com.wll.service.UserServiceImpl.*(..))")
        public void around(ProceedingJoinPoint pj) throws Throwable {
            //pj参数代表我们要切入的点
            System.out.println("around before");
            //打印签名
            System.out.println(pj.getSignature());
            //执行方法
            pj.proceed();
            System.out.println("around after");
        }
    }
  2. applicationContext.xml


  3. 结果

    Spring学习笔记_Spring_17

整合Mybatis

官网:mybatis-spring –

pom.xml导入下列包

mybatis、mysql-connector-java、aspectjweaver、mybatis-spring、spring-webmvc、spring-jdbc、junit

编写User、UserMapper、UserMapper.xml、mybatis-config.xml

参考文章:Mybatis学习笔记 - 芜湖男酮 - 博客园 (cnblogs.com)

方式一

  1. 文件结构

    Spring学习笔记_Spring_18

  2. User

    /**
     * @author wulele
     */
    @Data
    public class User {
        private int id;
        private String name;
        private String pwd;
    }
  3. UserMapper

    /**
     * @author wulele
     */
    public interface UserMapper {
        /**
         * get user list
         *
         * @return
         */
        ListgetUser();
    }
  4. UserMapper.xml

    select * from mybatis.user
  5. UserMapperImpl

    /**
     * @author wulele
     */
    public class UserMapperImpl implements UserMapper{
        /**
         * 以前使用sqlSession,现在使用SqlSessionTemplate
         */
        private SqlSessionTemplate sqlSession = null;
    
        public void setSqlSession(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        @Override
        public ListgetUser() {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.getUser();
        }
    }
  6. mybitas-config.xml



  7. spring-mybatis.xml


               
       
  8. applicationContext.xml


  9. Test

    public class MyTest {
        @Test
        public void test() throws IOException {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserMapper mapper = applicationContext.getBean("userMapperImpl", UserMapperImpl.class);
            ListuserList = mapper.getUser();
            for (User user : userList) {
                System.out.println(user);
            }
        }
    }
  10. 结果

    Spring学习笔记_Spring_19

  11. 总结

    将mybatis的配置简化,最后注册成为一个bean来让我们获取所需的sqlsession,不再用为了sqlsession而写一个工具类,但是多了一个UserMapperImpl的实现类来返回查询结果(加了一层)。

方式二

在方式一的基础上选择使用SqlSessionDaoSupport而不是SqlSessionTemplate

  1. 修改UserMapperImpl

    /**
     * @author wulele
     */
    public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
        @Override
        public ListgetUser() {
            return getSqlSession().getMapper(UserMapper.class).getUser();
        }
    }
  2. 修改spring-mybatis.xml,删除以下部分

        
  3. 修改applicationContext.xml


  4. 总结

    将我们原本需要去获取SqlSessionTemplate的过程封装在了SqlSessionDaoSupport中,简化了操作。

事务管理

  • 声明式事务:AOP切入,不影响原有代码
  • 编程式事务:在代码中进行事务管理

声明式事务

  1. UserMapper

    /**
     * @author wulele
     */
    public interface UserMapper {
        /**
         * get user list
         *
         * @return
         */
        ListgetUser();
    
        /**
         * add user
         *
         * @param user
         * @return
         */
        int addUser(User user);
    
        /**
         * delete user
         *
         * @param id
         * @return
         */
        int deleteUser(int id);
    }
  2. UserMapper.xml

    insert into user(id,name,pwd) values(#{id},#{name},#{pwd})deletes from user where id = #{id}select * from mybatis.user
  3. UserMapperImpl

    /**
     * @author wulele
     */
    public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
    
        @Override
        public ListgetUser() {
            User user = new User(5, "xxx", "qwe");
            UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
            //先执行添加用户
            mapper.addUser(user);
            //再执行删除用户,如果删除失败,按照事务原子性应该回滚
            mapper.deleteUser(5);
            return mapper.getUser();
        }
    
        @Override
        public int addUser(User user) {
            return getSqlSession().getMapper(UserMapper.class).addUser(user);
        }
    
        @Override
        public int deleteUser(int id) {
            return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
        }
    }
  4. spring-mybatis.xml


  5. applicationContext.xml

    -->
    -->
    -->