# springdemo **Repository Path**: coderxgc/springdemo ## Basic Information - **Project Name**: springdemo - **Description**: springdemo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-07-06 - **Last Updated**: 2022-07-09 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # spring-demo > 本 demo 主要演示了Spring 中的IOC、AOP等内容 Ioc (Inversion of Control),中文叫做控制反转。这是一个概念,也是一种思想。控制反转,实际上就是指对一个对象的控制权的反转。 ```java public class Book { private Integer id; private String name; private Double price; //省略 getter/setter } public class User { private Integer id; private String name; private Integer age; public 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 容器去要。 ### 类注入 ```xml ``` 可以通过如下的方式进行读取 ```java //FileSystemXmlApplicationContext fileSystemXmlApplicationContext = new FileSystemXmlApplicationContext(new ClassPathXmlApplicationContext("绝对路径")); ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); User user1 = (User) ctx.getBean("user"); User user2 = ctx.getBean("user", User.class); User user3 = ctx.getBean(User.class); System.out.println("user1 = " + user1); System.out.println("user2 = " + user2); System.out.println("user3 = " + user3); ``` User.java ```java public class User { private String id; private String name; @Override public String toString() { return "User{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; } private User() { System.out.println("----------------------User Init---------------------"); } public User(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` ### 基本属性注入 #### 通过构造方法进行赋值 ```xml ``` #### 通过set方法进行赋值 ```xml ``` #### 通过名称空间进行赋值 ```xml ``` ### 工厂注入 #### 通过静态工厂注入 ```xml ``` #### 通过实例工厂方法注入 ```xml ``` ### 复杂属性注入 #### 注入对象 ```xml ``` Cat.java ```java public class Cat { private String name; private String age; 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 "Cat{" + "name='" + name + '\'' + ", age='" + age + '\'' + '}'; } } ``` #### 注入数组、list、map、properties ```xml 篮球 羽毛球 123456 ``` User.java ```java package com.ylesb.ioc.model; /** * @title: User * @projectName springdemo * @description: TODO * @author White * @site : [www.ylesb.com] * @date 2022/7/614:52 */ import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; /** * @className : User * @description : [描述说明该类的功能] * @author : [XuGuangchao] * @site : [www.ylesb.com] * @version : [v1.0] * @createTime : [2022/7/6 14:52] * @updateUser : [XuGuangchao] * @updateTime : [2022/7/6 14:52] * @updateRemark : [描述说明本次修改内容] */ public class User { private String id; private String name; private Cat cat; private Cat[] cats; private List favorites; private Map details; private Properties properties; private User() { System.out.println("----------------------User Init---------------------"); } public User(String id, String name) { this.id = id; this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Cat getCat() { return cat; } public void setCat(Cat cat) { this.cat = cat; } public Cat[] getCats() { return cats; } public void setCats(Cat[] cats) { this.cats = cats; } public List getFavorites() { return favorites; } public void setFavorites(List favorites) { this.favorites = favorites; } public Map getDetails() { return details; } public void setDetails(Map details) { this.details = details; } public Properties getProperties() { return properties; } public void setProperties(Properties properties) { this.properties = properties; } @Override public String toString() { return "User{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", cat=" + cat + ", cats=" + Arrays.toString(cats) + ", favorites=" + favorites + ", details=" + details + ", properties=" + properties + '}'; } } ``` ### Java配置 在 Spring 中,想要将一个 Bean 注册到 Spring 容器中,整体上来说,有三种不同的方式。 •XML 注入,如前文所说 •Java 配置(通过 Java 代码将 Bean 注册到 Spring 容器中) •自动化扫描 这里我们来看 Java 配置。 Java 配置这种方式在 Spring Boot 出现之前,其实很少使用,自从有了Spring Boot,Java 配置开发被广泛使用,因为在 Spring Boot 中,不使用一行XML 配置。 JavaConfig.class ```java @Configuration public class JavaConfig { @Bean SayHello sayHello() { return new SayHello(); } } ``` SayHello.class ```java public class SayHello { public String sayHello(String name) { return "hello"+name; } } ``` JavaConfigTest.class ```java public class JavaConfigTest { public static void main(String[] args) { AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(JavaConfig.class); SayHello sayHello=ctx.getBean("sh",SayHello.class); System.out.println("sayHello= " + sayHello.sayHello("xgc")); } } ``` ### 自动化配置 例如我有一个 UserService,我希望在自动化扫描时,这个类能够自动注册到Spring 容器中去,那么可以给该类添加一个 @Service,作为一个标记。 和 @Service 注解功能类似的注解,一共有四个: - @Component - @Repository - @Service - @Controller 这四个中,另外三个都是基于 @Component 做出来的,而且从目前的源码来看,功能也是一致的,那么为什么要搞三个呢?主要是为了在不同的类上面添加时方便。 - 在 Service 层上,添加注解时,使用 @Service - 在 Dao 层,添加注解时,使用 @Repository - 在 Controller 层,添加注解时,使用 @Controller - 在其他组件上添加注解时,使用 @Component ```java @Service public class UserService { public List getAllUsers() { List users = new ArrayList<>(); for (int i = 0; i <10;i++) { users.add("xgc"+i); } return users; } } ``` JavaConfig.class ```java @Configuration @ComponentScan(basePackages = {"com.ylesb.ioc.service"})//添加扫描具体包 如果加了@service等就会被扫描,不加默认扫描本类所在的包 public class JavaConfig { //@Bean @Bean("sh")//获取的时候就是需要填sh SayHello sayHello() { return new SayHello(); } } ``` 这里有几个问题需要注意: 1.Bean 的名字叫什么? 默认情况下,Bean 的名字是类名首字母小写。例如上面的 UserService,它的实例名,默认就是 userService。如果开发者想要自定义名字,就直接在 @Service 注解中添加即可。 2.有几种扫描方式? 上面的配置,我们是按照包的位置来扫描的。也就是说,Bean 必须放在指定的扫描位置,否则,即使你有 @Service 注解,也扫描不到。 除了按照包的位置来扫描,还有另外一种方式,就是根据注解来扫描。例如如下配置: ```java @Configuration @ComponentScan(basePackages = "org.javaboy.javaconfig",useDefaultFilters = true,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)}) public class JavaConfig { } ``` xml注解方式 ```xml ``` ### 对象注入 自动扫描时的对象注入有三种方式: 1.@Autowired 2.@Resources 3.@Injected @Autowired 是根据类型去查找,然后赋值,这就有一个要求,这个类型只可以有一个对象,否则就会报错。@Resources 是根据名称去查找,默认情况 下,定义的变量名,就是查找的名称,当然开发者也可以在 @Resources 注解中手动指定。所以,如果一个类存在多个实例,那么就应该使用 @Resources 去注入,如果非常使用 @Autowired,也是可以的,此时需要配合另外一个注解,@Qualifier,在 @Qualifier 中可以指定变量名,两个一起用(@Qualifier 和 @Autowired)就可以实现通过变量名查找到变量。 ```java @Repository public class UserDao { public String hello() { return "hello"; } } ``` ### 条件注解 条件注解就是在满足某一个条件的情况下,生效的配置。 首先在 Windows 中如何获取操作系统信息?Windows 中查看文件夹目录的命令是 dir,Linux 中查看文件夹目录的命令是 ls,我现在希望当系统运行在 Windows 上时,自动打印出 Windows 上的目录展示命令,Linux 运行时,则自动展示 Linux 上的目录展示命令。 shoucmd.class ```java public interface ShowCmd { public String showCmd(); } ``` WindCmd.class ```java public class WindCmd implements ShowCmd{ @Override public String showCmd() { return "dir"; } } ``` LinuxCmd.class ```java public class LinuxCmd implements ShowCmd{ @Override public String showCmd() { return "ls"; } } ``` WindCondition.class ```java public class WindCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String osName = context.getEnvironment().getProperty("os.name"); return osName.toLowerCase().contains("win"); } } ``` LinuxCondition.class ```java public class LinuxCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String osName = context.getEnvironment().getProperty("os.name"); return osName.toLowerCase().contains("linux"); } } ``` JavaConfig.class ```java @Configuration public class JavaConfig { @Bean("cmd") @Conditional(WindCondition.class) ShowCmd winCmd() { return new WindCmd(); } @Bean("cmd") @Conditional(LinuxCondition.class) ShowCmd LinuxCmd() { return new LinuxCmd(); } } ``` ShowCmdTest.class ```java public class ShowCmdTest { public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class); ShowCmd cmd = (ShowCmd)ctx.getBean("cmd"); String s= cmd.showCmd(); System.out.println("s = " +s); } } ``` ### 环境切换 开发中,如何在 开发/生产/测试 环境之间进行快速切换?Spring 中提供了Profile 来解决这个问题,Profile 的底层就是条件注解。 这个从 @Profile 注解的定义就可以看出来。 DataSource.class ```java public class DataSource { private String username; private String password; private String url; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } @Override public String toString() { return "DataSource{" + "username='" + username + '\'' + ", password='" + password + '\'' + ", url='" + url + '\'' + '}'; } } ``` JavaConfig.class ```java @Configuration public class JavaConfig { @Bean("cmd") @Conditional(WindCondition.class) ShowCmd winCmd() { return new WindCmd(); } @Bean("cmd") @Conditional(LinuxCondition.class) ShowCmd LinuxCmd() { return new LinuxCmd(); } @Bean() @Profile("dev") DataSource devDS() { DataSource ds = new DataSource(); ds.setUrl("devUrl"); ds.setUsername("devroot"); ds.setPassword("dev"); return ds; } @Bean() @Profile("prod") DataSource prodDS() { DataSource ds = new DataSource(); ds.setUrl("prodUrl"); ds.setUsername("prodroot"); ds.setPassword("prod"); return ds; } } ``` 在ShowCmdTest中就可以进行测试了。 ``` AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("prod");//设置相关开发 ctx.register(JavaConfig.class); ctx.refresh(); DataSource ds = ctx.getBean(DataSource.class); System.out.println("ds = " + ds); ``` 另一种就是xml中配置 ```xml ``` 启动方式。 ```java ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicat ionContext(); ctx.getEnvironment().setActiveProfiles("prod"); ctx.setConfigLocation("applicationContext.xml"); ctx.refresh(); DataSource dataSource = (DataSource) ctx.getBean("dataSource "); ``` ### Bean的作用域 从 Spring 容器中多次获取同一个 Bean,默认情况下,获取到的实际上是同一个实例。当然我们可以自己手动配置。 通过在 XML 节点中,设置 scope 属性,我们可以调整默认的实例个数。 scope 的值为 singleton(默认),表示这个 Bean 在 Spring 容器中,是以单 例的形式存在,如果 scope 的值为 prototype,表示这个 Bean 在 Spring 容器中不是单例,多次获取将拿到多个不同的实例。默认是单例模式。 除了 singleton 和 prototype 之外,还有两个取值,request 和 session。这两个取值在 web 环境下有效。这是在 XML 中的配置,我们也可以在 Java 中配置。 配置就是添加注解@Scope("prototype") ```java AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("prod"); ctx.register(JavaConfig.class); ctx.refresh(); DataSource ds1 = ctx.getBean(DataSource.class); DataSource ds2 = ctx.getBean(DataSource.class); System.out.println(ds1 == ds2);//判断是否相同 ``` ### id 和name 的区别 在 XML 配置中,我们可以看到,即可以通过 id 给 Bean 指定一个唯一标识符, 也可以通过 name 来指定,大部分情况下这两个作用是一样的,有一个小小区别: name 支持取多个。多个 name 之间,用 , 隔开。 ### 混合配置 混合配置就是 Java 配置+XML 配置。混用的话,可以在 Java 配置中引入 XML 配置。 ```java @Configuration @ImportResource("classpath:applicationContext.xml") public class JavaConfig { } ``` 在 Java 配置中,通过 @ImportResource 注解可以导入一个 XML 配置。 ## Aop Aop(Aspect Oriented Programming),面向切面编程,这是对面向对象思想的一种补充。 面向切面编程,就是在程序运行时,不改变程序源码的情况下,动态的增强方法的功能,常见的使用场景非常多。 1.日志 2.事务 3.数据库操作 这些操作中,无一例外,都有很多模板化的代码,而解决模板化代码,消除臃肿就是 Aop 的强项。 在 Aop 中,有几个常见的概念: 概念 说明 切点 要添加代码的地方,称作切点 通知(增强) 通知就是向切点动态添加的代码 切面 切点+通知 连接点 切点的定义 #### Aop 的实现 在 Aop 实际上集基于 Java 动态代理来实现的。 Java 中的动态代理有两种实现方式: - cglib - jdk ##### 动态代理 基于 JDK 的动态代理。 1.定义一个计算器接口: ```java public interface MyCalculator { int add(int a, int b); } ``` 2.定义计算机接口的实现: ```java public class MyCalculatorImpl implements MyCalculator { public int add(int a, int b) { return a+b; } } ``` 3.定义代理类 ```java public class CalculatorProxy { public static Object getInstance(MyCalculatorImpl myCalculator){ return Proxy.newProxyInstance(myCalculator.getClass().getClassLoader(), myCalculator.getClass().getInterfaces(), new InvocationHandler() { @Override 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; } }); } } ``` Proxy.newProxyInstance 方法接收三个参数,第一个是一个 classloader,第二个是代理多项实现的接口,第三个是代理对象方法的处理器,所有要额外添加的行为都在 invoke 方法中实现。 ### 五种通知 - 前置通知 - 后置通知 - 异常通知 - 返回通知 - 环绕通知 ```xml org.aspectj aspectjrt 1.9.7 org.aspectj aspectjweaver 1.9.7 ``` 接下来,定义切点,这里介绍两种切点的定义方式: - 使用自定义注解 - 使用规则 其中,使用自定义注解标记切点,是侵入式的,所以这种方式在实际开发中不推荐, 仅作为了解,另一种使用规则来定义切点的方式,无侵入,一般推荐使用这种方式。 ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Action { } ``` 然后在需要的地方使用注解@Action ```java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Action { } ``` ```java @Component @Aspect public class LogAspect { @Before("@annotation(Action)") //前置通知 public void before(JoinPoint joinPoint){ String name = joinPoint.getSignature().getName(); System.out.println(name+"前方法执行了"); } @After("@annotation(Action)") //后置通知 public void after(JoinPoint joinPoint){ String name = joinPoint.getSignature().getName(); System.out.println(name+"后方法执行了"); } @AfterReturning(value = "@annotation(Action)",returning = "r")//要与下面方法的参数一致 //返回通知,获取返回的结果 注解的方法有返回值类型一致才有返回通知 public void returning(JoinPoint joinPoint,Integer r){ String name = joinPoint.getSignature().getName(); System.out.println(name+"返回通知"+r); } @AfterThrowing(value = "@annotation(Action)",throwing = "e")//要与下面方法的参数一致 //异常通知,获取返回的结果 注解的方法有异常类型才有返回通知 public void afterThrowing(JoinPoint joinPoint,Exception e){ String name = joinPoint.getSignature().getName(); System.out.println(name+"抛出异常"+e.getMessage()); } @Around("@annotation(Action)")//要与下面方法的参数一致 //环绕通知,获取返回的结果 注解的方法有异常类型才有返回通知 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { Object result = joinPoint. proceed(new Object[]{1,2});//强制修改数据 return result; } } ``` 但是,大家也注意到,使用注解是侵入式的,我们还可以继续优化,改为非侵入式的。重新定义切点,新切点的定义就不在需要 @Action 注解了,要拦截的目标方法上也不用添加 @Action 注解。下面这种方式是更为通用的拦截方式: ```java /** *可以统一定义切点 */ @Pointcut("@annotation(Action)") public void pointcut() {}//下面引入pointcut()方法就可以了 /** *可以统一定义切点 *第一个 * 表示要拦截的目标方法返回值任意(也可以明确指定返回值类型 *第二个 * 表示包中的任意类(也可以明确指定类 *第三个 * 表示类中的任意方法 *最后面的两个点表示方法参数任意,个数任意,类型任意 */ @Pointcut("execution(* com.ylesb.aop.service.*.*(..))") public void pointcut() {} ``` XML引入AOP 然后再test引入相关xml就可以了 ``` xml ```