说明: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);
}
}