资源
- 下载地址:https://repo.spring.io/release/org/springframework/spring/
- docs文档、api文档地址:https://docs.spring.io/spring-framework/docs/
- 中文文档地址:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference
- github地址:https://github.com/spring-projects/spring-framework
- 笔记地址:https://github.com/userwusir/study-notes
控制反转(IOC容器)重点
一种通过描述(xml或注解)并通过第三方去生产或获取特定对象的方式
Spring实现控制反转的是IOC容器,实现方法:依赖注入
理解
程序从程序员控制(对象创建)--->用户控制(选择对象)
原理
从前
UserDao
public interface UserDao { /** * get user */ void getUser(); }
UserDaoImpl
public class UserDaoImpl implements UserDao { @Override public void getUser() { System.out.println("获取用户信息"); } }
UserService
public interface UserService { /** * get user */ void getUser(); }
UserServiceImpl
public class UserServiceImpl implements UserService{ private UserDao userDao = new UserDaoImpl(); @Override public void getUser() { userDao.getUser(); } }
Test
public void test(){ UserService userService = new UserServiceImpl(); userService.getUser(); }
结果
问题:此时,若是一个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(); } }
结果:
总结:这是由程序员控制对象的创建
现在
在原有UserDao、UserDaoImpl、UserService不变的情况下进行改进
UserServiceImpl
public class UserServiceImpl implements UserService { private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } @Override public void getUser() { userDao.getUser(); } }
Test
public void test(){ UserServiceImpl userService = new UserServiceImpl(); userService.setUserDao(new UserDaoImpl()); userService.getUser(); }
总结:UserServiceImpl实现了setter方法来获取用户选择创建的UserDao对象调用对应UserDaoImpl的方法,实现了由程序员控制对象创建到用户控制对象创建。
Spring配置
alias
给bean取别名
bean
对象,id:对象唯一标识符,class:对象地址,包名+类型,name:取别名,可以取多个(各种分隔符)
import
团队多人开发,多个配置文件导入到总配置文件
example
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(applicationContext.xml)进行管理
通过Bean创建对象
在applicationContext.xml中配置Bean(相当于new对象),默认pojo层使用无参构造方法,有参构造方法有三种配置Bean方式,在加载applicationContext.xml文件的时候对象就已经创建好了。
无参构造
Hello的无参构造
public Hello() { System.out.println("Hello的无参构造"); }
applicationContext.xml
Test
public void test(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); }
结果:
有参构造
Hello的有参构造
public Hello(String str) { this.str = str; System.out.println("Hello的有参构造"); }
方式一,下标(index)
applicationContext.xml
Test
public void test(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Hello hello = (Hello) applicationContext.getBean("hello"); System.out.println(hello.toString()); }
结果:
方式二,类型(type),不建议使用
结果:
方式三,参数名(name)
结果:
依赖注入(DI)
程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入,对比着控制反转来理解。
1、构造器注入
查看通过Bean创建对象
2、Setter方法注入
测试环境
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; }
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); }
结果:
注意:
配置文件插入
xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c"
Bean作用域
Scope | Description |
---|---|
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
自动装配
xml实现自动装配
example
byName:在容器上下文查找和自己set方法对象一致的类型,注意保证全局id唯一
byType:在容器上下文查找和自己对象属性一致的类型,可以不写id
注解实现自动装配
注意xml文件,需要增加注解支持
-->
Spring提供的注解
book和books类型相同,name不同
@Autowired @Qualifier(value = "books") private Book book;
Autowired通过byType装配,如果有相同类型,则通过byName,可以用在属性字段上,也可用于方法上
Qualifier指定name去装配,两个搭配使用效果更佳
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()); }
结果:
由@Component衍生出的几个注解,在MVC三层架构时使用
- Dao ---> @Repository
- Service ---> @Service
- Controller ---> @Controller
使用@Configuration配置开发
@Configuration用于定义配置类,代替xml文件配置,该类内部包含一个或多个@Bean注解的方法,此类通过AnnotationConfigApplicationContext类进行扫描,构建Bean,初始化Spring。
使用@Bean
pojo
/** * @author wulele */ public class User { @Value("wll") private String name; //下面这两行可不写 @Autowired private User user; public String getName() { return name; } }
UserConfig
/** * @author wulele */ @Configuration public class UserConfig { //取别名,默认为getUser @Bean(name = "user") //设置作用域 @Scope("singleton") //描述 @Description("return user") public User getUser() { return new User(); } }
相当于
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()); }
结果
使用@Component和@ComponentScan(包扫描)
pojo
/** * @author wulele */ //取别名,默认user @Component("getUser") public class User { @Value("wll") private String name; public String getName() { return name; } }
UserConfig
/** * @author wulele */ @Configuration //包扫描,等价于@ComponentScan("com.wll.pojo") //导入配置文件 //@Import(UserConfig2.class) public class UserConfig { public User getUser() { return new User(); } }
总结
- @Component注解表明一个类会作为组件类,并告知Spring要为这个类创建bean,搭配@ComponentScan注解使用
- @Bean注解告诉Spring这个方法将会返回一个对象,这个对象要注册为Spring应用上下文中的bean。相较于@Component更加灵活
代理
静态代理
本人(RealSubject)和代理人(ProxySubject)都是对象,实现了同一个主题(Subject)。如果本人太忙,有些工作无法自己亲自完成,就将其交给代理人负责。
Subject
/** * 主题 * @author wulele */ public interface Subject { /** * 业务操作 * @param str */ public void doSomething(String str); }
RealSubject
/** * 真实对象(本人) * @author wulele */ public class RealSubject implements Subject { @Override public void doSomething(String str) { System.out.println("something ---> " + str); } }
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); } }
Client
public class Client { public static void main(String[] args) { Subject subject = new ProxySubject(); subject.doSomething("RealSubject"); } }
结果
动态代理
参考《设计模式之禅》,资源地址:链接:https://pan.baidu.com/s/1Pot02W-5DpKZ5zeaHXS-Rg 提取码:86dz
反射学习地址:注解和反射学习笔记 - 芜湖男酮 - 博客园 (cnblogs.com)
动态代理调用过程示意图:
实现动态代理有两个重要的类和接口InvocationHandler(接口)和Proxy(类),当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke()方法来调用
Subject
/** * 主题 * * @author wulele */ public interface Subject { /** * 业务操作 * * @param str */ public void doSomething(String str); }
RealSubject
/** * 真实对象(本人) * * @author wulele */ public class RealSubject implements Subject { @Override public void doSomething(String str) { System.out.println("real subject ---> " + str); } }
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); } }
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); } }
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接口
UserService
/** * @author wulele */ public interface UserService { /** * add */ public void add(); /** * delete */ public void delete(); }
UserServiceImpl
/** * @author wulele */ public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加用户"); } @Override public void delete() { System.out.println("删除用户"); } }
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()+"()"); } }
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() + "()"); } }
applicationContext.xml
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(); } }
结果
方式二:自定义类
MyLog
/** * @author wulele */ public class MyLog { public void before(){ System.out.println("method before"); } public void after(){ System.out.println("method after"); } }
applicationContext.xml
方式三:注解
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"); } }
applicationContext.xml
结果
整合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)
方式一
文件结构
User
/** * @author wulele */ @Data public class User { private int id; private String name; private String pwd; }
UserMapper
/** * @author wulele */ public interface UserMapper { /** * get user list * * @return */ ListgetUser(); }
UserMapper.xml
select * from mybatis.user
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(); } }
mybitas-config.xml
spring-mybatis.xml
applicationContext.xml
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); } } }
结果
总结
将mybatis的配置简化,最后注册成为一个bean来让我们获取所需的sqlsession,不再用为了sqlsession而写一个工具类,但是多了一个UserMapperImpl的实现类来返回查询结果(加了一层)。
方式二
在方式一的基础上选择使用SqlSessionDaoSupport而不是SqlSessionTemplate
修改UserMapperImpl
/** * @author wulele */ public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{ @Override public ListgetUser() { return getSqlSession().getMapper(UserMapper.class).getUser(); } }
修改spring-mybatis.xml,删除以下部分
修改applicationContext.xml
总结
将我们原本需要去获取SqlSessionTemplate的过程封装在了SqlSessionDaoSupport中,简化了操作。
事务管理
- 声明式事务:AOP切入,不影响原有代码
- 编程式事务:在代码中进行事务管理
声明式事务
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); }
UserMapper.xml
insert into user(id,name,pwd) values(#{id},#{name},#{pwd})deletes from user where id = #{id}select * from mybatis.user
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); } }
spring-mybatis.xml
applicationContext.xml
--> --> -->