学习Spring第四天
Spring PropertyPlaceholderConfigurer的使用
对于一些隐秘的或者是全局变量的操作我们希望可以统一的管理他,我们可以把这些配置写在properties或者yaml文件里,然后在xml配置文件里获取相应的值,PropertyPlaceholderConfigurer就可以帮助我们实现这个功能
我们还是从简单的jdbc开始,项目结构如下:
gradle依赖
testCompile group: 'junit', name: 'junit', version: '4.12'
compile group: 'org.springframework', name: 'spring-context', version: '5.0.0.RELEASE'
compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.8-dmr'
compile group: 'org.springframework', name: 'spring-jdbc', version: '5.0.0.RELEASE'
compile group: 'org.springframework', name: 'spring-core', version: '5.0.0.RELEASE'
数据表如下:
首先我们新建一个Entity
Customer.java
package com.demo.Model;
public class Customer {
private int custId;
private String name;
private int age;
public void setCustId(int custId) {
this.custId = custId;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public int getCustId() {
return custId;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Cust_Id: " + custId + "\nName: " + name + "\nAge: " + age;
}
}
Dao层接口实现
CustomerDao.java
package com.demo.Dao;
import com.demo.Model.Customer;
public interface CustomerDao {
void insert(Customer customer);
Customer findById(int custId);
}
Impl实现接口方法
CustomerDaoImpl.java
package com.demo.Impl;
import com.demo.Dao.CustomerDao;
import com.demo.Model.Customer;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import java.util.List;
public class CustomerDaoImpl extends JdbcDaoSupport implements CustomerDao {
@Override
public void insert(Customer customer) {
String sql = "INSERT INTO customer VALUES (? ,? ,?)";
getJdbcTemplate().update(sql, customer.getCustId(), customer.getName(), customer.getAge());
}
@Override
public Customer findById(int custId) {
String sql = "SELECT * FROM customer WHERE CUST_ID = ?";
List<Customer> customers = getJdbcTemplate().query(sql, new Object[]{custId}, new BeanPropertyRowMapper<>(Customer.class));
return customers.get(0);
}
}
上面都是很基本的Jdbc操作,通过增删改查来测试代码
不同于之前,我们把数据库的变量写在properties里
data-config.properties
jdbc.driverClassName=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/Spring
jdbc.username=root
jdbc.password=19970819wy
Spring-Beans中配置dataSource和beans
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="database-config.properties"/>
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="customer" class="com.demo.Impl.CustomerDaoImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
这里我们首先通过PropertyPlaceholderConfigurer来导入properties里的变量(location属性定位properties文件)
然后我们就可以在dataSource里通过${}来获取对应的值
测试类
App.java
package com.demo;
import com.demo.Impl.CustomerDaoImpl;
import com.demo.Model.Customer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Beans.xml");
Customer customer = new Customer();
customer.setCustId(1);
customer.setName("RenBuRuGu");
customer.setAge(20);
CustomerDaoImpl impl = context.getBean(CustomerDaoImpl.class);
impl.insert(customer);
System.out.println(impl.findById(1));
}
}
Bean的继承
继承的方式有两种 直接继承和抽象继承
子类可以继承父类的一系列属性
直接继承
我个人认为有两种 一种是class由父类指定,子类不指定class,那么父类和子类都会共享同一个实体类,子类可以省略父类既定的字段或者重写。第二种是父类子类都指定class,这里的class不一定要存在继承关系,只要相应的class有共享的字段即可(我感觉这样不是很好)
创建一个entity
package com.demo.Model;
public class Customer {
private int type;
private String action;
private String country;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
@Override
public String toString() {
return "type: " + type + "\naction: " + action + "\ncountry: " + country;
}
}
xml配置文件
<bean id="baseBean" class="com.demo.Model.Customer">
<property name="country" value="China"/>
</bean>
<bean id="customerBean" parent="baseBean">
<property name="action" value="buy"/>
<property name="type" value="1"/>
</bean>
这样子类的country字段就变成China了 运行结果如下:
抽象继承
抽象继承也分为两种,一般抽象继承和纯抽象继承
抽象继承的目的是为了父类只提供属性模板而不可被实例化,直接抽象继承很简单,只需要在父类后面加一个abstract="true"
即可
<bean id="baseBean" class="com.demo.Model.Customer" abstract="true">
<property name="country" value="China"/>
</bean>
<bean id="customerBean" parent="baseBean">
<property name="action" value="buy"/>
<property name="type" value="1"/>
</bean>
纯抽象继承允许父类不设置class,只是为了共享字段使用
<bean id="baseBean" abstract="true">
<property name="country" value="China"/>
</bean>
<bean id="customerBean" parent="baseBean" class="com.demo.Model.Customer">
<property name="action" value="buy"/>
<property name="type" value="1"/>
</bean>
- 父类的属性值在子类中可以被覆盖
##依赖检查(感觉4.x这个功能不被支持,更多的使用注解完成(如@Required字段))
当我们在xml配置文件中没有对属性值进行赋值时,一般不会报错,如果我们需要它报错,就要用到依赖检查这个功能(
dependency-check
)
依赖检查分为四种 none(默认) simple objects all
默认方式为none,即他不会检查是否赋值
simple方式只检查基本数据类型(int, long,double…)和集合类型(map, list..),如果以上任何属性都没有设置,UnsatisfiedDependencyException将被抛出
objects方式检查对象类型的数据
all检查任何类型的数据
举一个objects的例子
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="CustomerBean" class="com.demo.Model.Customer"
dependency-check="objects">
<property name="action" value="buy" />
<property name="type" value="1" />
</bean>
<bean id="PersonBean" class="com.demo.Model.Person">
<property name="name" value="RenBuRuGu" />
<property name="address" value="address ABC" />
<property name="age" value="20" />
</bean>
</beans>
这里我们漏掉了person的注入,由于使用objects的依赖检查方式,将会收到UnsatisfiedDependencyException报错
@Required注解
对于必须的字段我们可以采用@Required注解的方式
public class Customer
{
private Person person;
private int type;
private String action;
public Person getPerson() {
return person;
}
@Required
public void setPerson(Person person) {
this.person = person;
}
}
还要在xml配置文件中打开annotation注解,像以前一样,有两种方式
<context:annotation-config/>
和<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/>
自定义required注解(个人感觉没什么用…)
首先自定义一个注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Mandatory {
}
现在我们可以在业务逻辑中使用它
public class Customer
{
private Person person;
private int type;
private String action;
@Mandatory
public void setPerson(Person person) {
this.person = person;
}
}
最后需要在xml中去注册它
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor">
<property name="requiredAnnotationType" value="com.demo.Annotation.Mandatory"/>
</bean>
<bean id="CustomerBean" class="com.demo.Model.Customer">
<property name="action" value="buy" />
<property name="type" value="1" />
</bean>
InitializingBean和DisposableBean接口
这两个接口在我理解中是两个钩子,分别用在bean初始化完成和即将被销毁时调用
InitializingBean中的afterPropertiesSet()用于执行初始化方法
DisposableBean中的destroy()用于执行bean被容器销毁之前的操作
Person.java
package com.demo.Model;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class Person implements InitializingBean,DisposableBean {
private int age;
private String name;
private String address;
@Override
public void destroy() throws Exception {
System.out.println("Destroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("Init");
}
}
App.java
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("Spring-Beans.xml");
Person person = context.getBean(Person.class);
context.close();
}
}
运行结果如下:
官方不推荐这么做,我也不推荐,因为如果钩子方法都写在业务代码中,就违反了Spring的低耦合机制,业务代码和Spring容器的耦合度大大加强。
init-method和destroy-method
不继承接口,我们单纯的写两个方法,然后在xml配置文件中将他们指定为初始化和销毁之前执行的方法
package com.demo.Model;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
public class Person{
private int age;
private String name;
private String address;
public void destroy() throws Exception {
System.out.println("Destroy");
}
public void init() throws Exception {
System.out.println("Init");
}
}
我们在xml中手动管理
<bean id="person" class="com.demo.Model.Person"
init-method="init"
destroy-method="destroy"
p:name="RenBuRuGu"
p:age="20"
p:address="JiangSu NanJing"/>
@PostConstruct 和 @PreDestroy可以帮我们达到同样的目的 当然前提是需要在xml中开启注解
package com.demo.Model;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;public class Person{
private int age; private String name; private String address; @PreDestroy public void destroy() throws Exception { System.out.println("Destroy"); } @PostConstruct public void init() throws Exception { System.out.println("Init"); }
}
xml中不需要写init和destroy方法
Spring中的EL表达式
el表达式可以简化代码量,我觉得还是很有必要了解的
1、在使用PropertyPlaceholderConfigurer从properties文件引入配置信息时,使用${SpEL expression}
来引用
2、引用同一xml中的其他bean的属性,使用#{SpEL expression}
<bean id="itemBean" class="com.demo.Model.Item">
<property name="name" value="RenBuRuGu"/>
<property name="qty" value="10"/>
</bean>
<bean id="customer" class="com.demo.Model.Customer">
<property name="item" value="#{itemBean}"/>
<property name="itemName" value="#{itemBean.name}"/>
</bean>
Spring EL也可以使用纯注解的方式完成,但是有几个前提
1、每一个bean使用Component注解(个人理解相当于xml中配置bean)
2、相应的字段增加@Value注解,Spring EL表达式写在注解中
3、xml配置文件中开启自动扫描Components
item.java
package com.demo.Model;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("itemBean")
public class Item {
@Value("RenBuRuGu")
private String name;
@Value("10")
private int qty;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getQty() {
return qty;
}
public void setQty(int qty) {
this.qty = qty;
}
@Override
public String toString() {
return name + " " + qty;
}
}
Customer.java
package com.demo.Model;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component("customerBean")
public class Customer {
@Value("#{itemBean}")
private Item item;
@Value("#{itemBean.name}")
private String itemName;
public Item getItem() {
return item;
}
public void setItem(Item item) {
this.item = item;
}
public String getItemName() {
return itemName;
}
public void setItemName(String itemName) {
this.itemName = itemName;
}
@Override
public String toString() {
return item + " " + itemName;
}
}
Spring-Beans.xml
<context:component-scan base-package="com.demo.Model"/>
- EL表达式可以直接引用Entity实例、属性和方法,也可以执行java内置的方法,运算符和三元表达式
EL表达式中的正则匹配 可以简单的使用
matches
关键字@Component(“customerBean”)
public class Customer {
String emailRegEx = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)" +
"*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";
@Value("#{'100' matches '\\d+' }")
private boolean validDigit;
@Value("#{ ('100' matches '\\d+') == true ? " +
"'yes this is digit' : 'No this is not a digit' }")
private String msg;
@Value("#{emailBean.emailAddress matches customerBean.emailRegEx}")
private boolean validEmail;
}
ExpressionParser(不知道有什么用…)
看意思好像大概也许是提取字符并转化成相应的数据类型
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'put spel expression here'");
String msg = exp.getValue(String.class);
下面给个例子自己感受一下
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class App {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello World'");
String msg1 = exp.getValue(String.class);
System.out.println(msg1);
Expression exp2 = parser.parseExpression("'Hello World'.length()");
int msg2 = (Integer) exp2.getValue();
System.out.println(msg2);
Expression exp3 = parser.parseExpression("100 * 2");
int msg3 = (Integer) exp3.getValue();
System.out.println(msg3);
Item item = new Item("RenBuRuGu", 100);
StandardEvaluationContext itemContext = new StandardEvaluationContext(item);
Expression exp4 = parser.parseExpression("name");
String msg4 = exp4.getValue(itemContext, String.class);
System.out.println(msg4);
Expression exp5 = parser.parseExpression("name == 'RenBuRuGu'");
boolean msg5 = exp5.getValue(itemContext, Boolean.class);
System.out.println(msg5);
}
}
Spring自动扫描组件
Spring的业务逻辑分为好多层(DAO,Service,Controller,Views),主要目的还是为了解耦
一般我们都会通过xml的方式来注册组件
Dao层
首先创建一个CustomerDao
package com.demo.Dao;
public class CustomerDao {
@Override
public String toString() {
return "Hello, This is CustomerDao";
}
}
Service层实现业务逻辑
package com.demo.Service;
import com.demo.Dao.CustomerDao;
public class CustomerService {
CustomerDao customerDao;
public void setCustomerDao(CustomerDao customerDao) {
this.customerDao = customerDao;
}
@Override
public String toString() {
return "CustomerService [ customerDao = " + customerDao + " ]";
}
}
xml注册bean
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<bean id="customerDao" class="com.demo.Dao.CustomerDao"/>
<bean id="customerService" class="com.demo.Service.CustomerService">
<property name="customerDao" ref="customerDao"/>
</bean>
</beans>
测试函数
package com.demo;
import com.demo.Service.CustomerService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("Spring-Beans.xml");
CustomerService service = context.getBean(CustomerService.class);
System.out.println(service);
}
}
运行结果如下:
这是一般做法
我们还可以通过自动扫描组件来完成相同的工作
像之前所说,给所有的实体类加上@Components注解,通过注解的方式注册实体类
CustomerDao.java
package com.demo.Dao;
import org.springframework.stereotype.Component;
@Component
public class CustomerDao {
@Override
public String toString() {
return "Hello, This is CustomerDao";
}
}
CustomerService.java
package com.demo.Service;
import com.demo.Dao.CustomerDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CustomerService {
private CustomerDao customerDao;
@Autowired
public void setCustomerDao(CustomerDao customerDao) {
this.customerDao = customerDao;
}
@Override
public String toString() {
return "CustomerService [ customerDao = " + customerDao + " ]";
}
}
然后在xml中开启自动扫描beans
<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-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.demo"/>
</beans>
运行结果相同
为了区分开功能不同的各个模块,Spring官方给出了四种注解
@Component – 指示自动扫描组件。
@Repository – 表示在持久层DAO组件。
@Service – 表示在业务层服务组件。
@Controller – 表示在表示层控制器组件。
所有的注解最后都会被编译为Components,但是这样就可以很好的区分业务层和持久层的组件
附上代码
CustomerDao.java
package com.demo.Dao;
import org.springframework.stereotype.Repository;
@Repository
public class CustomerDao {
@Override
public String toString() {
return "Hello, This is CustomerDao";
}
}
CustomerService.java
package com.demo.Service;
import com.demo.Dao.CustomerDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
private CustomerDao customerDao;
@Autowired
public void setCustomerDao(CustomerDao customerDao) {
this.customerDao = customerDao;
}
@Override
public String toString() {
return "CustomerService [ customerDao = " + customerDao + " ]";
}
}
component-scan过滤器的使用
过滤器有两种(include-filter和exclude-filter),顾名思义,一个是包含的过滤器另一个则是排除的过滤器.
filter可以通过很多种方式进行过滤,有以下几种类型:
annotation 注解方式
regix 正则表达式
custom 自定义方式
assignable 未知.....
aspectj 切片方式(面向切片编程)
通过正则过滤
<context:component-scan base-package="com.yiibai" >
<context:include-filter type="regex"
expression="com.demo.Dao.*DAO.*" />
<context:include-filter type="regex"
expression="com.demo.Services.*Service.*" />
</context:component-scan>
通过include的方式,Spring只会扫描在com.demo.Dao和com.demo.Service包下面名为_Dao._和_Service._的文件
通过annotation注解的方式
<context:component-scan base-package="com.demo">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
通过exclude-filter方式,所有@Service注解的entity将会被Spring忽略
Spring AOP
- 面向接口编程
我们首先来创建一个简单的小例子
创建Service层
CustomerService.java
package com.demo.Service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class CustomerService {
@Value("RenBuRuGu")
private String name;
@Value("https://wangyu1997.github.io/")
private String url;
public void printName() {
System.out.println("Customer name: " + this.name);
}
public void printUrl() {
System.out.println("Customer url: " + this.url);
}
public void printThrowException() {
throw new IllegalArgumentException();
}
}
创建入口测试函数
package com.demo;
import com.demo.Service.CustomerService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan({"com.demo.Service"})
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(App.class);
CustomerService customerService = context.getBean(CustomerService.class);
customerService.printName();
customerService.printUrl();
customerService.printThrowException();
}
}
运行结果如下:
通过注解的方式加入AOP
1、之前通知
首先实现MethodBeforeAdvice方法
@Component("heiBeforeMethod")
public class HiBeforeMethod implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, @Nullable Object target) throws Throwable {
System.out.println("Method Before: Hey Spring AOP !");
}
}
在配置文件中注册一个代理bean
@Configuration
public class CustomerServiceProxy {
@Bean("customerServiceProxy")
public ProxyFactoryBean proxyFactoryBean() {
ProxyFactoryBean bean = new ProxyFactoryBean();
bean.setTargetName("customerService");
bean.setInterceptorNames("heiBeforeMethod");
return bean;
}
}
测试类中调用
@Configuration
@ComponentScan({"com.demo.Service","com.demo.Impl"})
@Import({CustomerServiceProxy.class})
public class App {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(App.class);
CustomerService customerService = (CustomerService) context.getBean("customerServiceProxy");
customerService.printName();
customerService.printUrl();
try {
customerService.printThrowException();
}catch (IllegalArgumentException e){
System.out.println("Throw exception...");
}
}
}
注意这里的引用不再是customerService
而是customerServiceProxy
运行结果如下:
2、返回后通知
通过重写AfterReturningAdvice的afterReturning方法来实现功能
接着上面再加一个类
package com.demo.Impl;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Component("afterReturn")
public class AfterReturn implements AfterReturningAdvice {
@Override
public void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable {
System.out.println("Spring after returning: Spring AOP!");
}
}
CustomerServiceProxy中InterceptorNames加入该类
bean.setInterceptorNames("afterReturn","heiBeforeMethod");
运行结果如下:
3、抛出后通知,即方法抛出异常后调用
同上,新建一个类实现ThrowsAdvice的afterThrowing方法即可
不过,我感觉,可能是在新版本中被删除了吧…
4、环绕通知
我感觉这种方式十分强大,首先实现MethodInterceptor的invoke方法,这里用到了反射
package com.demo.Impl;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component("aroundMethod")
public class AroundMethod implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("Method name : " + invocation.getMethod().getName());
System.out.println("Method arguments : " + Arrays.toString(invocation.getArguments()));
System.out.println("Around: Method Before");
try {
Object result = invocation.proceed();
System.out.println("Around: Method After");
return result;
} catch (Exception ignored) {
System.out.println("Around: Method Exception");
throw ignored;
}
}
}
运行结果如下: